diff --git a/README.md b/README.md index 32d3fb5..6e68237 100644 --- a/README.md +++ b/README.md @@ -7,57 +7,113 @@ This idea was inspired from https://stackoverflow.com/a/40981277 in order to be - .shn: **SH**ell **N**amespace - .shc: **SH**ell **C**lass -## Example syntax: +## Syntax: + + +To declare objects, there exists 3 scripts. + +- The main script in which we'll use our object (suggested extension: `.sh`) +- The namespace declaration script (suggested extension: `.shn`) +- The class declaration script (suggested extension: `.shc`) + +The full example is available in the "example" directory. ### Declaring objects. -`script.sh`: -This is the main script which will use our objects. +An object has a type name, properties and functions. +To declare a property, you can use the `property` function. For example, for an class named `Object`, you can declare a property name using: +```bash +property Object.name +``` +**NOTE**: Bash doesn't have a typing system, so you cannot set property types. +Class functions are declared the same way you would in bash, except it uses a prefix with object type. For example: +```bash +Object.print() { + echo "Example OOP from $(this.name)!" +} +``` +As you can see here, you can access properties of the object using the `this` keyword in a function call. +Similarly, you can set properties using a `=` and value argument. For example: +```bash +this.name = "New name" +``` + +Objects can also have constructors which will be called at the creation of the object with arguments provided at the creation. +They are simply a function with the name `constructor`. They aren't mandatory for any object. + +### Creating a namespace. +While you can import objects directly in the global namespace, it's recommanded to use a separate namespace file. + +When you've created your namespace file, you can specify the name of the namespace using the `namespace` keyword: +```bash +namespace Example +``` +You can then declare object classes using the `class` directive by specifying it's name and associated script file. For example: +```bash +class Object "Object.shc" +``` + +All objects created under this class will be accessible with namespace as prefix (here our Object class would be accessible under `Example.Object`). + +Similarly, static classes can be declared using the `static_class` keyword. +```bash +static_class Static "Static.shc" +``` + +**NOTE**: Static classes can't have properties. As all static classes are global, you can declare global variables directly. + +### Using objects. +Now that we've created our namespace, we will want to use it and our objects in our script. +First things first, we'll want to import the library `oop.sh`. Depending on where it's located, you will want to use a global variable indicating it's location. ```bash . $OOP_ROOT/oop.sh # Import library. - -importNamespace Example.shn - -Example.Object t1 "Test" -Example.Object t2 "Example" - -t1.print -t2.print - -t1.name = "New name" - -t1.print ``` -`Example.shn`: -This file declares our namespace and all the objects within it. - +After that, we'll want to import our namespace file with all it's classes prefixed in the namespace name. ```bash -# Namespace declaration. -namespace Example -# If namespace is set to null (no argument), then the object will be declared globally. -# Otherwise, the object will be declared within the namespace. - -# Object declaration, from class name to file name. -class Object "Object.shc" - +importNamespace "Example.shn" ``` -`Object.shc`: -This file will contain the object code. - +After that you can declare the object using the following syntax: ` [constructor arguments...]`. For example: ```bash -# Property declaration -property Object.name - -# Optional constructor. -Object.constructor() { - Object.name = $1 -} - -# Example function -Object.print() { - echo "Example OOP from $($this.name)!" -} +Example.Object obj1 "Test" ``` + +You can then call it's functions. +```bash +$obj1.print +obj1.print +``` +**NOTE**: The $ is not mandatory, but is recommanded for clarity. + +... or access and edit it's properties. +```bash +name=$(obj1.name) +obj1.name = "New name" +``` + +You can store objects in variables as a string. For example, you can have have objects as class arguments, function returs or arrays of objects like this: +```bash +Example.Object obj1 "First Object" +Example.Object obj2 "Second Object" +objs=($obj1 $obj2) +${objs[0]}.print +${objs[1]}.print +``` + +You can also access the static classes by using their class type directly. For example: +```bash +Example.Static.print "Example text" +``` + +If you find that using the namespace everytime is a bit cumbersome, you can use the `using` keyword to alias all classes of a namespace into the global namespace. +Example usage: +```bash +using Example + +Object usingObj "New" + +$usingObj.print +``` +**NOTE**: When `using` a namespace which contains static classes, please note that the static class file will be re-imported. diff --git a/oop.sh b/oop.sh index d467d89..c34f1ab 100644 --- a/oop.sh +++ b/oop.sh @@ -3,9 +3,16 @@ # This file contains all functions required to create a namespace. # Internal variables are marked with a beginning underscore, like in most other languages. # Signatures are a list of arguments.. Those within <> are mandatory, the ones within [] are optional. + +# Namespace related variables. _namespace="" _namespacePath=$(realpath $(dirname ${BASH_SOURCE[0]})) +# This dictionnary saves all classes for each namespace so they can be retreived and aliased. +declare -Ag _namespacesClasses +# This dictionnary links all files for static namespaces. +declare -Ag _namespacesStaticClasses + # Namespace declaration. # Signature: ([string namespaceName]) namespace() { @@ -13,6 +20,10 @@ namespace() { } # Imports a namespace into the current shell. +# It saves the path of the file so that relative paths can be +# properly resolved. +# For example, if the object Object exists within namespace Example, it +# will be accessible with "Example.Object". # Signature: () importNamespace() { namespaceFile=$1 @@ -21,6 +32,33 @@ importNamespace() { . $namespaceFile } +# Aliases the classes in global namespace. +# For example, if the object Object exists within namespace Example, it +# will be accessible with "Example.Object" and "Object". +# Signature: () +using() { + namespaceName=$1 + # Import static classes + if [ "${_namespacesStaticClasses[$namespaceName]}" != "" ]; then + oldNamespace=_namespace + namespace # Reset namespace + # Split all + staticClasses=$(echo "${_namespacesStaticClasses[$namespaceName]}" | tr ";" "\n") + for cl in $staticClasses; do + parts=$(echo "$cl" | tr ":" "\n") + static_class ${parts[0]} ${parts[1]} + done + namespace $oldNamespace + fi + # Import static classes + if [ "${_namespacesClasses[$namespaceName]}" != "" ]; then + classes=$(echo "${_namespacesClasses[$namespaceName]}" | tr ";" "\n") + for type in $classes; do + eval "$type() { $namespaceName.$type \$@; }" + done + fi +} + # Creates an object instance. # Signature: (, , , [string[] constructorArguments]) _createObject() { @@ -37,6 +75,8 @@ _createObject() { . <(sed s/this\\./$varName./g <(sed s/$type\\./$varName./g $associatedFile)) # Call the constructor eval "$varName.constructor $constructorArguments" + # alias the "varName" variable to itself, so that it can be used and transmitted in other variables (e.g: $varName.name would alias to varName.name) + eval "$varName='$varName'" } # Object creation. @@ -54,7 +94,42 @@ class() { associatedFile="$_namespacePath/$associatedFile" fi # Declares a new function for object initialisation. - eval "$objFullName() { _createObject $type $associatedFile \$@; }" + eval "$objFullName() { _createObject $type $associatedFile \$@; }" + # Save the class in the dictionnary for reference. + if [ "$_namespace" != "" ]; then + if [ "${_namespacesClasses[$_namespace]}" == "" ]; then + _namespacesClasses[$_namespace]=$type + else + _namespacesClasses[$_namespace]="${_namespacesClasses[$_namespace]};$type" + fi + fi +} + +# Static class creation +# Signature: (, ) +static_class() { + type=$1 # Type of the object as declared within the file. + associatedFile=$2 + objFullName=$type # Type of the object referenced elsewhere + + if [ "$_namespace" != "" ]; then + objFullName="${_namespace}.$type" + fi + + if [ ${associatedFile::1} != "/" ]; then # Relative path, we save only the absolute path + associatedFile="$_namespacePath/$associatedFile" + fi + + # Imports the file and replace all "." with the variable name. + . <(sed s/this\\./$objFullName./g <(sed s/$type\\./$objFullName./g $associatedFile)) + # Save the class in the dictionnary for reference. + if [ "$_namespace" != "" ]; then + if [ "${_namespacesStaticClasses[$_namespace]}" == "" ]; then + _namespacesStaticClasses[$_namespace]="$type:$associatedFile" + else + _namespacesStaticClasses[$_namespace]="${_namespacesStaticClasses[$_namespace]};$type:$associatedFile" + fi + fi } # Associated function for properties