├── .cirrus.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .idea ├── .name ├── bashsupport_project.xml ├── codeStyleSettings.xml ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── .travis.yml ├── LICENSE ├── README.md ├── TODO.md ├── build ├── build.sh └── test.sh ├── docs ├── exception.png └── unit.png ├── example ├── array.sh ├── exitcode.sh ├── human.sh ├── string.sh ├── testing.sh ├── trycatch.sh └── v1 │ ├── README.md │ ├── assign-reference.sh │ ├── capture-pipe.sh │ ├── debug.sh │ ├── exception-demo │ ├── demo.sh │ ├── maid.sh │ └── run.sh │ ├── logging.sh │ ├── run-tests.sh │ ├── seamless.sh │ ├── seamless2.sh │ ├── seamless3.sh │ ├── seamless4.sh │ ├── serialization.sh │ ├── steffen.sh │ ├── tmp │ ├── assigning-local.sh │ ├── backtrace-test.sh │ ├── build-min.sh │ ├── core-test.sh │ ├── import.sh │ ├── import2.sh │ ├── named-params.sh │ ├── test-extractlocal.sh │ ├── test.sh │ ├── test2.sh │ └── types │ │ └── examples.sh │ └── try-catch.sh ├── lib ├── Array │ ├── Contains.sh │ ├── Intersect.sh │ ├── List.sh │ └── Reverse.sh ├── String │ ├── GetSpaces.sh │ ├── IsNumber.sh │ ├── SanitizeForVariable.sh │ ├── SlashReplacement.sh │ └── UUID.sh ├── TypePrimitives │ ├── array.sh │ ├── boolean.sh │ ├── integer.sh │ ├── map.sh │ └── string.sh ├── UI │ ├── Color.sh │ ├── Color.var.sh │ ├── Console.sh │ └── Cursor.sh ├── oo-bootstrap.sh └── util │ ├── bash4.sh │ ├── class.sh │ ├── command.sh │ ├── exception.sh │ ├── exits.sh │ ├── log.sh │ ├── namedParameters.sh │ ├── pipe.sh │ ├── test.sh │ ├── tryCatch.sh │ ├── type.sh │ └── variable.sh ├── package.json ├── test ├── escaping.sh └── test-imports.sh └── your-script.sh /.cirrus.yml: -------------------------------------------------------------------------------- 1 | test_task: 2 | container: 3 | matrix: 4 | # version numbers found on https://hub.docker.com/_/bash 5 | image: bash:latest 6 | image: bash:5.0.3 7 | image: bash:4.4.23 8 | image: bash:4.3.48 9 | image: bash:4.2.53 10 | cpu: 1 11 | memory: 512M 12 | version_script: bash --version 13 | test_script: 14 | - cd example 15 | - ./array.sh 16 | - ./human.sh 17 | - ./string.sh 18 | - ./testing.sh 19 | - ./trycatch.sh 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text eol=lf 3 | *.ico binary 4 | *.png binary 5 | 6 | # Custom for Visual Studio 7 | *.cs diff=csharp 8 | 9 | # Standard to msysgit 10 | *.doc diff=astextplain 11 | *.DOC diff=astextplain 12 | *.docx diff=astextplain 13 | *.DOCX diff=astextplain 14 | *.dot diff=astextplain 15 | *.DOT diff=astextplain 16 | *.pdf diff=astextplain 17 | *.PDF diff=astextplain 18 | *.rtf diff=astextplain 19 | *.RTF diff=astextplain 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 4 | 5 | *.iml 6 | 7 | ## Directory-based project format: 8 | #.idea/ 9 | # if you remove the above rule, at least ignore the following: 10 | 11 | # User-specific stuff: 12 | .idea/workspace.xml 13 | .idea/tasks.xml 14 | .idea/dictionaries 15 | 16 | # Sensitive or high-churn files: 17 | .idea/dataSources.ids 18 | .idea/dataSources.xml 19 | .idea/sqlDataSources.xml 20 | .idea/dynamic.xml 21 | .idea/uiDesigner.xml 22 | 23 | # Gradle: 24 | .idea/gradle.xml 25 | .idea/libraries 26 | 27 | # Mongo Explorer plugin: 28 | .idea/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.ipr 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | /out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Crashlytics plugin (for Android Studio and IntelliJ) 46 | com_crashlytics_export_strings.xml 47 | crashlytics.properties 48 | crashlytics-build.properties 49 | 50 | 51 | ### OSX template 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | 70 | # Directories potentially created on remote AFP share 71 | .AppleDB 72 | .AppleDesktop 73 | Network Trash Folder 74 | Temporary Items 75 | .apdisk 76 | 77 | 78 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | bash-oo-framework -------------------------------------------------------------------------------- /.idea/bashsupport_project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | 3 | env: 4 | - BASH=latest 5 | - BASH=5.0.3 6 | - BASH=4.4.23 7 | - BASH=4.3.48 8 | - BASH=4.2.53 9 | before_script: 10 | - docker pull bash:$BASH 11 | - docker run bash:$BASH --version 12 | script: 13 | - docker run -v $PWD:/bash bash:$BASH bash/example/array.sh 14 | - docker run -v $PWD:/bash bash:$BASH bash/example/human.sh 15 | - docker run -v $PWD:/bash bash:$BASH bash/example/string.sh 16 | - docker run -v $PWD:/bash bash:$BASH bash/example/testing.sh 17 | - docker run -v $PWD:/bash bash:$BASH bash/example/trycatch.sh 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bazyli Brzóska @ https://invent.life/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bash Infinity 2 | ============= 3 | [![Build Status](https://travis-ci.com/niieani/bash-oo-framework.svg?branch=master)](https://travis-ci.com/niieani/bash-oo-framework) 4 | [![Build Status](https://api.cirrus-ci.com/github/niieani/bash-oo-framework.svg)](https://cirrus-ci.com/github/niieani/bash-oo-framework) 5 | [![Join the chat at https://gitter.im/niieani/bash-oo-framework](https://badges.gitter.im/niieani/bash-oo-framework.svg)](https://gitter.im/niieani/bash-oo-framework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | Bash Infinity is a standard library and a boilerplate framework for writing tools using **bash**. 8 | It's modular and lightweight, while managing to implement some concepts from C#, Java or JavaScript into bash. 9 | The Infinity Framework is also plug & play: include it at the beginning of your existing script to import any of the individual features such as error handling, and start using other features gradually. 10 | 11 | The aim of Bash Infinity is to maximize readability of bash scripts, minimize the amount of code repeat and create a central repository for well-written, and a well-tested standard library for bash. 12 | 13 | Bash Infinity transforms the often obfuscated "bash syntax" to a cleaner, more modern syntax. 14 | 15 | Disclaimer 16 | ========== 17 | 18 | Some components are more sturdy than others, and as-it-stands the framework lacks good test coverage (we need your help!). 19 | 20 | Due to the above and relatively high code-complexity, we have decided that it will make the most sense to do a rewrite for the next major version 3.0 (see discussion in #45), taking the best parts of the framework, while re-using established tools from bash community. 21 | 22 | At this point, I would **not recommend starting major projects** based on the whole framework. Instead, copy and paste parts you need, ideally those you understand, if you found a particular feature useful. 23 | 24 | Compatibility 25 | ============= 26 | 27 | Not all of the modules work with earlier versions of bash, as I test with **bash 4**. However, it should be possible (and relatively easy) to [port non-working parts](#porting-to-bash-3) to earlier versions. 28 | 29 | Quick-start 30 | =========== 31 | 32 | Single-file release and dynamic loading is not available for v2.0 yet. To load the framework locally, [read on](#how-to-use). 33 | 34 | Main modules 35 | ============ 36 | 37 | * automatic error handling with exceptions and visual stack traces (`util/exception`) 38 | * named parameters in functions (instead of $1, $2...) (`util/namedParameters`) 39 | * passing arrays and maps as parameters (`util/variable`) 40 | * **try-catch** implementation (`util/tryCatch`) 41 | * throwing custom **exceptions** (`util/exception`) 42 | * **import** keyword for clever sourcing of scripts à la *require-js* (`oo-bootstrap`) 43 | * handy aliases for **colors** and **powerline** characters to increase readability in the output of your scripts (`UI/Color`) 44 | * well-formatted, colorful **logging** to *stderr* or custom delegate functions (`util/log`) 45 | * **unit test** library (`util/test`) 46 | * standard library for the type system with plenty of useful functions (`util/type`) 47 | * operational chains for **functional programming** in bash (`util/type`) 48 | * **type system** for object-oriented scripting (`util/class`) 49 | 50 | All of the features are modular and it's easy to only import the ones you'd like to use, without importing the rest of the framework. For example, the named parameters or the try-catch modules are self-contained in individual files. 51 | 52 | Error handling with exceptions and `throw` 53 | ============================================== 54 | 55 | ``` 56 | import util/exception 57 | ``` 58 | 59 | One of the highlight features is error handling that should work out of the box. If the script generates an error it will break and display a call stack: 60 | 61 | ![example call stack](https://raw.githubusercontent.com/niieani/bash-oo-framework/master/docs/exception.png "Example Call Stack") 62 | 63 | You may also force an error by `throw`ing your own Exception: 64 | 65 | ```bash 66 | e="The hard disk is not connected properly!" throw 67 | ``` 68 | 69 | It's useful for debugging, as you'll also get the call stack if you're not sure where the call is coming from. 70 | 71 | **Exceptions** combined with *try & catch* give you safety without having to run with **-o errexit**. 72 | 73 | If you do something wrong, you'll get a detailed exception backtrace, highlighting the command where it went wrong in the line from the source. The script execution will be halted with the option to continue or break. 74 | On the other hand if you expect a part of block to fail, you can wrap it in a `try` block, and handle the error inside a `catch` block. 75 | 76 | Named parameters in functions 77 | ============================= 78 | 79 | ``` 80 | import util/namedParameters 81 | ``` 82 | 83 | In any programing language, it makes sense to use meaningful names for variables for greater readability. 84 | In case of Bash, that means avoiding using positional arguments in functions. 85 | Instead of using the unhelpful `$1`, `$2` and so on within functions to access the passed in values, you may write: 86 | 87 | ```bash 88 | testPassingParams() { 89 | 90 | [string] hello 91 | [string[4]] anArrayWithFourElements 92 | l=2 [string[]] anotherArrayWithTwo 93 | [string] anotherSingle 94 | [reference] table # references only work in bash >=4.3 95 | [...rest] anArrayOfVariedSize 96 | 97 | test "$hello" = "$1" && echo correct 98 | # 99 | test "${anArrayWithFourElements[0]}" = "$2" && echo correct 100 | test "${anArrayWithFourElements[1]}" = "$3" && echo correct 101 | test "${anArrayWithFourElements[2]}" = "$4" && echo correct 102 | # etc... 103 | # 104 | test "${anotherArrayWithTwo[0]}" = "$6" && echo correct 105 | test "${anotherArrayWithTwo[1]}" = "$7" && echo correct 106 | # 107 | test "$anotherSingle" = "$8" && echo correct 108 | # 109 | test "${table[test]}" = "works" 110 | table[inside]="adding a new value" 111 | # 112 | # I'm using * just in this example: 113 | test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct 114 | } 115 | 116 | fourElements=( a1 a2 "a3 with spaces" a4 ) 117 | twoElements=( b1 b2 ) 118 | 119 | declare -A assocArray 120 | assocArray[test]="works" 121 | 122 | testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." 123 | 124 | test "${assocArray[inside]}" = "adding a new value" 125 | ``` 126 | 127 | The system will automatically assign: 128 | * **$1** to **$hello** 129 | * **$anArrayWithFourElements** will be an array of params with values from $2 till $5 130 | * **$anotherArrayWithTwo** will be an array of params with values from $6 till $7 131 | * **$8** to **$anotherSingle** 132 | * **$table** will be a reference to the variable whose name was passed in as the 9th parameter 133 | * **$anArrayOfVariedSize** will be a bash array containing all the following params (from $10 on) 134 | 135 | In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays easily (and references to variables - this feature needs bash >=4.3 though)! Plus, the mapped variables are all in the local scope. 136 | This module is pretty light and works in bash 3 and bash 4 (except for references - bash >=4.3) and if you only want to use it separately from this project, get the file /lib/system/02_named_parameters.sh. 137 | 138 | Note: For lengths between 2-10 there are aliases for arrays, such as ```[string[4]]```, if you need anything more, you need to use the syntax ```l=LENGTH [string[]]```, like shown in the above example. Or, make your own aliases :). 139 | 140 | Using ```import``` 141 | ================== 142 | 143 | After bootstrapping, you may use `import` to load either the library files or your own files. 144 | The command will ensure they're only loaded once. You may either use a relative path from the file you're importing, a path relative to the file that first included the framework, or an absolute path. `.sh` suffix is optional. 145 | You can also load all the files inside of a directory by simply including the path to that directory instead of the file. 146 | 147 | Using `try & catch` 148 | ======================= 149 | 150 | ```bash 151 | import util/tryCatch 152 | import util/exception # needed only for Exception::PrintException 153 | ``` 154 | 155 | Sample usage: 156 | 157 | ```bash 158 | try { 159 | # something... 160 | cp ~/test ~/test2 161 | # something more... 162 | } catch { 163 | echo "The hard disk is not connected properly!" 164 | echo "Caught Exception:$(UI.Color.Red) $__BACKTRACE_COMMAND__ $(UI.Color.Default)" 165 | echo "File: $__BACKTRACE_SOURCE__, Line: $__BACKTRACE_LINE__" 166 | 167 | ## printing a caught exception couldn't be simpler, as it's stored in "${__EXCEPTION__[@]}" 168 | Exception::PrintException "${__EXCEPTION__[@]}" 169 | } 170 | ``` 171 | 172 | If any command fails (i.e. returns anything else than 0) in the ```try``` block, the system will automatically start executing the ```catch``` block. 173 | Braces are optional for the ```try``` block, but required for ```catch``` if it's multiline. 174 | 175 | Note: `try` is executed in a subshell, therefore you cannot assign any variables inside of it. 176 | 177 | Using Basic Logging, Colors and Powerline Emoji 178 | =============================================== 179 | 180 | ``` 181 | import util/log 182 | ``` 183 | 184 | ```bash 185 | # using colors: 186 | echo "$(UI.Color.Blue)I'm blue...$(UI.Color.Default)" 187 | 188 | # enable basic logging for this file by declaring a namespace 189 | namespace myApp 190 | # make the Log method direct everything in the namespace 'myApp' to the log handler called DEBUG 191 | Log::AddOutput myApp DEBUG 192 | 193 | # now we can write with the DEBUG output set 194 | Log "Play me some Jazz, will ya? $(UI.Powerline.Saxophone)" 195 | 196 | # redirect error messages to STDERR 197 | Log::AddOutput error STDERR 198 | subject=error Log "Something bad happened." 199 | 200 | # reset outputs 201 | Log::ResetAllOutputsAndFilters 202 | 203 | # You may also hardcode the use for the StdErr output directly: 204 | Console::WriteStdErr "This will be printed to STDERR, no matter what." 205 | ``` 206 | 207 | Both the colors and the Powerline characters fallback gracefully on systems that don't support them. 208 | To see Powerline icons, you'll need to use a powerline-patched font. 209 | 210 | For the list of available colors and emoji's take a look into [lib/UI/Color.sh](https://github.com/niieani/bash-oo-framework/blob/master/lib/UI/Color.sh). 211 | Fork and contribute more! 212 | 213 | See [Advanced Logging](#advanced-logging) below to learn more about advanced logging capabilities. 214 | 215 | Passing arrays, maps and objects as parameters 216 | ============================================== 217 | 218 | ``` 219 | import util/variable 220 | ``` 221 | 222 | The Variable utility offers lossless dumping of arrays and associative array (referred here to as `maps`) declarations by the use of the `@get` command. 223 | 224 | Combined with the `util/namedParameters` module, you can pass in either as individual parameters. 225 | 226 | A more readable way of specifying the will to pass a variable by it's declaration is to simply refer to the variable as `$var:yourVariableName`. 227 | 228 | In bash >=4.3, which supports references, you may pass by reference. This way any changes done to the variable within the function will affect the variable itself. To pass a variable by reference, use the syntax: `$ref:yourVariableName`. 229 | 230 | ```bash 231 | array someArray=( 'one' 'two' ) 232 | # the above is an equivalent of: declare -a someArray=( 'one' 'two' ) 233 | # except this one creates a $var:someArray method handler 234 | 235 | passingArraysInput() { 236 | [array] passedInArray 237 | 238 | # chained usage, see below for more details: 239 | $var:passedInArray : \ 240 | { map 'echo "${index} - $(var: item)"' } \ 241 | { forEach 'var: item toUpper' } 242 | 243 | $var:passedInArray push 'will work only for references' 244 | } 245 | 246 | echo 'passing by $var:' 247 | 248 | ## 2 ways of passing a copy of an array (passing by it's definition) 249 | passingArraysInput "$(@get someArray)" 250 | passingArraysInput $var:someArray 251 | 252 | ## no changes yet 253 | $var:someArray toJSON 254 | 255 | echo 256 | echo 'passing by $ref:' 257 | 258 | ## in bash >=4.3, which supports references, you may pass by reference 259 | ## this way any changes done to the variable within the function will affect the variable itself 260 | passingArraysInput $ref:someArray 261 | 262 | ## should show changes 263 | $var:someArray toJSON 264 | ``` 265 | 266 | Standard Library 267 | ================ 268 | 269 | ``` 270 | import util/type 271 | ``` 272 | 273 | The framework offers a standard library for the primitive types, such as string or array manipulations to make common tasks simpler and more readable. 274 | 275 | There are three ways to make use of the standard library. 276 | 277 | ### 1. Create variables by their handle-creating declaration 278 | 279 | If you create your variables using the oo-framework's handle-creating declarations, you can execute methods of the standard library by referring to your variable as: `$var:yourVariable someMethod someParameter`. 280 | 281 | Available handle-creating declarations: 282 | 283 | * string 284 | * integer 285 | * array 286 | * map 287 | * boolean 288 | 289 | Since bash doesn't support boolean variables natively, the boolean variable is a special case that always needs to be declared and modified using the handle-creating declaration. 290 | 291 | Example: 292 | 293 | ```bash 294 | # create a string someString 295 | string someString="My 123 Joe is 99 Mark" 296 | 297 | # saves all matches and their match groups for the said regex: 298 | array matchGroups=$($var:someString getMatchGroups '([0-9]+) [a-zA-Z]+') 299 | 300 | # lists all matches in group 1: 301 | $var:matchGroups every 2 1 302 | 303 | ## group 0, match 1 304 | $var:someString match '([0-9]+) [a-zA-Z]+' 0 1 305 | 306 | # calls the getter - here it prints the value 307 | $var:someString 308 | ``` 309 | 310 | ### 2. Invoke the methods with `var:` 311 | 312 | If you didn't create your variables with their handles, you can also use the method `var:` to access them. 313 | 314 | Example: 315 | 316 | ```bash 317 | # create a string someString 318 | declare someString="My 123 Joe is 99 Mark" 319 | 320 | # saves all matches and their match groups for the said regex: 321 | declare -a matchGroups=$(var: someString getMatchGroups '([0-9]+) [a-zA-Z]+') 322 | 323 | # lists all matches in group 1: 324 | var: matchGroups every 2 1 325 | 326 | ## group 0, match 1 327 | var: someString match '([0-9]+) [a-zA-Z]+' 0 1 328 | 329 | # calls the getter - here it prints the value 330 | var: someString 331 | ``` 332 | 333 | ### 3. Pipe the variable declaration directly to the method 334 | 335 | Finally, you can also pipe the variable declarations to the methods you wish to invoke. 336 | 337 | Example: 338 | 339 | ```bash 340 | # create a string someString 341 | declare someString="My 123 Joe is 99 Mark" 342 | 343 | # saves all matches and their match groups for the said regex: 344 | declare -a matchGroups=$(@get someString | string.getMatchGroups '([0-9]+) [a-zA-Z]+') 345 | 346 | # lists all matches in group 1: 347 | @get matchGroups | array.every 2 1 348 | 349 | ## group 0, match 1 350 | @get someString | string.match '([0-9]+) [a-zA-Z]+' 0 1 351 | 352 | # prints the value 353 | echo "$someString" 354 | ``` 355 | 356 | ## Adding to the Standard Library 357 | 358 | You can add your own, custom methods to the Standard Library by declaring them like: 359 | 360 | ```bash 361 | string.makeCool() { 362 | @resolve:this ## this is required is you want to make use of the pipe passing 363 | local outValue="cool value: $this" 364 | @return outValue 365 | } 366 | 367 | string someString="nice" 368 | $var:someString makeCool 369 | # prints "cool value: nice" 370 | ``` 371 | 372 | See more info on writing classes below. 373 | 374 | Functional/operational chains with the Standard Library and custom classes 375 | ========================================================================== 376 | 377 | ``` 378 | import util/type 379 | ``` 380 | 381 | The type system in Bash Infinity allows you to chain methods together in a similar fashion one might pipe the output from one command to the other, or chain methods in C#, Java or JavaScript (think JQuery's pseudo-monad style). 382 | 383 | ```bash 384 | declare -a someArray=( 'one' 'two' ) 385 | 386 | var: someArray : \ 387 | { map 'echo "${index} - $(var: item)"' } \ 388 | { forEach 'var: item toUpper' } 389 | 390 | # above command will result in a definition of an array: 391 | # ( '0 - ONE' '1 - TWO' ) 392 | ``` 393 | 394 | Methods available in the next chain depend on the return type of the previously executed method. 395 | 396 | Writing your own classes 397 | ======================== 398 | 399 | It's really simple and straight-forward, like with most modern languages. 400 | 401 | Keywords for definition: 402 | 403 | * **class:YourName()** - defining a class 404 | 405 | Keywords to use inside of the class definition: 406 | 407 | * **method ClassName.FunctionName()** - Use for defining methods that have access to *$this* 408 | * **public SomeType yourProperty** - define public properties (works in all types of classes) 409 | * **private SomeType _yourProperty** - as above, but accessible only for internal methods 410 | * **$this** - This variable is available inside the methods, used to refer to the current type 411 | * **this** - Alias of $var:this, used to invoke methods or get properties of an object 412 | * NOT YET IMPLEMENTED: **extends SomeClass** - inherit from a base class 413 | 414 | After a class has been defined, you need to invoke `Type::Initialize NameOfYourType` or `Type::InitializeStatic NameOfYourStaticType` if you want to make your class a singleton. 415 | 416 | Here's an example that shows how to define your own classes: 417 | 418 | ```bash 419 | import util/namedParameters util/class 420 | 421 | class:Human() { 422 | public string name 423 | public integer height 424 | public array eaten 425 | 426 | Human.__getter__() { 427 | echo "I'm a human called $(this name), $(this height) cm tall." 428 | } 429 | 430 | Human.Example() { 431 | [array] someArray 432 | [integer] someNumber 433 | [...rest] arrayOfOtherParams 434 | 435 | echo "Testing $(var: someArray toString) and $someNumber" 436 | echo "Stuff: ${arrayOfOtherParams[*]}" 437 | 438 | # returning the first passed in array 439 | @return someArray 440 | } 441 | 442 | Human.Eat() { 443 | [string] food 444 | 445 | this eaten push "$food" 446 | 447 | # will return a string with the value: 448 | @return:value "$(this name) just ate $food, which is the same as $1" 449 | } 450 | 451 | Human.WhatDidHeEat() { 452 | this eaten toString 453 | } 454 | 455 | # this is a static method, hence the :: in definition 456 | Human::PlaySomeJazz() { 457 | echo "$(UI.Powerline.Saxophone)" 458 | } 459 | } 460 | 461 | # required to initialize the class 462 | Type::Initialize Human 463 | 464 | class:SingletonExample() { 465 | private integer YoMamaNumber = 150 466 | 467 | SingletonExample.PrintYoMama() { 468 | echo "Number is: $(this YoMamaNumber)!" 469 | } 470 | } 471 | 472 | # required to initialize the static class 473 | Type::InitializeStatic SingletonExample 474 | ``` 475 | 476 | Now you can use both the `Human` and the `SingletonExample` classes: 477 | 478 | ```bash 479 | # create an object called 'Mark' of type Human 480 | Human Mark 481 | 482 | # call the string.= (setter) method 483 | $var:Mark name = 'Mark' 484 | 485 | # call the integer.= (setter) method 486 | $var:Mark height = 180 487 | 488 | # adds 'corn' to the Mark.eaten array and echoes the output 489 | $var:Mark Eat 'corn' 490 | 491 | # adds 'blueberries' to the Mark.eaten array and echoes the uppercased output 492 | $var:Mark : { Eat 'blueberries' } { toUpper } 493 | 494 | # invoke the getter 495 | $var:Mark 496 | 497 | # invoke the method on the static instance of SingletonExample 498 | SingletonExample PrintYoMama 499 | ``` 500 | 501 | Writing Unit Tests 502 | ================== 503 | 504 | ``` 505 | import util/test 506 | ``` 507 | 508 | ![unit tests](https://raw.githubusercontent.com/niieani/bash-oo-framework/master/docs/unit.png "Unit tests for the framework itself") 509 | 510 | Similarly to [Bats](https://github.com/sstephenson/bats), you can use the unit test module to test Bash scripts or any UNIX program. 511 | Test cases consist of standard shell commands. Like Bats, Infinity Framework uses Bash's errexit (set -e) option when running test cases. Each test is run in a subshell, and is independent from one another. To quote from Bats: 512 | 513 | > If every command in the test case exits with a 0 status code (success), the test passes. In this way, each line is an assertion of truth. 514 | 515 | If you need to do more advanced testing, or need to be able to run your tests on shells other than bash 4, I'd still recommend Bats. 516 | 517 | Example usage: 518 | 519 | ```bash 520 | it 'should make a number and change its value' 521 | try 522 | integer aNumber=10 523 | aNumber = 12 524 | test (($aNumber == 12)) 525 | expectPass 526 | 527 | it "should make basic operations on two arrays" 528 | try 529 | array Letters 530 | array Letters2 531 | 532 | $var:Letters push "Hello Bobby" 533 | $var:Letters push "Hello Maria" 534 | 535 | $var:Letters contains "Hello Bobby" 536 | $var:Letters contains "Hello Maria" 537 | 538 | $var:Letters2 push "Hello Midori, 539 | Best regards!" 540 | 541 | $var:Letters2 concatAdd $var:Letters 542 | 543 | $var:Letters2 contains "Hello Bobby" 544 | expectPass 545 | ``` 546 | 547 | Can you believe this is bash?! ;-) 548 | 549 | Advanced Logging 550 | ================ 551 | 552 | ``` 553 | import util/log 554 | ``` 555 | 556 | Here's an example of how to use the power of advanced logging provided by the Infinity Framework. 557 | 558 | In every file you are logging from, you may name the logging scope (namespace). 559 | If you won't do it, it'll be the filename, minus the extension. 560 | It's better to name though, as filenames can conflict. 561 | Thanks to scopes, you can specify exactly what and how you want to log. 562 | 563 | ```bash 564 | namespace myApp 565 | 566 | ## ADD OUTPUT OF "myApp" TO DELEGATE STDERR 567 | Log::AddOutput myApp STDERR 568 | 569 | ## LET'S TRY LOGGING SOMETHING: 570 | Log "logging to stderr" 571 | ``` 572 | 573 | The above will simply print "logging to stderr" to STDERR. 574 | As you saw we used the logger output called "STDERR". It is possible to create and register your own loggers: 575 | 576 | ```bash 577 | ## LET'S MAKE A CUSTOM LOGGER: 578 | myLoggingDelegate() { 579 | echo "Hurray: $*" 580 | } 581 | 582 | ## WE NEED TO REGISTER IT: 583 | Log::RegisterLogger MYLOGGER myLoggingDelegate 584 | ``` 585 | 586 | Now, we can set it up so that it direct only logs from a specific function to the our custom logger output: 587 | 588 | ```bash 589 | ## WE WANT TO DIRECT ALL LOGGING WITHIN FUNCTION myFunction OF myApp TO MYLOGGER 590 | Log::AddOutput myApp/myFunction MYLOGGER 591 | 592 | ## LET'S DECLARE THAT FUNCTION: 593 | myFunction() { 594 | echo "Hey, I am a function!" 595 | Log "logging from myFunction" 596 | } 597 | 598 | ## AND RUN: 599 | myFunction 600 | ``` 601 | 602 | The above code should print: 603 | 604 | ``` 605 | Hey, I am a function! 606 | Hurray: logging from myFunction 607 | ``` 608 | 609 | As you can see, logging automatically redirected the logger from our function from our previously registered STDERR to our more specifically defined MYLOGGER. 610 | If you wish to keep logging to both loggers, you can disable the specificity filter: 611 | 612 | ```bash 613 | Log::DisableFilter myApp 614 | ``` 615 | 616 | Now if we run the function ```myFunction```: 617 | 618 | The output will be: 619 | 620 | ``` 621 | Hey, I am a function! 622 | Hurray: logging from myFunction 623 | logging from myFunction 624 | ``` 625 | 626 | We can be even more specific and redirect messages with specific *subjects* to other loggers, or mute them altogether: 627 | 628 | ```bash 629 | ## Assuming we're in the same file, let's reset first 630 | Log::ResetAllOutputsAndFilters 631 | 632 | Log::AddOutput myApp/myFunction MYLOGGER 633 | 634 | myFunction() { 635 | echo "Hey, I am a function!" 636 | Log "logging from myFunction" 637 | subject="unimportant" Log "message from myFunction" 638 | } 639 | ``` 640 | 641 | And let's change our custom logger a little, to support the subject: 642 | 643 | ```bash 644 | myLoggingDelegate() { 645 | echo "Hurray: $subject $*" 646 | } 647 | ``` 648 | 649 | Now when we run ```myFunction```, we should get: 650 | 651 | ``` 652 | Hey, I am a function! 653 | Hurray: logging from myFunction 654 | Hurray: unimportant message from myFunction 655 | ``` 656 | 657 | To filter (or redirect) messages with subject ```unimportant``` within ```myFunction``` of ```myApp```'s file: 658 | 659 | ```bash 660 | Log::AddOutput myApp/myFunction/unimportant VOID 661 | ``` 662 | 663 | To filter any messages with subject ```unimportant``` within ```myApp```'s file: 664 | 665 | ```bash 666 | Log::AddOutput myApp/unimportant VOID 667 | ``` 668 | 669 | Or any messages with subject ```unimportant``` anywhere: 670 | 671 | ```bash 672 | Log::AddOutput unimportant VOID 673 | ``` 674 | 675 | Now, running ```myFunction``` will print: 676 | 677 | ``` 678 | Hey, I am a function! 679 | Hurray: logging from myFunction 680 | ``` 681 | 682 | How to use? 683 | =========== 684 | 685 | 1. Clone or download this repository. You'll only need the **/lib/** directory. 686 | 2. Make a new script just outside of that directory and at the top place this: 687 | 688 | ```shell 689 | #!/usr/bin/env bash 690 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib/oo-bootstrap.sh" 691 | ``` 692 | 693 | 3. You may of course change the name of the **/lib/** directory to your liking, just change it in the script too. 694 | 4. Out-of-box you only get the import functionality. 695 | If you wish to use more features, such as the typing system, you'll need to import those modules as follows: 696 | 697 | ```shell 698 | # load the type system 699 | import util/log util/exception util/tryCatch util/namedParameters 700 | 701 | # load the standard library for basic types and type the system 702 | import util/class 703 | ``` 704 | 705 | 5. To import the unit test library you'll need to ```import lib/types/util/test```. 706 | The first error inside of the test will make the whole test fail. 707 | 708 | 6. When using `util/exception` or `util/tryCatch` don't use ```set -o errexit``` or ```set -e``` - it's not necessary, because error handling will be done by the framework itself. 709 | 710 | Contributing 711 | ============ 712 | 713 | Feel free to fork, suggest changes or new modules and file a pull request. 714 | Because of limitations and unnecessary complexity of the current implementation we're currently brainstorming a 3.0 rewrite in #45. 715 | 716 | The things that I'd love to add are: 717 | 718 | * unit tests for all important methods 719 | * port to bash 3 (preferably a dynamic port that imports the right file for the right version) 720 | * a web generator for a single file version of the boilerplate (with an option to select modules of your choice) 721 | * more functions for the standard library for primitive types (arrays, maps, strings, integers) 722 | * useful standard classes are very welcome too 723 | 724 | Porting to Bash 3 725 | ================= 726 | 727 | The main challenge in porting to **bash 3** lays with creating a polyfill for associative arrays (probably by using every other index for the keys in an array), which are used by the type system. The other challenge would be to remove the global declarations (`declare -g`). 728 | 729 | Acknowledgments 730 | =============== 731 | 732 | If a function's been adapted or copied from the web or any other libraries out there, I always mention it in a comment within the code. 733 | 734 | Additionally, in the making of the v1 of Bash Infinity I took some inspiration from object-oriented bash libraries: 735 | 736 | * https://github.com/tomas/skull/ 737 | * https://github.com/domachine/oobash/ 738 | * https://github.com/metal3d/Baboosh/ 739 | * http://sourceforge.net/p/oobash/ 740 | * http://lab.madscience.nl/oo.sh.txt 741 | * http://unix.stackexchange.com/questions/4495/object-oriented-shell-for-nix 742 | * http://hipersayanx.blogspot.sk/2012/12/object-oriented-programming-in-bash.html 743 | 744 | More bash goodness: 745 | 746 | * http://wiki.bash-hackers.org 747 | * http://kvz.io/blog/2013/11/21/bash-best-practices/ 748 | * http://www.davidpashley.com/articles/writing-robust-shell-scripts/ 749 | * http://qntm.org/bash 750 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * #magic beans: it's like magic for your bash script needs. turn your bash scripts from this: to this: (extensive example) 4 | * false boolean should return fail when invoked as a property 5 | * redirect throws to 3 or somewhere else and redirect that else to stderr, so they can't be supressed 6 | * md5sum in external imports 7 | * don't depend on mktemp (http://www.linuxsecurity.com/content/view/115462/151/#mozTocId316364) 8 | * save previous trap state before setting a new one and restore when unsetting 9 | * await/async for bash (perhaps coproc http://stackoverflow.com/questions/20017805/bash-capture-output-of-command-run-in-background & http://wiki.bash-hackers.org/syntax/keywords/coproc & http://www.ict.griffith.edu.au/anthony/info/shell/co-processes.hints or http://unix.stackexchange.com/a/116802/106138) 10 | * [function] argument resolver, checking if a method exists or defining an anonymous method 11 | * [commands/options] parser for params 12 | * autodownload precompiled bash 4 when running bash <4. 13 | * lib for mutex lock (http://wiki.bash-hackers.org/howto/mutex) 14 | * some functionality from how-to (http://wiki.bash-hackers.org/start) 15 | * md5sum of script requested online 16 | * recommend some libs, e.g.: 17 | * https://github.com/AsymLabs/realpath-lib 18 | * https://github.com/themattrix/bash-concurrent 19 | * https://github.com/jmcantrell/bashful 20 | 21 | ## import examples 22 | 23 | ``` 24 | import http://localhost:9000/test.sh 25 | import github:themattrix/bash-concurrent/master/demo.sh 26 | import github:avleen/bashttpd/master/bashttpd 27 | import github:sstephenson/bats/master/libexec/bats 28 | import github:AsymLabs/realpath-lib/master/make-generic-test.sh 29 | ``` 30 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | # source "$( cd "${BASH_SOURCE[0]%/*}/.." && pwd )/lib/oo-bootstrap.sh" 5 | # import lib/system/oo 6 | declare -g __oo__libPath="$( cd "${BASH_SOURCE[0]%/*}/../lib" && pwd )" 7 | 8 | concat() { 9 | local outputPath="$1" 10 | local -a inputFiles=("${@:2}") 11 | 12 | local blob 13 | local file 14 | 15 | for file in "${inputFiles[@]}" 16 | do 17 | blob+=$'\n' 18 | blob+=$(<"$file") 19 | done 20 | 21 | eval "main() { ${blob} " $'\n' " }" 22 | 23 | [[ ! -z "${replaceAliases+x}" ]] && main 24 | 25 | local body=$(declare -f main) 26 | body="${body#*{}" # trim start definition 27 | body="${body%\}}" # trim end definition 28 | 29 | printf %s "#!/usr/bin/env bash" > "$outputPath" 30 | 31 | while IFS= read -r line 32 | do 33 | ## NOTE: might mess up herestrings that start with spaces 34 | [[ "$line" == ' '* ]] && line="${line:4}" 35 | [[ "$line" == 'namespace '* ]] && continue 36 | printf %s "$line" >> "$outputPath" 37 | printf "\n" >> "$outputPath" 38 | done <<< "$body" 39 | 40 | # printf %s "#!/usr/bin/env bash${body}" > "$outputPath" 41 | } 42 | 43 | concat "out-$(git rev-parse --short HEAD).sh" "$__oo__libPath"/oo-bootstrap.sh "$__oo__libPath"/system/*.sh "$__oo__libPath"/system/oo/*.sh 44 | 45 | 46 | # evalIntoMain 47 | # declare -f main > out-$(git rev-parse --short HEAD).sh 48 | 49 | # minify() { 50 | # [string] filePath 51 | # [string] outputPath 52 | # 53 | # { 54 | # local IFS= 55 | # while read -r line 56 | # do 57 | # [[ $line != "System::Bootstrap" ]] && echo "$line" 58 | # done < "$filePath" >> "$outputFile" 59 | # } 60 | # } 61 | # 62 | # 63 | # build() { 64 | # [string] outputFile 65 | # 66 | # echo "#!/usr/bin/env bash" > "$outputFile" 67 | # echo "# oo-framework version: $(git rev-parse --short HEAD)" >> "$outputFile" 68 | # 69 | # minify "$__oo__libPath"/oo-bootstrap.sh "$outputFile" 70 | # 71 | # local file 72 | # local path 73 | # for file in "$__oo__libPath"/system/*.sh 74 | # do 75 | # # minify "$file" "$outputFile" 76 | # cat "$file" >> "$outputFile" 77 | # echo >> "$outputFile" 78 | # done 79 | # } 80 | 81 | # build ./oo-bootstrap.sh 82 | -------------------------------------------------------------------------------- /build/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source <(VERSION=2.0.0; URL="https://github.com/niieani/bash-oo-framework/releases/download/$VERSION/oo-bootstrap.sh"; RETRIES=3; hash curl 2>/dev/null && curl -sL --retry $RETRIES "$URL" || wget -t $RETRIES -O - -o /dev/null "$URL" || echo "echo 'An error occured while downloading the framework.' && exit 1") 5 | 6 | echo Works. 7 | -------------------------------------------------------------------------------- /docs/exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niieani/bash-oo-framework/0a9ea03e74256f3e4eec252623126b801d3491e2/docs/exception.png -------------------------------------------------------------------------------- /docs/unit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niieani/bash-oo-framework/0a9ea03e74256f3e4eec252623126b801d3491e2/docs/unit.png -------------------------------------------------------------------------------- /example/array.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/log util/type 6 | Log::AddOutput util/type CUSTOM 7 | 8 | manipulatingArrays() { 9 | array exampleArrayA 10 | array exampleArrayB 11 | 12 | $var:exampleArrayA push 'one' 13 | $var:exampleArrayA push 'two' 14 | 15 | # above is equivalent to calling: 16 | # var: exampleArrayA push 'two' 17 | # or using native bash 18 | # exampleArrayA+=( 'two' ) 19 | 20 | $var:exampleArrayA toString 21 | $var:exampleArrayA toJSON 22 | } 23 | 24 | passingArrays() { 25 | 26 | passingArraysInput() { 27 | [array] passedInArray 28 | 29 | $var:passedInArray : \ 30 | { map 'echo "${index} - $(var: item)"' } \ 31 | { forEach 'var: item toUpper' } 32 | 33 | $var:passedInArray push 'will work only for references' 34 | } 35 | 36 | array someArray=( 'one' 'two' ) 37 | 38 | echo 'passing by $var:' 39 | ## 2 ways of passing a copy of an array (passing by it's definition) 40 | passingArraysInput "$(var: someArray)" 41 | passingArraysInput $var:someArray 42 | 43 | ## no changes yet 44 | $var:someArray toJSON 45 | 46 | echo 47 | echo 'passing by $ref:' 48 | 49 | ## in bash >=4.3, which supports references, you may pass by reference 50 | ## this way any changes done to the variable within the function will affect the variable itself 51 | passingArraysInput $ref:someArray 52 | 53 | ## should show changes 54 | $var:someArray toJSON 55 | } 56 | 57 | ## RUN IT: 58 | manipulatingArrays 59 | passingArrays 60 | -------------------------------------------------------------------------------- /example/exitcode.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/exception util/tryCatch util/log util/test 6 | 7 | exit 1 8 | -------------------------------------------------------------------------------- /example/human.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/namedParameters util/class 6 | 7 | class:Human() { 8 | public string name 9 | public integer height 10 | public array eaten 11 | 12 | Human.__getter__() { 13 | echo "I'm a human called $(this name), $(this height) cm tall." 14 | } 15 | 16 | Human.Example() { 17 | [array] someArray 18 | [integer] someNumber 19 | [...rest] arrayOfOtherParams 20 | 21 | echo "Testing $(var: someArray toString) and $someNumber" 22 | echo "Stuff: ${arrayOfOtherParams[*]}" 23 | 24 | # returning the first passed in array 25 | @return someArray 26 | } 27 | 28 | Human.Eat() { 29 | [string] food 30 | 31 | this eaten push "$food" 32 | 33 | # will return a string with the value: 34 | @return:value "$(this name) just ate $food, which is the same as $1" 35 | } 36 | 37 | Human.WhatDidHeEat() { 38 | this eaten toString 39 | } 40 | 41 | # this is a static method, hence the :: in definition 42 | Human::PlaySomeJazz() { 43 | echo "$(UI.Powerline.Saxophone)" 44 | } 45 | } 46 | 47 | # required to initialize the class 48 | Type::Initialize Human 49 | 50 | # create an object called 'Mark' of type Human 51 | Human Mark 52 | 53 | # call the string.= (setter) method 54 | $var:Mark name = 'Mark' 55 | 56 | # call the integer.= (setter) method 57 | $var:Mark height = 180 58 | 59 | # adds 'corn' to the Mark.eaten array and echoes the output 60 | $var:Mark Eat 'corn' 61 | 62 | # adds 'blueberries' to the Mark.eaten array and echoes the uppercased output 63 | $var:Mark : { Eat 'blueberries' } { toUpper } 64 | 65 | # invoke the getter 66 | $var:Mark 67 | 68 | 69 | ## singleton 70 | 71 | class:SingletonExample() { 72 | private integer YoMamaNumber = 150 73 | 74 | SingletonExample.PrintYoMama() { 75 | echo "Number is: $(this YoMamaNumber)!" 76 | } 77 | } 78 | 79 | # required to initialize the static class 80 | Type::InitializeStatic SingletonExample 81 | 82 | # invoke the method on the static instance of SingletonExample 83 | SingletonExample PrintYoMama 84 | -------------------------------------------------------------------------------- /example/string.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/log util/type 6 | Log::AddOutput util/type CUSTOM 7 | 8 | regex() { 9 | # create a string someString 10 | string someString="My 123 Joe is 99 Mark" 11 | 12 | # saves all matches and their match groups for the said regex: 13 | array matchGroups=$($var:someString getMatchGroups '([0-9]+) [a-zA-Z]+') 14 | 15 | # lists all matches in group 1: 16 | $var:matchGroups every 2 1 17 | 18 | ## group 0, match 1 19 | $var:someString match '([0-9]+) [a-zA-Z]+' 0 1 20 | 21 | # calls the getter - here it prints the value 22 | $var:someString 23 | } 24 | 25 | regex 26 | -------------------------------------------------------------------------------- /example/testing.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/exception util/log util/type util/test 6 | Log::AddOutput util/type CUSTOM 7 | 8 | describe 'Operations on primitives' 9 | it 'should make a number and change its value using the setter' 10 | try 11 | integer aNumber=10 12 | $var:aNumber = 12 13 | [[ $aNumber -eq 12 ]] 14 | expectPass 15 | 16 | it "should make basic operations on two arrays" 17 | try 18 | array Letters 19 | array Letters2 20 | 21 | $var:Letters push "Hello Bobby" 22 | $var:Letters push "Hello Maria" 23 | 24 | $var:Letters contains "Hello Bobby" 25 | $var:Letters contains "Hello Maria" 26 | 27 | $var:Letters2 push "Hello Midori, 28 | Best regards!" 29 | 30 | $var:Letters2 concatPush $var:Letters 31 | $var:Letters2 contains "Hello Bobby" 32 | expectPass 33 | summary 34 | -------------------------------------------------------------------------------- /example/trycatch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/exception util/tryCatch util/log util/test 6 | 7 | describe 'Try Catch' 8 | it 'should throw a "test exception"' 9 | try 10 | try { 11 | e="test exception" throw 12 | } catch { 13 | [[ "${__EXCEPTION__[1]}" == "test exception" ]] 14 | } 15 | expectPass 16 | 17 | it 'should throw a general exception inside of a catch' 18 | try { 19 | try { 20 | false 21 | } catch { 22 | e="we throw again inside of a catch" throw 23 | echo this should not be displayed 24 | } 25 | echo this should not be displayed 26 | } 27 | expectFail 28 | 29 | summary 30 | -------------------------------------------------------------------------------- /example/v1/README.md: -------------------------------------------------------------------------------- 1 | ## Note 2 | 3 | These files need to be updated for v2. 4 | -------------------------------------------------------------------------------- /example/v1/assign-reference.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | #Log.Debug.SetLevel 3 6 | 7 | import lib/types/base 8 | import lib/types/ui 9 | import lib/types/util/test 10 | 11 | reftest() { 12 | [reference] table 13 | [reference] retval 14 | 15 | echo ${table[test]} 16 | table[ok]=added 17 | 18 | retval="boom" 19 | } 20 | 21 | declare -A assoc 22 | assoc[test]="hi" 23 | 24 | declare outerKlops 25 | reftest assoc outerKlops 26 | 27 | echo ${assoc[ok]} 28 | 29 | ## dynamic references 30 | 31 | passingTest() { 32 | [string] hello 33 | [string] makownik 34 | [string] third 35 | 36 | echo Inside Test Func 37 | echo $hello + $makownik + $third 38 | 39 | declare -n 40 | } 41 | 42 | second="works!" 43 | someArray=(a b c) 44 | 45 | passingTest first $ref:second $ref:someArray[1] 46 | 47 | 48 | 49 | #declare -n refToLocal="klops" 50 | #echo ${refToLocal[tat]} 51 | -------------------------------------------------------------------------------- /example/v1/capture-pipe.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s lastpipe 4 | 5 | Pipe::Capture() { 6 | read -r -d '' $1 7 | } 8 | 9 | # shopt -s expand_aliases 10 | # alias Pipe::Capture="read -r -d ''" 11 | 12 | Pipe::CaptureFaithful() { 13 | IFS= read -r -d '' $1 14 | } 15 | 16 | # declare -g awesome 17 | # declare awesome= 18 | printf "test1234\n\ntest999\n\n" | Pipe::Capture awesome 19 | # echo "test999" | Pipe::Capture awesome 20 | echo $awesome 21 | declare -p awesome -------------------------------------------------------------------------------- /example/v1/debug.sh: -------------------------------------------------------------------------------- 1 | func() { 2 | local wtf 3 | echo test 4 | local 5 | declare --p 6 | } 7 | func 8 | 9 | #trap 10 | # trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"; func' DEBUG 11 | # echo mama 12 | # 13 | # echo lol 14 | # echo works 15 | 16 | # echo test 17 | # echo test2 18 | # echo test3 19 | # trap 20 | # trap 'echo TEST' DEBUG 21 | # echo LALALA 22 | # trap - DEBUG 23 | # echo will it work? 24 | # echo yes? 25 | -------------------------------------------------------------------------------- /example/v1/exception-demo/demo.sh: -------------------------------------------------------------------------------- 1 | import maid 2 | 3 | 4 | prepareTheCastle() 5 | { 6 | true dance! 7 | makeTheRoom 'nice&clean' 8 | } 9 | 10 | makeTheRoom() { 11 | false || false 12 | echo $(callTheMaid "Franka" && bring VacuumCleaner) 13 | } -------------------------------------------------------------------------------- /example/v1/exception-demo/maid.sh: -------------------------------------------------------------------------------- 1 | callTheMaid(){ 2 | [string] maid 3 | 4 | [[ $maid == "Steven, The Robot" ]] || throw "We don't use real maids!" 5 | } -------------------------------------------------------------------------------- /example/v1/exception-demo/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../../lib/oo-bootstrap.sh" 5 | 6 | ## MAIN ## 7 | 8 | import lib/type-core 9 | import lib/types/base 10 | import lib/types/ui 11 | 12 | import tests/exception-demo/demo 13 | prepareTheCastle "Burning Candles" 14 | -------------------------------------------------------------------------------- /example/v1/logging.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | NO_UNICODE=true source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 5 | 6 | ## MAIN ## 7 | 8 | import lib/type-core 9 | import lib/types/base 10 | import lib/types/ui 11 | 12 | ## YOUR CODE GOES HERE ## 13 | 14 | ## NAME THE REFERENCE TO LOGGING FOR THIS FILE 15 | ## (if you won't do it it'll be the filename without the extension) 16 | ## DO THIS IN EVERY FILE YOU WANT TO USE LOGGING FROM 17 | ## TO BE ABLE TO SPECIFY EXACTLY WHAT AND HOW YOU WANT TO LOG 18 | namespace myApp 19 | 20 | ## ADD OUTPUT OF "myApp" TO STDERR 21 | Log::AddOutput myApp STDERR 22 | 23 | ## LET'S TRY LOGGING SOMETHING: 24 | Log "logging to stderr" 25 | 26 | ## LET'S MAKE A CUSTOM LOGGER: 27 | myLoggingDelegate() { 28 | echo "Hurray: $*" 29 | } 30 | 31 | ## WE NEED TO REGISTER IT: 32 | Log::RegisterLogger MYLOGGER myLoggingDelegate 33 | 34 | ## WE WANT TO DIRECT ALL LOGGING WITHIN FUNCTION myFunction OF myApp TO MYLOGGER 35 | Log::AddOutput myApp/myFunction MYLOGGER 36 | 37 | ## LET'S DECLARE THAT FUNCTION: 38 | myFunction() { 39 | echo "Hey, I am a function!" 40 | Log "logging from myFunction" 41 | } 42 | 43 | ## AND RUN: 44 | myFunction 45 | 46 | ## IT SHOULD PRINT: 47 | ## Hey, I am a function! 48 | ## Hurray: logging from myFunction 49 | 50 | ## As you can see, logging automatically redirected the logger from our function from our previously registered STDERR to our more specifically defined MYLOGGER 51 | ## If you wish to keep logging to both loggers, you can disable the specificity filter: 52 | Log::DisableFilter myApp 53 | 54 | ## Now if we run the function: 55 | myFunction 56 | 57 | ## The output will be: 58 | ## Hey, I am a function! 59 | ## Hurray: logging from myFunction 60 | ## logging from myFunction 61 | 62 | ## We can also be even more specific and redirect messages with specific subjects to other loggers or mute them 63 | ## Let's reset first 64 | Log::ResetAllOutputsAndFilters 65 | 66 | Log::AddOutput myApp/myFunction MYLOGGER 67 | 68 | myFunction() { 69 | echo "Hey, I am a function!" 70 | Log "logging from myFunction" 71 | subject="unimportant" Log "message from myFunction" 72 | } 73 | 74 | ## and let's change our custom logger a little, to support the subject: 75 | myLoggingDelegate() { 76 | echo "Hurray: $subject $*" 77 | } 78 | 79 | ## Now when we run: 80 | myFunction 81 | 82 | ## Will print: 83 | ## Hey, I am a function! 84 | ## Hurray: logging from myFunction 85 | ## Hurray: unimportant message from myFunction 86 | 87 | ## To filter messages with subject "unimportant" within myFunction of myApp's file: 88 | Log::AddOutput myApp/myFunction/unimportant VOID 89 | ## or any messages with subject "unimportant" within myApp's file: 90 | Log::AddOutput myApp/unimportant VOID 91 | ## or any messages with subject "unimportant" anywhere 92 | Log::AddOutput unimportant VOID 93 | 94 | ## Now when we run: 95 | myFunction 96 | 97 | ## Will print: 98 | ## Hey, I am a function! 99 | ## Hurray: logging from myFunction 100 | 101 | 102 | Log::AddOutput myApp INFO 103 | Log::DisableFilter oo/func 104 | Log::AddOutput oo/func DEBUG 105 | Log::AddOutput oo/func/creation CUSTOM 106 | 107 | func() { 108 | Log from-func 109 | 110 | subject=creation Log "from func with subject" 111 | } 112 | 113 | Log root 114 | func -------------------------------------------------------------------------------- /example/v1/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | # Log::AddOutput oo/System::LoadFile CUSTOM 6 | # Log::AddOutput oo/System::LoadFile/level2 VOID 7 | # Log::AddOutput oo CUSTOM 8 | # Log::AddOutput level1 CUSTOM 9 | # Log::AddOutput level2 CUSTOM 10 | # Log::AddOutput level3 CUSTOM 11 | # Log::AddOutput oo/level1 CUSTOM 12 | 13 | import lib/type-core 14 | import lib/types/base 15 | import lib/types/ui 16 | import lib/types/util/test 17 | 18 | Test.NewGroup "Named Parameters" 19 | it 'should try to assign map the params locally' 20 | try 21 | testPassingParams() { 22 | [string] hello 23 | [string[4]] anArrayWithFourElements 24 | 25 | # note: between 2-10 there are aliases for arrays like [string[4]] 26 | # after 10 you need to write l=LENGTH [string[]], like this: 27 | l=2 [string[]] anotherArrayWithTwo 28 | 29 | [string] anotherSingle 30 | [reference] table 31 | [...rest] anArrayOfVariedSize 32 | 33 | local thisShouldWork="correct" 34 | 35 | test "$hello" = "$1" 36 | # 37 | test "${anArrayWithFourElements[0]}" = "$2" 38 | test "${anArrayWithFourElements[1]}" = "$3" 39 | test "${anArrayWithFourElements[2]}" = "$4" 40 | test "${anArrayWithFourElements[3]}" = "$5" 41 | # 42 | test "${anotherArrayWithTwo[0]}" = "$6" 43 | test "${anotherArrayWithTwo[1]}" = "$7" 44 | # 45 | test "$anotherSingle" = "$8" 46 | # 47 | test "${table[test]}" = "works" 48 | table[inside]="adding a new value" 49 | # 50 | test "${anArrayOfVariedSize[*]}" = "${*:10}" 51 | # 52 | test "$thisShouldWork" = "correct" 53 | } 54 | 55 | fourElements=( a1 a2 "a3 with spaces" a4 ) 56 | twoElements=( b1 b2 ) 57 | declare -A assocArray 58 | assocArray[test]="works" 59 | 60 | testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." 61 | 62 | test "${assocArray[inside]}" = "adding a new value" 63 | 64 | # run twice, just to be sure we don't leave behind anythinh 65 | testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." 66 | 67 | expectPass 68 | 69 | Test.DisplaySummary 70 | 71 | 72 | Test.NewGroup "Objects" 73 | 74 | it 'should print a colorful message' 75 | try 76 | hexDump="0000000 1b 5b 30 3b 33 32 6d 48 65 6c 6c 6f 21 1b 5b 30" 77 | message=$(echo $(UI.Color.Green)Hello!$(UI.Color.Default) | hexdump | head -1) 78 | [[ "$hexDump" = "$message" ]] 79 | expectPass 80 | 81 | it 'should make an instance of an Object' 82 | try 83 | Object anObject 84 | test "$(anObject)" = "[Object] anObject" 85 | expectPass 86 | 87 | it 'should make an instance of a number' 88 | try 89 | Number aNumber 90 | Function::Exists aNumber 91 | expectPass 92 | 93 | it 'should have destroyed the previous instance' 94 | try 95 | ! Function::Exists aNumber 96 | expectPass 97 | 98 | it 'should make an instance of a number and initialize with 5' 99 | try 100 | Number aNumber = 5 101 | aNumber.Equals 5 102 | expectPass 103 | 104 | it 'should make a number and change its value' 105 | try 106 | Number aNumber = 10 107 | aNumber = 12 108 | # it's possible to compare with '==' operator too 109 | aNumber == 12 110 | expectPass 111 | 112 | it "should make basic operations on two arrays" 113 | try 114 | Array Letters 115 | Array Letters2 116 | 117 | Letters.Add "Hello Bobby" 118 | Letters.Add "Hello Maria" 119 | Letters.Contains "Hello Bobby" 120 | Letters.Contains "Hello Maria" 121 | 122 | Letters2.Add "Hello Midori, 123 | Best regards!" 124 | 125 | lettersRef=$(Letters) 126 | Letters2.Merge "${!lettersRef}" 127 | 128 | Letters2.Contains "Hello Bobby" 129 | expectPass 130 | 131 | it 'should make a boolean and assign false to it' 132 | try 133 | Boolean aBool = false 134 | ! $(aBool) 135 | expectPass 136 | 137 | it 'should make a boolean and assign true to it' 138 | try 139 | Boolean aBool = true 140 | $(aBool) 141 | expectPass 142 | 143 | it "is playing der saxomophone! $(UI.Powerline.Saxophone)" 144 | try 145 | sleep 0 146 | expectPass 147 | 148 | Test.DisplaySummary 149 | 150 | Test.NewGroup "Exceptions" 151 | 152 | it 'should manually throw and catch an exception' 153 | try 154 | throw 'I like to fail!' 155 | expectFail 156 | 157 | it 'should throw and catch an unknown reference exception' 158 | try 159 | unknown_reference # This will throw 160 | expectFail 161 | 162 | it 'should nest try-and-catch' 163 | try { 164 | try { 165 | try { 166 | throw "Inner-Most" 167 | echo Not executed 168 | } catch { 169 | cought 170 | throw "Outer" 171 | echo Not executed 172 | } 173 | } catch { 174 | cought 175 | } 176 | } 177 | expectOutputPass 178 | 179 | Test.DisplaySummary 180 | 181 | 182 | -------------------------------------------------------------------------------- /example/v1/seamless.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #__INTERNAL_LOGGING__=true 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 5 | 6 | namespace seamless 7 | 8 | Log::AddOutput seamless CUSTOM 9 | Log::AddOutput error ERROR 10 | #Log::AddOutput oo/parameters-executing CUSTOM 11 | 12 | alias ~="Exception::CustomCommandHandler" 13 | 14 | declare -Ag __oo__objectToType 15 | declare -Ag __oo__objectToName 16 | obj=OBJECT 17 | 18 | Type.CreateVar() { 19 | # USE DEFAULT IFS IN CASE IT WAS CHANGED 20 | local IFS=$' \t\n' 21 | 22 | local commandWithArgs=( $1 ) 23 | local command="${commandWithArgs[0]}" 24 | 25 | shift 26 | 27 | if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]] 28 | then 29 | return 0 30 | fi 31 | 32 | if [[ "${commandWithArgs[*]}" == "true" ]] 33 | then 34 | __typeCreate_next=true 35 | # Console::WriteStdErr "Will assign next one" 36 | return 0 37 | fi 38 | 39 | local varDeclaration="${commandWithArgs[*]:1}" 40 | if [[ $varDeclaration == '-'* ]] 41 | then 42 | varDeclaration="${commandWithArgs[*]:2}" 43 | fi 44 | local varName="${varDeclaration%%=*}" 45 | 46 | # var value is only important if making an object later on from it 47 | local varValue="${varDeclaration#*=}" 48 | 49 | # TODO: make this better: 50 | if [[ "$varValue" == "$varName" ]] 51 | then 52 | local varValue="" 53 | fi 54 | 55 | if [[ ! -z $__typeCreate_varType ]] 56 | then 57 | # Console::WriteStdErr "SETTING $__typeCreate_varName = \$$__typeCreate_paramNo" 58 | # Console::WriteStdErr -- 59 | #Console::WriteStdErr $tempName 60 | 61 | DEBUG Log "creating $__typeCreate_varName ($__typeCreate_varType) = $__typeCreate_varValue" 62 | 63 | if [[ -z "$__typeCreate_varValue" ]] 64 | then 65 | case "$__typeCreate_varType" in 66 | 'array'|'dictionary') eval "$__typeCreate_varName=()" ;; 67 | 'string') eval "$__typeCreate_varName=''" ;; 68 | 'integer') eval "$__typeCreate_varName=0" ;; 69 | * ) ;; 70 | esac 71 | fi 72 | 73 | case "$__typeCreate_varType" in 74 | 'array'|'dictionary'|'string'|'integer') ;; 75 | *) 76 | local return 77 | Object.New $__typeCreate_varType $__typeCreate_varName 78 | eval "$__typeCreate_varName=$return" 79 | ;; 80 | esac 81 | 82 | # __oo__objects+=( $__typeCreate_varName ) 83 | 84 | unset __typeCreate_varType 85 | unset __typeCreate_varValue 86 | fi 87 | 88 | if [[ "$command" != "declare" || "$__typeCreate_next" != "true" ]] 89 | then 90 | __typeCreate_normalCodeStarted+=1 91 | 92 | # Console::WriteStdErr "NOPASS ${commandWithArgs[*]}" 93 | # Console::WriteStdErr "normal code count ($__typeCreate_normalCodeStarted)" 94 | # Console::WriteStdErr -- 95 | else 96 | unset __typeCreate_next 97 | 98 | __typeCreate_normalCodeStarted=0 99 | __typeCreate_varName="$varName" 100 | __typeCreate_varValue="$varValue" 101 | __typeCreate_varType="$__capture_type" 102 | __typeCreate_arrLength="$__capture_arrLength" 103 | 104 | # Console::WriteStdErr "PASS ${commandWithArgs[*]}" 105 | # Console::WriteStdErr -- 106 | 107 | __typeCreate_paramNo+=1 108 | fi 109 | } 110 | 111 | Type::CaptureParams() { 112 | # Console::WriteStdErr "Capturing Type $_type" 113 | # Console::WriteStdErr -- 114 | 115 | __capture_type="$_type" 116 | } 117 | 118 | # NOTE: true; true; at the end is required to workaround an edge case where TRAP doesn't behave properly 119 | alias trapAssign='Type::CaptureParams; declare -i __typeCreate_normalCodeStarted=0; trap "declare -i __typeCreate_paramNo; Type.CreateVar \"\$BASH_COMMAND\" \"\$@\"; [[ \$__typeCreate_normalCodeStarted -ge 2 ]] && trap - DEBUG && unset __typeCreate_varType && unset __typeCreate_varName && unset __typeCreate_varValue && unset __typeCreate_paramNo" DEBUG; true; true; ' 120 | alias reference='_type=reference trapAssign declare -n' 121 | alias string='_type=string trapAssign declare' 122 | alias int='_type=integer trapAssign declare -i' 123 | alias array='_type=array trapAssign declare -a' 124 | alias dictionary='_type=dictionary trapAssign declare -A' 125 | 126 | alias TestObject='_type=TestObject trapAssign declare' 127 | 128 | declare -Ag __oo__garbageCollector 129 | 130 | 131 | # we don't need to define anything if using command_not_found 132 | # we only need to check what type that variable is! 133 | # and return whatever we need! 134 | # it also means we can PIPE to a variable/object 135 | # echo dupa | someArray.Add 136 | 137 | alias ~modifiesLocals="[[ \"\${FUNCNAME[2]}\" != \"command_not_found_handle\" ]] || subject=warn Log \"Method \$FUNCNAME modifies locals and needs to be run prefixed by '@'\"" 138 | 139 | writeln() ( # forking for local scope for $IFS 140 | local IFS=" " # needed for "$*" 141 | printf '%s\n' "$*" 142 | ) 143 | 144 | write() ( 145 | local IFS=" " 146 | printf %s "$*" 147 | ) 148 | 149 | writelne() ( 150 | local IFS=" " 151 | printf '%b\n' "$*" 152 | ) 153 | 154 | String.GetRandomAlphanumeric() { 155 | # http://stackoverflow.com/a/23837814/595157 156 | local chars=( {a..z} {A..Z} {0..9} ) 157 | local length=$1 158 | local ret= 159 | while ((length--)) 160 | do 161 | ret+=${chars[$((RANDOM%${#chars[@]}))]} 162 | done 163 | printf '%s\n' "$ret" 164 | } 165 | 166 | Object.New() { 167 | local objectUUID=$obj:$(String.GetRandomAlphanumeric 12) 168 | __oo__objectToType[$objectUUID]="$1" 169 | __oo__objectToName[$objectUUID]="$2" 170 | 171 | return=$objectUUID 172 | } 173 | 174 | Object.Invoke() { 175 | [string] objectUUID 176 | # remainingStack[] 177 | [string] stackElement 178 | [...rest] params 179 | 180 | if [[ -z ${__oo__objectToType[$objectUUID]+isSet} ]] 181 | then 182 | e="Object $objectUUID doesn't exist" throw "$stackElement" && return 0 183 | fi 184 | 185 | ${__oo__objectToType[$objectUUID]}.$stackElement "${params[@]}" 186 | } 187 | 188 | class:TestObject() { 189 | var name 190 | int age=30 191 | 192 | # if parent function starts with "class:" 193 | # capture and save 194 | } 195 | 196 | TestObject.__constructor__() { 197 | this.age=20 198 | 199 | } 200 | 201 | TestObject.Hello() { 202 | echo Hello! 203 | alias Opica="local hello_sub1=wtf; local hello_sub2=lol; " 204 | } 205 | 206 | Object.IsObject() { 207 | : 208 | } 209 | 210 | Object.GetType() { 211 | : 212 | } 213 | 214 | Reference.GetRealVariableName() { 215 | local realObject="$1" 216 | # local typeInfo="$(declare -p $realObject 2> /dev/null || declare -p | grep "^declare -[aAign\-]* $realObject\(=\|$\)" || true)" 217 | local typeInfo="$(declare -p $realObject 2> /dev/null || true)" 218 | 219 | if [[ -z "$typeInfo" ]] 220 | then 221 | DEBUG local extraInfo="$(declare -p | grep "^declare -[aAign\-]* $realObject\(=\|$\)" || true)" 222 | DEBUG subject="dereferrenceNoSuccess" Log "$extraInfo" 223 | echo "$realObject" 224 | return 0 225 | fi 226 | 227 | #DEBUG subject="dereferrence" Log "$realObject to $typeInfo" 228 | # dereferrence 229 | while [[ "$typeInfo" =~ "declare -n" ]] && [[ "$typeInfo" =~ \"([a-zA-Z0-9_]*)\" ]] 230 | do 231 | DEBUG subject="dereferrence" Log "$realObject to ${BASH_REMATCH[1]}" 232 | realObject=${BASH_REMATCH[1]} 233 | typeInfo="$(declare -p $realObject 2> /dev/null)" # || declare -p | grep "^declare -[aAign\-]* $realObject\(=\|$\)" 234 | done 235 | 236 | echo "$realObject" 237 | } 238 | 239 | Type::GetTypeOfVariable() { 240 | local typeInfo="$(declare -p $1 2> /dev/null || declare -p | grep "^declare -[aAign\-]* $1\(=\|$\)" || true)" 241 | 242 | if [[ -z "$typeInfo" ]] 243 | then 244 | echo undefined 245 | return 0 246 | fi 247 | 248 | if [[ "$typeInfo" == "declare -n"* ]] 249 | then 250 | echo reference 251 | elif [[ "$typeInfo" == "declare -a"* ]] 252 | then 253 | echo array 254 | elif [[ "$typeInfo" == "declare -A"* ]] 255 | then 256 | echo dictionary 257 | elif [[ "$typeInfo" == "declare -i"* ]] 258 | then 259 | echo integer 260 | # elif [[ "${!1}" == "$obj:"* ]] 261 | # then 262 | # echo "$(Object.GetType "${!realObject}")" 263 | else 264 | echo string 265 | fi 266 | } 267 | 268 | # insted of echo let's use $return 269 | # return="something" 270 | # return should be declared prior to entering the func 271 | 272 | ~returns() { 273 | [string] returnType 274 | # switch case array 275 | # check if $return is an array 276 | # etc... 277 | #: 278 | 279 | # initialize/reset return variable: 280 | case "$returnType" in 281 | 'array' | 'dictionary') return=() ;; 282 | 'string' | 'integer') return="";; 283 | *) ;; 284 | esac 285 | 286 | # DEBUG subject="returnsMatch" Log 287 | local realVar=$(Reference.GetRealVariableName return) 288 | local type=$(Type::GetTypeOfVariable $realVar) 289 | 290 | if [[ "$returnType" != "$type" ]] 291 | then 292 | e="Return type ($returnType) doesn't match with the actual type ($type)." throw 293 | fi 294 | } 295 | 296 | string.length() { 297 | return=${#this} 298 | } 299 | 300 | string.toUpper() { 301 | return="${this^^}" 302 | } 303 | 304 | array.length() { 305 | ~returns int 306 | return=${#this[@]} 307 | } 308 | 309 | string.sanitized() { 310 | local sanitized="${this//[^a-zA-Z0-9]/_}" 311 | return="${sanitized^^}" 312 | } 313 | 314 | string.toArray() { 315 | #[reference] array 316 | ~modifiesLocals 317 | 318 | local newLine=$'\n' 319 | local separationCharacter=$'\UFAFAF' 320 | local string="${this//"$newLine"/"$separationCharacter"}" 321 | local IFS=$separationCharacter 322 | local element 323 | for element in $string 324 | do 325 | return+=( "$element" ) 326 | done 327 | 328 | local newLines=${string//[^$separationCharacter]} 329 | local -i trailingNewLines=$(( ${#newLines} - ${#return[@]} + 1 )) 330 | while (( trailingNewLines-- )) 331 | do 332 | return+=( "" ) 333 | done 334 | } 335 | 336 | array.print() { 337 | local index 338 | for index in "${!this[@]}" 339 | do 340 | echo "$index: ${this[$index]}" 341 | done 342 | } 343 | 344 | # string.change() { 345 | # ## EXAMPLE 346 | # ~modifiesLocals 347 | # # [[ "${FUNCNAME[2]}" != "command_not_found_handle" ]] || s=warn Log "Method $FUNCNAME modifies locals and needs to be run prefixed by '@'." 348 | # this="$1" 349 | # DEBUG Log "change list: $*" 350 | # } 351 | 352 | string.match() { 353 | [string] regex 354 | [integer] capturingGroup=${bracketParams[0]} #bracketParams 355 | [string] returnMatch="${bracketParams[1]}" 356 | 357 | DEBUG subject="string.match" Log "string to match on: $this" 358 | 359 | array allMatches 360 | ~ allMatches~=this.matchGroups "$regex" "$returnMatch" 361 | 362 | return="${allMatches[$capturingGroup]}" 363 | } 364 | 365 | string.matchGroups() { 366 | ~returns array 367 | [string] regex 368 | # [reference] matchGroups 369 | [string] returnMatch="${bracketParams[0]}" 370 | 371 | DEBUG subject="matchGroups" Log "string to match on: $this" 372 | local -i matchNo=0 373 | local string="$this" 374 | while [[ "$string" =~ $regex ]] 375 | do 376 | DEBUG subject="regex" Log "match $matchNo: ${BASH_REMATCH[*]}" 377 | 378 | if [[ "$returnMatch" == "@" || $matchNo -eq "$returnMatch" ]] 379 | then 380 | return+=( "${BASH_REMATCH[@]}" ) 381 | [[ "$returnMatch" == "@" ]] || return 0 382 | fi 383 | # cut out the match so we may continue 384 | string="${string/"${BASH_REMATCH[0]}"}" # " 385 | matchNo+=1 386 | done 387 | } 388 | 389 | array.takeEvery() { 390 | ~returns array 391 | [integer] every 392 | [integer] startingIndex 393 | # [reference] outputArray 394 | 395 | local -i count=0 396 | 397 | local index 398 | for index in "${!this[@]}" 399 | do 400 | if [[ $index -eq $(( $every * $count + $startingIndex )) ]] 401 | then 402 | #echo "$index: ${this[$index]}" 403 | return+=( "${this[$index]}" ) 404 | count+=1 405 | fi 406 | done 407 | } 408 | 409 | array.last() { 410 | ~returns string 411 | 412 | local count="${#this[@]}" 413 | return="${this[($count-1)]}" 414 | } 415 | 416 | array.add() { 417 | [string] element 418 | 419 | this+=( "$element" ) 420 | } 421 | 422 | array.forEach() { 423 | [string] elementName 424 | [string] do 425 | 426 | # first dereferrence 427 | # local typeInfo="$(declare -p this)" 428 | # if [[ "$typeInfo" =~ "declare -n" ]] && [[ "$typeInfo" =~ \"([a-zA-Z0-9_]*)\" ]] 429 | # then 430 | # local realName=${BASH_REMATCH[1]} 431 | # fi 432 | 433 | local index 434 | for index in "${!this[@]}" 435 | do 436 | local $elementName="${this[$index]}" 437 | # local -n $elementName="$realName[$index]" 438 | # local -n $elementName="this[$index]" 439 | eval "$do" 440 | # unset -n $elementName 441 | done 442 | } 443 | 444 | Exception::CustomCommandHandler() { 445 | # best method for checking if variable is declared: http://unix.stackexchange.com/questions/56837/how-to-test-if-a-variable-is-defined-at-all-in-bash-prior-to-version-4-2-with-th 446 | if [[ ! "$1" =~ \. ]] && [[ -n ${!1+isSet} ]] && [[ -z "${*:2}" ]] 447 | then 448 | # check if an object UUID 449 | # else print var 450 | 451 | DEBUG subject="builtin" Log "Invoke builtin getter" 452 | # echo "var $1=${!1}" 453 | echo "${!1}" 454 | return 0 455 | fi 456 | 457 | local regex='(^|\.)([a-zA-Z0-9_]+)(({[^}]*})*)((\[[^]]*\])*)((\+=|-=|\*=|/=|==|\+\+|~=|:=|=|\+|/|\\|\*|~|:|-)(.*))*' 458 | 459 | local -a matches 460 | local -n return=matches; this="$1" bracketParams=@ string.matchGroups "$regex"; unset -n return 461 | 462 | if (( ${#matches[@]} == 0 )) 463 | then 464 | return 1 465 | fi 466 | 467 | local -a callStack 468 | local -a callStackParams 469 | local -a callStackLastParam 470 | local -a callStackBrackets 471 | local -a callStackLastBracket 472 | local callOperator="${matches[-2]}" 473 | local callValue="${matches[-1]}" 474 | 475 | #unset -n this 476 | local originalThisReference="$(Reference.GetRealVariableName this)" 477 | DEBUG [[ ${originalThisReference} == this ]] || subject="originalThisReference" Log $originalThisReference 478 | [[ ${originalThisReference} != this ]] || local originalThis="$this" 479 | 480 | local -n this="matches" 481 | local -n return=callStack; array.takeEvery 10 2; unset -n return 482 | local -n return=callStackParams; array.takeEvery 10 3; unset -n return 483 | local -n return=callStackLastParam; array.takeEvery 10 4; unset -n return 484 | local -n return=callStackBrackets; array.takeEvery 10 5; unset -n return 485 | local -n return=callStackLastBracket; array.takeEvery 10 6; unset -n return 486 | unset -n this 487 | 488 | DEBUG local -n this="callStack" 489 | DEBUG subject="complex" Log callStack: 490 | DEBUG array.print 491 | DEBUG unset -n this 492 | 493 | DEBUG local -n this="callStackParams" 494 | DEBUG subject="complex" Log callStackParams: 495 | DEBUG array.print 496 | DEBUG unset -n this 497 | 498 | DEBUG local -n this="callStackBrackets" 499 | DEBUG subject="complex" Log callStackBrackets: 500 | DEBUG array.print 501 | DEBUG unset -n this 502 | 503 | # restore the this reference/value: 504 | [[ ${originalThisReference} == this ]] || local -n this="$originalThisReference" 505 | [[ -z ${originalThis} ]] || local this="$originalThis" 506 | 507 | #DEBUG subject="complex" Log this: ${this[@]} 508 | DEBUG subject="complex" Log callOperator: $callOperator 509 | DEBUG subject="complex" Log callValue: $callValue 510 | 511 | local -i callLength=$((${#callStack[@]} - 1)) 512 | local -i callHead=1 513 | 514 | DEBUG subject="complex" Log callLength: $callLength 515 | 516 | local rootObject="${callStack[0]}" 517 | 518 | ## TODO: also check if rootObject is a function - the call it if it is 519 | ## i.e. we allow calling myFunction[param][param]{param}{param} 520 | 521 | # check for existance of $callStack[0] and whether it is an object 522 | # if is resolvable immediately 523 | local rootObjectResolvable=$rootObject[@] 524 | if [[ -n ${!rootObjectResolvable+isSet} || "$(eval "echo \" \${!$rootObject*} \"")" == *" $rootObject "* ]] 525 | then 526 | local realVar=$(Reference.GetRealVariableName $rootObject) 527 | local type=$(Type::GetTypeOfVariable $realVar) 528 | DEBUG subject="variable" Log "Variable \$$realVar of type: $type" 529 | 530 | if [[ $type == array || $type == dictionary ]] && [[ ! -z "${callStackBrackets[0]}" ]] 531 | then 532 | type=string 533 | rootObject="$rootObject${callStackBrackets[0]}" 534 | fi 535 | 536 | if [[ $type == string && "${!rootObject}" == "$obj:"* ]] 537 | then 538 | # local isObject=true 539 | # pass the rest of the call stack to the object invoker 540 | Object.Invoke "${!rootObject}" "${@:2}" 541 | return 0 542 | fi 543 | 544 | if (( $callLength == 0 )) && [[ -n "$callOperator" ]] #&& [[ $isObject != true ]] 545 | then 546 | DEBUG subject="complex" Log "CallStack length is 0, using the operator." 547 | case "$callOperator" in 548 | '~=') 549 | if [[ "${!callValue}" == "$obj:"* && -z "${*:2}" ]] 550 | then 551 | # TODO: rather than eval, use a local -n target="$rootObject" 552 | eval "$rootObject=\"\${!callValue}\"" 553 | elif [[ -n "${callValue}" ]] 554 | then 555 | #unset -n this 556 | local -n returnVariable="$rootObject" 557 | __oo__useReturnVariable=true ~ "$callValue" "${@:2}" 558 | # eval "@ $callValue \"\${@:2}\"" 559 | unset -n returnVariable 560 | fi 561 | DEBUG subject="complexAssignment" Log "$rootObject=${!rootObject}" 562 | ;; 563 | # TODO: other operators 564 | # $type.$callOperator "$callValue" "${@:2}" 565 | esac 566 | else 567 | # if [[ $isObject != true ]] 568 | # then 569 | local value="" 570 | 571 | case "$type" in 572 | "array"|"dictionary") local -n this="$rootObject" ;; 573 | "integer"|"string") value="${!rootObject}" ;; 574 | esac 575 | # fi 576 | 577 | # make it possible to also call functions through the use of first parameter: 578 | # ex. ~ coolStuff add $ref:something 579 | # so, if 'coolStuff' is a VAR, then depending on it's type 580 | # runs it runs TYPE.add $ref:something 581 | if (( $callLength == 0 )) 582 | then 583 | callLength+=1 584 | callStack[1]="$2" 585 | shift 586 | fi 587 | 588 | while ((callLength--)) 589 | do 590 | DEBUG subject="complex" Log calling: $type.${callStack[$callHead]} 591 | # does the method exist? 592 | if ! Function::Exists $type.${callStack[$callHead]} 593 | then 594 | e="Method: $type.${callStack[$callHead]} does not exist." skipBacktraceCount=4 throw ${callStack[$callHead]} 595 | fi 596 | 597 | local -a mustacheParams=() 598 | local mustacheParamsRegex='[^{}]+' 599 | local -n return=mustacheParams; this="${callStackParams[$callHead]}" string.matchGroups "$mustacheParamsRegex" @; unset -n return 600 | 601 | local -a bracketParams=() 602 | local bracketRegex='[^][]+' 603 | local -n return=bracketParams; this="${callStackBrackets[$callHead]}" string.matchGroups "$bracketRegex" @; unset -n return 604 | 605 | DEBUG subject="complex" Log bracketParams: ${bracketParams[*]} #${callStackParams[$callHead]} 606 | DEBUG subject="complex" Log mustacheParams: ${mustacheParams[*]} #${callStackBrackets[$callHead]} 607 | DEBUG subject="complex" Log -- 608 | 609 | #local originalThis="$(Reference.GetRealVariableName this)" 610 | 611 | 612 | if (( $callHead == 1 )) && ! [[ "$type" == "string" || "$type" == "integer" ]] 613 | then 614 | DEBUG subject="complexA" Log "Executing: $type.${callStack[$callHead]} ${*:2}" 615 | $type.${callStack[$callHead]} "${mustacheParams[@]}" "${@:2}" 616 | else 617 | DEBUG subject="complexB" Log "Executing: this=$value $type.${callStack[$callHead]} ${mustacheParams[*]} ${*:2}" 618 | 619 | local retVal 620 | if [[ -n ${__oo__useReturnVariable+isSet} ]] 621 | then 622 | local -n return=returnVariable 623 | else 624 | local -n return=retVal 625 | fi 626 | 627 | this="$value" $type.${callStack[$callHead]} "${mustacheParams[@]}" "${@:2}" 628 | unset -n return 629 | value="$retVal" 630 | fi 631 | 632 | callHead+=1 633 | done 634 | 635 | # don't polute with a "this" reference 636 | unset -n this 637 | 638 | if [[ -n ${value} ]] 639 | then 640 | echo "${value}" 641 | fi 642 | fi 643 | 644 | #DEBUG subject="complex" Log "Invoke type: $type, object: $rootObject, ${child:+child: $child, }${bracketOperator:+"$bracketOperator: $bracketParams, "}operator: $operator${parameter:+, param $parameter}" 645 | 646 | #$type${child:+".$child"} "${@:2}" 647 | else 648 | #eval "echo \" \${!$rootObject*} \"" 649 | DEBUG subject=Error Log ${rootObjectResolvable} is not resolvable: ${!rootObjectResolvable} 650 | return 1 651 | fi 652 | # if callOperator then for sure an object - check if exists, else error 653 | 654 | } 655 | 656 | # testFunc() { 657 | # local testing="onething.object['abc def'].length[123].something[2]{another}" 658 | #local testing="something.somethingElse{var1,var2,var3}[a].extensive{param1 : + can be =\"anything \"YO # -yo space}{another}[0][2]=LALALA} and what if=we have.an equals.test[immi]{lol}?" 659 | # local something="haha haha Yo!" 660 | # local testing="something.sanitized{}.length{}" 661 | # local -a dupa 662 | # dupa~=something.toArray -- use dupa as output parameter/ret-val 663 | #local regex='(?:^|\.)([a-zA-Z0-9_]+)((?:{.*?})*)((?:\[.*?\])*)(?:(=|\+|/|\\|\*|~|:|-|\+=|-=|\*=|/=|==)(.*))*' 664 | 665 | # testFunc 666 | 667 | # Object.Hello 668 | 669 | # myFunction() { 670 | # array something # creates object "something" && __oo__garbageCollector+=( something ) local -a something 671 | # array another 672 | # something.add "blabla" 673 | # something.add $ref:something 674 | # # for member in "${something[@]}" 675 | # Array.Merge $ref:something $ref:another 676 | # } 677 | 678 | # myFunction 679 | 680 | 681 | 682 | 683 | testFunc2() { 684 | string something="haha haha Yo!" 685 | string another="hey! works!" 686 | 687 | array coolStuff=(a bobo) 688 | 689 | echo $something 690 | something.length 691 | 692 | coolStuff.print 693 | ~ coolStuff.add{$ref:something} 694 | coolStuff.print 695 | 696 | # Object mikrofalowka 697 | #=> echo $mikrofalowka result unreachable 698 | 699 | #=> $mikrofalowka result unreachable 700 | # mikrofalowka Hello 701 | 702 | # Opica 703 | # echo $hello_sub1 704 | 705 | # ~ coolStuff.add{$ref:something} 706 | # ~ coolStuff.add{$ref:another} 707 | # coolStuff.print 708 | 709 | # string lol 710 | # lol="$(coolStuff[1].toUpper.match{'WOR.*'}[0][0])" 711 | # lol 712 | 713 | # coolStuff 714 | # ~ coolStuff add something 715 | # coolStuff print 716 | #coolStuff[1] 717 | # ~ something~=something toUpper 718 | # something 719 | 720 | # for creating objects we can use 721 | # SomeType objectName 722 | 723 | # always save a full stack 724 | # for each created object at moment of creation 725 | 726 | # something 727 | # another 728 | # something.sanitized{}.length{}.length{} 729 | # something.sanitized{} 730 | # ~ something~="another.sanitized{}" 731 | # something 732 | # local -a someArray=() 733 | # ~ someArray~=something.match{'WOR.*'}[0][0] 734 | # someArray.print 735 | 736 | # string stringArray="$(echo -e "ba\nok\nmimi\nlol")" 737 | # array fromStringArray 738 | 739 | # ~ stringArray~=stringArray.toUpper 740 | # ~ fromStringArray~=stringArray.toArray{} 741 | # fromStringArray.print 742 | } 743 | 744 | testFunc2 745 | 746 | # new method for a type system: 747 | # 748 | # command_not_found_handle() { 749 | # echo hi, "$*" "${!2}" 750 | # } 751 | # declare jasia="haha" 752 | # dupa jasia 753 | 754 | # readPipe() { 755 | # read it 756 | # read 757 | # } 758 | 759 | # shopt -s lastpipe 760 | # echo "hello world" | readPipe 761 | # echo $it 762 | -------------------------------------------------------------------------------- /example/v1/seamless2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # It will make more sense to not use . to invoke methods 4 | # how about we use first param as method? 5 | # 6 | # array something 7 | # ~ something push 'abc' 8 | # something print 9 | # 10 | # string koko='123ha' 11 | # koko=$(koko sanitize) 12 | # 13 | # koko toUpper 14 | # equivalent of: string.toUpper $ref:koko 15 | # equivalent of (?): string.toUpper < <(koko) 16 | # koko=$(koko | string.toUpper | string.normalize) 17 | # 18 | # koko # returns $ref:koko 19 | # typeof koko # returns 'string' 20 | # 21 | # there could be intelligent assignment, because: 22 | # a pure invocation of an instance (variable) could set the _TYPE=string 23 | # which could be used for matching later on in the pipes 24 | # ...as long as that's not a problem with subshells... 25 | # plus, we even don't need to use values passed in from the pipe in the methods 26 | # since the invocation can set a RETURNS=... variable instead 27 | # 28 | # so theoretically we could: 29 | # 30 | # string kokoNormalized=$(koko |~ toUpper |~ normalize) 31 | # 32 | # tildas would be necessary (?) since the invocations would come from command_not_found... 33 | # 34 | # koko toUpper | string.sanitize 35 | # string.toUpper $koko 36 | # 37 | # 38 | # pass around arrays as stringified definitions 39 | # so that we always work on a new copy 40 | # in fact, the return (echo) value should be the assignment, like: 41 | # (a b c "d e" f) 42 | # or 43 | # ([ab]="12" [cd]="34") 44 | # so then we can simply: 45 | # someArr=$(firstArr | forEach toUpper | forEach toLower) 46 | 47 | # string.length() { 48 | # read this 49 | # return=${#this} 50 | # echo "$return" 51 | # } 52 | 53 | # string.toUpper() { 54 | # read this 55 | # return="${this^^}" 56 | # echo "$return" 57 | # } 58 | 59 | # something(){ 60 | # eval echo \$$FUNCNAME 61 | # } 62 | 63 | # something 64 | 65 | Variable::ExportDeclarationAndTypeToVariables() { 66 | local variableName="$1" 67 | local targetVariable="$2" 68 | 69 | local declaration 70 | local regexArray="declare -([a-zA-Z-]+) $variableName='(.*)'" 71 | local regex="declare -([a-zA-Z-]+) $variableName=\"(.*)\"" 72 | local definition=$(declare -p $variableName) 73 | 74 | local escaped="'\\\'" 75 | 76 | if [[ "$definition" =~ $regexArray ]] 77 | then 78 | declaration="${BASH_REMATCH[2]//$escaped/}" 79 | elif [[ "$definition" =~ $regex ]] 80 | then 81 | declaration="${BASH_REMATCH[2]//$escaped/}" 82 | fi 83 | 84 | eval $targetVariable=\$declaration 85 | eval ${targetVariable}_type=\${BASH_REMATCH[1]} 86 | # __declaration_type=${BASH_REMATCH[1]} 87 | } 88 | 89 | Variable::PrintDeclaration() { 90 | local __declaration 91 | Variable::ExportDeclarationAndTypeToVariables "$1" __declaration 92 | echo "$__declaration" 93 | } 94 | 95 | 96 | # shopt -s lastpipe # not needed 97 | shopt -s expand_aliases 98 | declare __declaration_type 99 | 100 | Console::WriteStdErr() { 101 | # http://stackoverflow.com/questions/2990414/echo-that-outputs-to-stderr 102 | cat <<< "$*" 1>&2 103 | return 0 104 | } 105 | 106 | Pipe::Capture() { 107 | read -r -d '' $1 108 | } 109 | 110 | Pipe::CaptureFaithful() { 111 | IFS= read -r -d '' $1 112 | } 113 | 114 | ## note: declaration needs to be trimmed, 115 | ## since bash adds an enter at the end, hence %? 116 | alias @resolveThis=" 117 | local __declaration; 118 | if [ -z \${this+x} ]; 119 | then 120 | Pipe::Capture __declaration; 121 | else 122 | Variable::ExportDeclarationAndTypeToVariables \$this __declaration; 123 | unset this; 124 | fi; 125 | local -\${__declaration_type:--} this=\${__declaration};" 126 | 127 | alias @return='Variable::PrintDeclaration' 128 | alias @get='Variable::PrintDeclaration' 129 | 130 | map.set() { 131 | @resolveThis 132 | 133 | this["$1"]="$2" 134 | 135 | @return this 136 | } 137 | 138 | map.delete() { 139 | @resolveThis 140 | 141 | unset this["$1"] 142 | 143 | @return this 144 | } 145 | 146 | map.get() { 147 | @resolveThis 148 | 149 | local value="${this[$1]}" 150 | @return value 151 | } 152 | 153 | 154 | declare -A oldarr=([' escape "1" me ']="me \" too" ["simple"]="123" ["simple'2"]="99'9") 155 | 156 | echo 157 | echo copying arrays: 158 | # Variable::PrintDeclaration oldarr 159 | declare -A cpyarr=$(Variable::PrintDeclaration oldarr) 160 | declare -p oldarr 161 | declare -p cpyarr 162 | echo 163 | 164 | 165 | echo 166 | echo nesting arrays: 167 | declare -A nstarr=([first]=123 [nested]=$(Variable::PrintDeclaration oldarr)) 168 | # Variable::PrintDeclaration nstarr 169 | declare -A nstarr=${nstarr[nested]} 170 | declare -p nstarr 171 | echo 172 | # Variable::PrintDeclaration outOfArr 173 | 174 | 175 | # declare -A addRemArr=$(Variable::PrintDeclaration oldarr | 176 | # map.set "info 123 \"hey" "works yo" | 177 | # map.delete "simple") 178 | 179 | declare -A simple=() 180 | 181 | # declare -A addRemArr=$(Variable::PrintDeclaration simple | 182 | # declare -A withEnters=$() 183 | 184 | 185 | 186 | echo 187 | echo copy empty: 188 | Variable::PrintDeclaration simple 189 | echo 190 | 191 | echo 192 | echo simple addition: 193 | Variable::PrintDeclaration simple | 194 | map.set "simple" "simple" 195 | echo 196 | 197 | echo 198 | echo with enters: 199 | Variable::PrintDeclaration simple | 200 | map.set "enter" "$(printf "a\nb\nc")" 201 | echo 202 | 203 | echo 204 | echo adding after enters: 205 | Variable::PrintDeclaration simple | 206 | map.set "enter" "$(printf "a\nb\nc")" | 207 | map.set "space" "1 space 2" | 208 | map.set "another" "$(printf "q\nb\nr")" 209 | echo 210 | 211 | echo 212 | echo deleting: 213 | Variable::PrintDeclaration simple | 214 | map.set "test" "1234" | 215 | map.set "enter" "$(printf "a\nb\nc")" | 216 | map.delete "enter" 217 | echo 218 | 219 | echo 220 | echo adding and getting: 221 | Variable::PrintDeclaration oldarr | 222 | map.set "sober" "works yo" | 223 | map.get "sober" 224 | echo 225 | 226 | something=$(this=oldarr map.get "simple") 227 | echo $something 228 | 229 | # someMap | .set one 'val' | .set two 'val' | .get one | .toUpper | .sanitize | .parseInt 230 | 231 | # obj=$(new Object) 232 | 233 | # arr=$(Array.FromFile open.txt $delimiter) 234 | # arr=$(Array.FromDir ./) 235 | # arr | .forEach element 'echo "$element"' 236 | # arr | .filter element "element | .startsWith 'ab'" 237 | 238 | # arr filter ( element "element startsWith ( 'ab' )" ) 239 | # without ( 'rambo' ) 240 | 241 | # object updateProperty ( 1 2 ) 242 | # human .leftLeg assign 243 | 244 | # when invoking via the autogenerated function 245 | # arr forEach element 'echo "$element"' 246 | 247 | # autogenerated function should be generic, i.e. so that it will always find out 248 | # the current type of the underlying var, since it can vary based on scope 249 | 250 | # Object someTest=$(new Test 'Primavera') 251 | # someTest=$(someTest | .startTimer) 252 | # this=someTest Test.startTimer 253 | # Test.startTimer $ref:someTest 254 | 255 | # someTest .startTimer 256 | # someTest .timer .start 257 | 258 | # for objects we could capture the name during declaration (special declaration trap) 259 | # and then we create a function with the same name to handle execution and declaration printing 260 | # perhaps instead of declaration printing we set this= prior to invocation 261 | # 262 | # for each method of each type we create universal functions .function that 263 | # runs / selects the right vesion to run based on the type passed in 264 | 265 | # this way we can have .contains for both arrays and strings and custom objects 266 | 267 | # properties of custom objects should always be stored 'serialized', i.e. as declarations 268 | # this way we can nest them indefinitely -------------------------------------------------------------------------------- /example/v1/seamless3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #__INTERNAL_LOGGING__=true 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 5 | 6 | namespace seamless 7 | 8 | Log::AddOutput seamless CUSTOM 9 | Log::AddOutput error ERROR 10 | 11 | import lib/system/oo 12 | 13 | #Log::AddOutput oo/parameters-executing CUSTOM 14 | 15 | 16 | # ------------------------ # 17 | 18 | 19 | 20 | # ------------------------ # 21 | 22 | 23 | 24 | # there could be a variable "modifiedThis", 25 | # which is set to "Variable::PrintDeclaration this" 26 | # so we kind of have two returns, one real one, 27 | # and one for the internal change 28 | # 29 | # this way: 30 | # someMap set a 30 31 | # 32 | # would actually update someMap 33 | # and manual rewriting: someMap=$(someMap set a 30) 34 | # would not be required 35 | 36 | # declare __integer_fingerprint=2D6A822E36884C70843578D37E6773C4 37 | # declare __integer_array_fingerprint=2884B8F8E6774006AD0CA1BD4518E093 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ################ 50 | 51 | class:Human() { 52 | public string firstName 53 | public string lastName = 'Lastnameovitch' 54 | public array children 55 | public Human child 56 | 57 | Human.test() { 58 | @resolve:this 59 | @return:value "children: $(this children)" 60 | } 61 | 62 | Human.shout() { 63 | @resolve:this 64 | 65 | this firstName = "$(this firstName) shout!" 66 | this children push 'shout' 67 | local a=$(this test) 68 | 69 | @return a 70 | } 71 | } 72 | 73 | Type::Initialize Human 74 | 75 | # class:Human 76 | # alias Human="_type=Human trapAssign declare -A" 77 | 78 | # DONE: special overriden 'echo' and 'printf' function in methods that saves to a variable 79 | 80 | function test1() { 81 | string justDoIt="yes!" 82 | map ramda=([test]=ho [great]=ok [test]="\$result ''ha' ha" [enter]=$(printf "\na\nb\n")) 83 | 84 | # monad=true ramda set [ "one" "yep" ] 85 | ramda set "one" "yep" 86 | ramda set 'two' "oki dokies" 87 | ramda delete enter 88 | ramda delete test 89 | ramda 90 | ramda get 'one' 91 | ramda : { get 'one' } { toUpper } 92 | 93 | # ramda : get [ 'one' ] | string.toUpper 94 | # ramda { get 'one' } { toUpper } 95 | 96 | ramda set 'one' "$(ramda '{' get 'one' } '{' toUpper })" 97 | ramda 98 | 99 | map polio=$(ramda) 100 | 101 | map kwiko=$(polio | monad=true map.set "kwiko" "liko") 102 | kwiko 103 | map kwiko=$(polio | monad=true map.set "kwiko" "kombo") 104 | kwiko 105 | 106 | justDoIt toUpper 107 | } 108 | 109 | # test1 110 | 111 | function test2() { 112 | array hovno 113 | hovno push one 114 | hovno push two 115 | hovno 116 | } 117 | 118 | # test2 119 | 120 | function test3() { 121 | map obj=([__object_type]=Human [firstName]=Bazyli) 122 | declare -p obj 123 | obj firstName 124 | } 125 | 126 | # test3 127 | 128 | function test4() { 129 | Human obj 130 | obj firstName = Ivon 131 | declare -p obj 132 | obj firstName 133 | } 134 | 135 | # test4 136 | 137 | function test5() { 138 | Human obj 139 | obj children push ' "a" b c ' 140 | obj children push "123 $(printf "\ntest")" 141 | # declare -p obj 142 | obj children 143 | } 144 | 145 | # test5 146 | 147 | function test6() { 148 | Human obj 149 | obj child firstName = "Ivon \" $(printf "\ntest") 'Ivonova'" 150 | obj child firstName 151 | # declare -p obj 152 | } 153 | 154 | # test6 155 | 156 | function test7() { 157 | Human obj 158 | obj child child child children push ' "a" b c ' 159 | 160 | # obj { child child child children push 'abc' } { get 'abc' } { toUpper } 161 | 162 | test "$(obj child child child children)" == '([0]=" \"a\" b c ")' 163 | declare -p obj 164 | } 165 | 166 | # test7 167 | 168 | function test8() { 169 | Human obj 170 | 171 | # obj firstName = [ Bazyli ] 172 | obj : { firstName = Bazyli } { toUpper } 173 | # obj firstName = Bazyli 174 | 175 | obj shout 176 | obj shout 177 | 178 | # declare -p obj 179 | } 180 | 181 | # test8 182 | 183 | function test9() { 184 | array hovno 185 | hovno : { push one } 186 | hovno push two 187 | hovno 188 | } 189 | 190 | # test9 191 | 192 | function test10() { 193 | Human obj 194 | 195 | obj child child firstName = SuperBazyli 196 | local apostrophe="'" 197 | obj child child child child child child firstName = 'Bazyli " \\ '$apostrophe' " Brzoska' 198 | 199 | obj child child child child child child firstName 200 | obj child child child child child child 201 | 202 | # obj 203 | 204 | # declare -p obj 205 | # obj 206 | } 207 | 208 | # test10 209 | 210 | 211 | class:PropertiesTest() { 212 | # http://askubuntu.com/questions/366103/saving-more-corsor-positions-with-tput-in-bash-terminal 213 | 214 | private integer x 215 | 216 | PropertiesTest.set() { 217 | @resolve:this 218 | [integer] value 219 | 220 | echo -en "\E[6n" 221 | read -sdR CURPOS 222 | CURPOS=${CURPOS#*[} 223 | 224 | this x = $value 225 | 226 | @return 227 | } 228 | 229 | # PropertiesTest.capture() { 230 | # @resolve:this 231 | 232 | 233 | # this x 234 | 235 | # @return 236 | # } 237 | 238 | PropertiesTest.get() { 239 | @resolve:this 240 | 241 | this x 242 | 243 | @return 244 | } 245 | } 246 | 247 | Type::Initialize PropertiesTest 248 | 249 | 250 | function testProperties() { 251 | PropertiesTest obj 252 | 253 | # obj set 123 254 | # obj capture 255 | obj get 256 | } 257 | 258 | # testProperties 259 | 260 | ## TODO: parametric versions of string/integer/array functions 261 | ## they could either take the variable name as param or [string[]] 262 | 263 | ## obj firstname = Bazyli 264 | ## arr @ push Bazyli 265 | ## obj @ firstname = Bazyli 266 | 267 | ########################### 268 | 269 | function testCursor() { 270 | UI.Cursor cursor 271 | 272 | cursor capture 273 | 274 | echo "lol- x: $(cursor x) | y: $(cursor y)" 275 | echo haha 276 | 277 | sleep 1 278 | 279 | cursor restore 2 280 | 281 | echo lila 282 | echo 283 | } 284 | 285 | # testCursor 286 | 287 | boolean.__getter__() { 288 | : ## TODO implement getters (they're executed instead of @get if executed directly) 289 | } 290 | 291 | ## TODO: save UUID prefix for numbers (or maybe not!) 292 | 293 | 294 | 295 | ## TEST LIB 296 | 297 | testtest() { 298 | Test newGroup "Objects" 299 | 300 | it 'should work' 301 | try 302 | fail 303 | expectPass 304 | 305 | Test displaySummary 306 | } 307 | 308 | testtest 309 | 310 | # local -A somehuman=$(new Human) 311 | # new Human 312 | 313 | -------------------------------------------------------------------------------- /example/v1/seamless4.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #__INTERNAL_LOGGING__=true 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 5 | 6 | #namespace seamless 7 | 8 | Log::AddOutput oo/type CUSTOM 9 | Log::AddOutput error ERROR 10 | 11 | import lib/system/oo 12 | import lib/type/util/test 13 | 14 | ## TODO: import github:niieani/whatever/lalala 15 | 16 | class:Human() { 17 | public string firstName 18 | public string lastName = 'Lastnameovitch' 19 | public array children 20 | public Human child 21 | public boolean hasSomething 22 | private string privTest = "someVal" 23 | 24 | Human.test() { 25 | @resolve:this 26 | @return:value "children: $(this children)" 27 | } 28 | 29 | Human.shout() { 30 | @resolve:this 31 | 32 | this firstName = "$(this firstName) shout!" 33 | this children push 'shout' 34 | local a=$(this test) 35 | 36 | @return a 37 | } 38 | 39 | Human.accessPriv() { 40 | @resolve:this 41 | this privTest = "$(this privTest) - changed" 42 | @return:value $(this privTest) 43 | } 44 | 45 | } 46 | 47 | Type::Initialize Human 48 | #Type::Initialize Human static 49 | 50 | function testStatic() { 51 | Human lastName 52 | } 53 | 54 | #testStatic 55 | 56 | function test2() { 57 | array hovno 58 | hovno push one 59 | hovno push two 60 | hovno 61 | } 62 | 63 | #test2 64 | 65 | function testParamPassing() { 66 | [string] first 67 | [string] second 68 | [integer] number 69 | 70 | first toUpper 71 | second 72 | number 73 | } 74 | 75 | #testParamPassing 'one' 'two' 99 76 | 77 | function testBoolean() { 78 | boolean empty 79 | boolean presetTrue=true 80 | boolean presetFalse=false 81 | boolean presetUnrecognized=blabla 82 | 83 | echo bool default: $empty 84 | echo bool true: $presetTrue 85 | echo bool false: $presetFalse 86 | echo bool blabla: $presetUnrecognized 87 | 88 | empty = true 89 | echo bool default: $empty 90 | 91 | empty 92 | } 93 | 94 | #testBoolean 95 | 96 | 97 | function testBooleanInClass() { 98 | Human guy 99 | guy 100 | guy hasSomething toString 101 | guy hasSomething = true 102 | guy hasSomething toString 103 | # guy 104 | } 105 | 106 | #testBooleanInClass 107 | 108 | function testPassingAsParameterSimple() { 109 | [string] str 110 | declare -p str 111 | } 112 | 113 | #testPassingAsParameterSimple 'hello!' 114 | 115 | function testPassingAsParameter() { 116 | [map] someMap 117 | [string] str 118 | [boolean] bool=true 119 | [Human] theHuman 120 | # @required [string] last 121 | 122 | declare -p someMap 123 | declare -p str 124 | declare -p bool 125 | declare -p theHuman 126 | } 127 | 128 | function testPassingAsParameterCall() { 129 | declare -A aMap=( [hoho]="yes m'aam" ) 130 | declare -p aMap 131 | 132 | Human someHuman 133 | # declare -p someHuman 134 | # declare -f someHuman || true 135 | testPassingAsParameter "$(@get aMap)" 'string' false "$(someHuman)" 136 | 137 | string after # GC 138 | } 139 | 140 | testPassingAsParameterCall 141 | 142 | function testArrayMethods() { 143 | array someArr=( 1 2 three 4 ) 144 | 145 | someArr push '5' 'six' 146 | someArr forEach 'echo yep-$(item)' 147 | someArr map 'echo $($var:item toUpper)' 148 | 149 | someArr : { map 'echo $($var:item toUpper)' } { forEach 'echo yep-$($var:item)' } 150 | 151 | someArr : \ 152 | { map 'echo $($var:item toUpper)' } \ 153 | { forEach 'echo yep-$(item)' } 154 | } 155 | 156 | #testArrayMethods 157 | 158 | function testPrivate() { 159 | Human yeah 160 | 161 | yeah lastName 162 | yeah accessPriv 163 | 164 | try { 165 | yeah privTest = yo 166 | } 167 | catch { 168 | echo private - OK 169 | } 170 | try { 171 | yeah privTest 172 | } 173 | catch { 174 | echo private - OK 175 | } 176 | } 177 | 178 | #testPrivate 179 | -------------------------------------------------------------------------------- /example/v1/serialization.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import lib/type-core 6 | import lib/types/base 7 | import lib/types/ui 8 | import lib/types/util/test 9 | 10 | class:NestedTestObject() { 11 | extends Object 12 | 13 | public String aNestedString = "nestedString" 14 | public Array aNestedArray 15 | private String _nonSerialized = "do not serialize me either" 16 | 17 | NestedTestObject::Feature() { 18 | $this._nonSerialized 19 | $this._nonSerialized = "new value" 20 | } 21 | } 22 | 23 | Type.Load 24 | 25 | class:TestObject() { 26 | extends Object 27 | 28 | public Number aNumber = 512 29 | public String aString = "some string here" 30 | public NestedTestObject aNestedObject 31 | private String _nonSerialized = "do not serialize me" 32 | 33 | TestObject::__constructor__() { 34 | $this.aNestedObject.aNestedArray.Add "first element" 35 | $this.aNestedObject.aNestedArray.Add "second element" 36 | } 37 | 38 | TestObject::Feature() { 39 | $this._nonSerialized 40 | $this._nonSerialized = "new value" 41 | } 42 | } 43 | 44 | Type.Load 45 | 46 | 47 | ## TESTS ## 48 | 49 | ( 50 | Test.NewGroup "Serialization" 51 | 52 | ## prepare object for unit tests: 53 | TestObject object 54 | 55 | it 'should serialize an object' 56 | try 57 | object.Serialize | jq . 58 | expectOutputPass 59 | 60 | Test.DisplaySummary 61 | 62 | 63 | Test.NewGroup "Type System" 64 | 65 | it 'should not allow accessing private properties' 66 | try 67 | object._nonSerialized 68 | expectFail 69 | 70 | it 'should not allow accessing private properties of child objects' 71 | try 72 | object.aNestedObject._nonSerialized 73 | expectFail 74 | 75 | it 'should allow accessing of private properties from functions within the object' 76 | try 77 | object.Feature 78 | object.Feature 79 | object.aNestedObject.Feature 80 | object.aNestedObject.Feature 81 | expectOutputPass 82 | 83 | Test.DisplaySummary 84 | ) -------------------------------------------------------------------------------- /example/v1/steffen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | NO_UNICODE=true source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 5 | 6 | namespace MyApp 7 | Log::AddOutput MyApp CUSTOM 8 | 9 | subject=WARN Log "I am a warning" 10 | subject=STEFFEN Log "I am a Steffen :-)" -------------------------------------------------------------------------------- /example/v1/tmp/assigning-local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib/oo-bootstrap.sh" 5 | 6 | import lib/types/base 7 | import lib/types/ui 8 | import lib/types/util/test 9 | 10 | set -h 11 | #set -k 12 | alias kapusta="echo I alias" 13 | kapusta 14 | 15 | ignore() { printf ""; } 16 | 17 | #trap="local argument+=( \\\"\\\$_\\\" ); printf \"[\\\$argument]\"; trapCount+=1; echo trap [\\\$trapCount / \\\$((\\\$trapCount / 6 + 1))] last " 18 | trap="argument+=( \\\"\\\$_\\\" ); trapCount+=1; echo trap [\\\$trapCount]; ignore last" 19 | alias :="declare -a argument; declare -i trapCount=0; trap \"$trap\" DEBUG; eval" 20 | #alias :="declare -i trapCount=0; declare -i paramCount; paramCount+=1; trap \"$trap\" DEBUG; eval" 21 | 22 | checkFlow() 23 | { 24 | # FLOW: DEBUG last name1 argument trapCount=0 DEBUG last name2 argument trapCount=0 DEBUG last name3 test 25 | # if flow is disrupted 26 | # [ DEBUG last $NAME argument trapCount=0 ] 27 | # 4th element should be argument 28 | # if it's not, we can return true 29 | # and then we disrupt 30 | 31 | # 1st. measure distance between DEBUG and DEBUG, if it doesn't repeat, return FALSE 32 | # 2nd. 33 | } 34 | 35 | de() { 36 | # set -h 37 | # set -k 38 | #alias @kapusta2="local" 39 | # alias kapusta23="echo I alias2" 40 | # kapusta23 after 41 | ## echo $after 42 | # 43 | # @ @one nazwa 44 | # # paramCount = 1 45 | # 46 | # # trapCount = 1 47 | # @ [string] cos 48 | # # paramCount = 2 49 | # 50 | # # trapCount = 2 51 | # normal 52 | # # trapCount = 3 -- different, stop trap and release 53 | # blabla 54 | 55 | # [string] test 56 | # [string] test2 57 | # [string] test3 58 | : echo name1 59 | : echo name2 60 | : echo name3 61 | 62 | echo test 63 | echo ${argument[@]} 64 | } 65 | de test1 test2 test3 66 | 67 | # aliases only work when defined before a function was entered 68 | # the only exception is $() shell, but locals die inside of it 69 | 70 | #de() { 71 | # trap "echo $@" DEBUG 72 | # alias @kapusta2="local" 73 | # alias @kapusta23="echo I alias2" 74 | # eval @kapusta2 after=test 75 | # echo $after 76 | #} 77 | #de ata 78 | # 79 | #@NumberAssign(){ 80 | # local varName="$1" 81 | # shift 82 | # local allParams="$@" 83 | # $varName="${allParams[0]}" 84 | # (count++) 85 | #} 86 | #count=0 87 | #assign $1 nazwaZmiennej 88 | # 89 | #alias @="assign ${!count} " 90 | #alias @="local _value=$1; shift; assign ${!count} " 91 | # 92 | #@(){ 93 | # type=$1 94 | # name=$2 95 | # 96 | #} 97 | # 98 | #something(){ 99 | # @ Number X 100 | # @ String paplon 101 | # @ mixed something 102 | # 103 | #} 104 | #kapusta2 outside 105 | # 106 | #fe(){ 107 | # kapusta2 inside another 108 | #} 109 | #fe 110 | 111 | #Log.Debug.SetLevel:4 112 | 113 | #Test.Start 'should print a colorful message' -------------------------------------------------------------------------------- /example/v1/tmp/backtrace-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | #trap "previous_command=\$this_command; this_command=\$BASH_COMMAND" DEBUG 6 | #trap "throw \$previous_command" ERR 7 | 8 | 9 | #Log.Debug.SetLevel 3 10 | import lib/types/base 11 | import lib/types/ui 12 | 13 | import lib/types/util/test 14 | 15 | #rm lampka 16 | 17 | trap "throw \$BASH_COMMAND" ERR 18 | set -o errtrace # trace ERR through 'time command' and other functions 19 | #Test.NewGroup "Objects" 20 | laom(){ 21 | # rm maowerkowekro 22 | # rm pkpkpkp 23 | # kapusta 24 | throw THIS IS UNACCEPTABLE 25 | } 26 | 27 | 28 | laom 29 | 30 | #throw Some-error -------------------------------------------------------------------------------- /example/v1/tmp/build-min.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source "$( cd "${BASH_SOURCE[0]%/*}/.." && pwd )/lib/oo-bootstrap.sh" 5 | 6 | minify() { 7 | [string] filePath 8 | [string] outputPath 9 | 10 | { 11 | local IFS= 12 | while read -r line 13 | do 14 | [[ $line != "System::Bootstrap" ]] && echo "$line" 15 | done < "$filePath" >> "$outputFile" 16 | } 17 | } 18 | 19 | build() { 20 | [string] outputFile 21 | 22 | echo "#!/usr/bin/env bash" > "$outputFile" 23 | echo "# oo-framework version: $(git rev-parse --short HEAD)" >> "$outputFile" 24 | 25 | minify "$__oo__libPath"/oo-bootstrap.sh "$outputFile" 26 | 27 | local file 28 | local path 29 | for file in "$__oo__libPath"/system/*.sh 30 | do 31 | # minify "$file" "$outputFile" 32 | cat "$file" >> "$outputFile" 33 | echo >> "$outputFile" 34 | done 35 | } 36 | 37 | build ./oo-bootstrap.sh -------------------------------------------------------------------------------- /example/v1/tmp/core-test.sh: -------------------------------------------------------------------------------- 1 | ## usage ## 2 | echo "[Creating Human Bazyli:]" 3 | 4 | Object simpleObject 5 | 6 | Human Bazyli 7 | Bazyli.height = 100 8 | 9 | echo "[Eating:]" 10 | 11 | Bazyli.Eat strawberries 12 | Bazyli.Eat lemon 13 | 14 | echo "[Who is he?]" 15 | 16 | Bazyli 17 | 18 | # empty 19 | Bazyli.name 20 | 21 | # set value 22 | Bazyli.name = "Bazyli Brzóska" 23 | 24 | if Bazyli == Bazyli 25 | then 26 | echo equals checking passed 27 | fi 28 | 29 | # set height 30 | Bazyli.height = 170 31 | 32 | # if you want to use a parametrized constructor, create an object and use the double tilda ~~ operator 33 | Human Mambo ~~ Mambo Jumbo 150 960 34 | 35 | echo $(Bazyli.name) is $(Bazyli.height) cm tall. 36 | 37 | # throws an error 38 | Bazyli = "house" 39 | 40 | Array Letters 41 | Array Letters2 42 | 43 | Letters.Add "Hello Bobby" 44 | Letters.Add "Hello Jean" "Hello Maria" 45 | Letters2.Add "Hello Frank" "Bomba" 46 | Letters2.Add "Dude, 47 | How are you doing?" 48 | 49 | letters2=$(Letters2) 50 | Letters.Merge "${!letters2}" 51 | 52 | letters=$(Letters) 53 | for letter in "${!letters}"; do 54 | echo ---- 55 | echo "$letter" 56 | done 57 | 58 | ## or simply: 59 | Letters.List 60 | 61 | Letters.Contains "Hello" && echo "This shouldn't happen" 62 | Letters.Contains "Hello Bobby" && echo "Bobby was welcomed" 63 | 64 | Bazyli.Example "one single sentence" two "and here" "we put" "some stuff" 65 | 66 | Singleton.PrintYoMama 67 | Singleton = "some value" 68 | Singleton 69 | 70 | Singleton.YoMamaNumber 71 | Singleton.YoMamaNumber ++ 72 | Singleton.YoMamaNumber 73 | Singleton.YoMamaNumber._storedVariableName 74 | 75 | ExtensionTest specialVar 76 | specialVar = "testing setter" 77 | 78 | echo "Color Test: $(Color.Blue)Hello $(Color.White)There$(Color.Default)" -------------------------------------------------------------------------------- /example/v1/tmp/import.sh: -------------------------------------------------------------------------------- 1 | namespace oo 2 | 3 | # depends on: bootstrap, Array/Contains 4 | 5 | System::LoadFile(){ 6 | local libPath="$1" 7 | # [string] libPath 8 | 9 | if [ -f "$libPath" ] 10 | then 11 | ## if already imported let's return 12 | # if declare -f "Array::Contains" &> /dev/null && 13 | if [[ ! -z "${__oo__importedFiles[*]}" ]] && Array::Contains "$file" "${__oo__importedFiles[@]}" 14 | then 15 | DEBUG subject=level3 Log "File previously imported: ${libPath}" 16 | return 0 17 | fi 18 | 19 | DEBUG subject=level2 Log "Importing: $libPath" 20 | 21 | __oo__importedFiles+=( "$libPath" ) 22 | 23 | # eval "$(<"$libPath")" 24 | source "$libPath" || throw "Unable to load $libPath" 25 | 26 | # TODO: maybe only Type.Load when the filename starts with a capital? 27 | # In that case all the types would have to start with a capital letter 28 | 29 | # if Function::Exists Type.Load 30 | # then 31 | # Type.Load 32 | # DEBUG subject=level3 Log "Loading Types..." 33 | # fi 34 | else 35 | DEBUG subject=level2 Log "File doesn't exist when importing: $libPath" 36 | fi 37 | } 38 | 39 | System::Import() { 40 | local libPath 41 | for libPath in "$@"; do 42 | local requestedPath="$libPath" 43 | 44 | [ ! -e "$libPath" ] && libPath="${__oo__libPath}/${libPath}" 45 | [ ! -e "$libPath" ] && libPath="${libPath}.sh" 46 | 47 | [ ! -e "$libPath" ] && libPath="${__oo__path}/${libPath}" 48 | [ ! -e "$libPath" ] && libPath="${libPath}.sh" 49 | 50 | DEBUG subject=level4 Log "Trying to load from: ${__oo__path} / ${requestedPath}" 51 | 52 | ## correct path if relative 53 | if [ ! -e "$libPath" ] 54 | then 55 | # try a relative reference 56 | # local localPath="${BASH_SOURCE[1]%/*}" 57 | local localPath="$( cd "${BASH_SOURCE[1]%/*}" && pwd )" 58 | # [ -f "$localPath" ] && localPath="$(dirname "$localPath")" 59 | libPath="${localPath}/${requestedPath}" 60 | DEBUG subject=level4 Log "Trying to load from: ${localPath} / ${requestedPath}" 61 | 62 | [ ! -e "$libPath" ] && libPath="${libPath}.sh" 63 | fi 64 | 65 | DEBUG subject=level3 Log "Trying to load from: ${libPath}" 66 | [ ! -e "$libPath" ] && e="Cannot import $libPath" throw && return 1 67 | 68 | libPath="$(File::GetAbsolutePath "$libPath")" 69 | # [ -e "$libPath" ] && echo "Trying to load from: ${libPath}" 70 | 71 | if [ -d "$libPath" ]; then 72 | local file 73 | for file in "$libPath"/*.sh 74 | do 75 | System::LoadFile "$file" 76 | done 77 | else 78 | System::LoadFile "$libPath" 79 | fi 80 | done 81 | return 0 82 | } 83 | 84 | alias import="System::Import" 85 | 86 | import Array/Contains 87 | -------------------------------------------------------------------------------- /example/v1/tmp/import2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib/oo-bootstrap.sh" 5 | 6 | ## MAIN ## 7 | 8 | namespace oo 9 | 10 | System::LoadFile(){ 11 | [string] libPath 12 | 13 | if [ -f "$libPath" ] 14 | then 15 | ## if already imported let's return 16 | if Array::Contains "$file" "${__oo__importedFiles[@]}" 17 | then 18 | DEBUG subject=level3 Log "File previously imported: ${libPath}" 19 | return 0 20 | fi 21 | 22 | DEBUG subject=level2 Log "Importing: $libPath" 23 | 24 | __oo__importedFiles+=( "$libPath" ) 25 | 26 | source "$libPath" || throw "Unable to load $libPath" 27 | 28 | # TODO: maybe only Type.Load when the filename starts with a capital? 29 | # In that case all the types would have to start with a capital letter 30 | 31 | if Function::Exists Type.Load 32 | then 33 | Type.Load 34 | DEBUG subject=level3 Log "Loading Types..." 35 | fi 36 | else 37 | : 38 | DEBUG subject=level2 Log "File doesn't exist when importing: $libPath" 39 | fi 40 | } 41 | 42 | System::Import() { 43 | local libPath 44 | for libPath in "$@"; do 45 | local requestedPath="$libPath" 46 | 47 | ## correct path if relative 48 | [ ! -e "$libPath" ] && libPath="${__oo__path}/${libPath}" 49 | [ ! -e "$libPath" ] && libPath="${libPath}.sh" 50 | 51 | DEBUG subject=level4 Log "Trying to load from: ${__oo__path} / ${requestedPath}" 52 | 53 | if [ ! -e "$libPath" ] 54 | then 55 | # try a relative reference 56 | # local localPath="${BASH_SOURCE[1]%/*}" 57 | local localPath="$( cd "${BASH_SOURCE[1]%/*}" && pwd )" 58 | # [ -f "$localPath" ] && localPath="$(dirname "$localPath")" 59 | libPath="${localPath}/${requestedPath}" 60 | DEBUG subject=level4 Log "Trying to load from: ${localPath} / ${requestedPath}" 61 | 62 | [ ! -e "$libPath" ] && libPath="${libPath}.sh" 63 | fi 64 | 65 | DEBUG subject=level3 Log "Trying to load from: ${libPath}" 66 | [ ! -e "$libPath" ] && throw "Cannot import $libPath" && return 1 67 | 68 | libPath="$(File::GetAbsolutePath "$libPath")" 69 | 70 | if [ -d "$libPath" ]; then 71 | local file 72 | for file in "$libPath"/*.sh 73 | do 74 | System::LoadFile "$file" 75 | done 76 | else 77 | System::LoadFile "$libPath" 78 | fi 79 | done 80 | return 0 81 | } 82 | 83 | alias import="System::Import" 84 | -------------------------------------------------------------------------------- /example/v1/tmp/named-params.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o pipefail 3 | 4 | shopt -s expand_aliases 5 | 6 | Log.Write() { 7 | echo "${@}" 8 | } 9 | 10 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/system/02_named_parameters.sh" 11 | 12 | testPassingParams() { 13 | [string] hello 14 | [string] two 15 | 16 | true normal code 17 | true normal code2 18 | true normal code3 19 | 20 | echo $hello $two 21 | 22 | # l=4 [string[]] anArrayWithFourElements 23 | # l=2 [string[]] anotherArrayWithTwo 24 | # [string] anotherSingle 25 | # [reference] table 26 | # [...rest] anArrayOfVariedSize 27 | 28 | # test "$hello" = "$1" 29 | # # 30 | # test "${anArrayWithFourElements[0]}" = "$2" 31 | # test "${anArrayWithFourElements[1]}" = "$3" 32 | # test "${anArrayWithFourElements[2]}" = "$4" 33 | # test "${anArrayWithFourElements[3]}" = "$5" 34 | # # 35 | # test "${anotherArrayWithTwo[0]}" = "$6" 36 | # test "${anotherArrayWithTwo[1]}" = "$7" 37 | # # 38 | # test "$anotherSingle" = "$8" 39 | # # 40 | # test "${table[test]}" = "works" 41 | # table[inside]="adding a new value" 42 | # # 43 | # test "${anArrayOfVariedSize[*]}" = "${*:10}" 44 | } 45 | 46 | fourElements=( a1 a2 "a3 with spaces" a4 ) 47 | twoElements=( b1 b2 ) 48 | declare -A assocArray 49 | assocArray[test]="works" 50 | 51 | testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." 52 | 53 | testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." 54 | 55 | test "${assocArray[inside]}" = "adding a new value" -------------------------------------------------------------------------------- /example/v1/tmp/test-extractlocal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | shopt -s expand_aliases 4 | 5 | Function.AssignParamsLocally() { 6 | ## unset first miss 7 | unset __oo__params[0] 8 | declare -i i 9 | local iparam 10 | local variable 11 | local type 12 | for i in "${!__oo__params[@]}" 13 | do 14 | echo "i : $i" 15 | 16 | iparam=$i 17 | 18 | variable="${__oo__params[$i]}" 19 | echo "var : ${__oo__params[$i]}" 20 | 21 | i+=-1 22 | type="${__oo__param_types[$i]}" 23 | echo "type : ${__oo__param_types[$i]}" 24 | 25 | ### TODO: check if type is correct 26 | 27 | # eval "$variable=\"${!iparam}\"" 28 | eval "$variable=\"\$$iparam\"" 29 | done 30 | } 31 | 32 | alias Function.StashPreviousLocal="declare -a \"__oo__params+=( \$(declare -p | grep 'declare -- ' | tail -1 | cut -d ' ' -f 3) )\"" 33 | alias [string]="Function.StashPreviousLocal; declare -a \"__oo__param_types+=( TYPE )\"; local " 34 | alias @@map="Function.StashPreviousLocal; Function.AssignParamsLocally" 35 | 36 | bambo() { 37 | : [string] test1 38 | : [string] test2 39 | 40 | echo here is first: "$test1" 41 | echo here is 2nd: "$test2" 42 | } 43 | 44 | bambo "value of one" valueOf2 45 | -------------------------------------------------------------------------------- /example/v1/tmp/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Log Call Stack 4 | LSLOGSTACK () { 5 | local i=0 6 | local FRAMES=${#BASH_LINENO[@]} 7 | # FRAMES-2 skips main, the last one in arrays 8 | for ((i=FRAMES-2; i>=0; i--)); do 9 | echo ' File' \"${BASH_SOURCE[i+1]}\", line ${BASH_LINENO[i]}, in ${FUNCNAME[i+1]} 10 | # Grab the source code of the line 11 | sed -n "${BASH_LINENO[i]}{s/^/ /;p}" "${BASH_SOURCE[i+1]}" 12 | done 13 | } 14 | 15 | deeper(){ 16 | echo deeper 17 | echo name: $name 18 | name=changed 19 | # echo this: ${FUNCNAME[0]} 20 | # echo parent: ${FUNCNAME[1]} 21 | # echo source: ${BASH_SOURCE[1]} 22 | } 23 | inside(){ 24 | # echo params "$@" 25 | # echo 26 | # echo surface 27 | # caller 0 28 | # ps -ocommand= -p $PPID | awk "{print $1}" | awk -F/ "{print $NF}" 29 | # ps -ocommand= 30 | local name=$1 31 | deeper 32 | echo $name 33 | } 34 | 35 | inside "$@" 36 | -------------------------------------------------------------------------------- /example/v1/tmp/test2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a template0=( "123" "456" ) 4 | declare -a template1=( "zxc" "edc" ) 5 | 6 | i=0 7 | 8 | template_name="template$i[@]" 9 | echo ${!template_name} 10 | 11 | full=template$i 12 | 13 | #i=2 14 | #declare -a "template$i=( 414 515 )" 15 | 16 | modifyheyho() { 17 | heyho=true 18 | } 19 | 20 | doit() { 21 | declare -ga "template$i+=( \"one\" \"\$@\" )" 22 | declare -p template$i 23 | 24 | local heyho 25 | modifyheyho 26 | echo $heyho 27 | } 28 | 29 | doit "$@" 30 | 31 | echo should be nothing $heyho 32 | 33 | declare -p template$i 34 | 35 | #declare -a template2=( "414" "515" ) 36 | 37 | shopt -s expand_aliases 38 | 39 | alias booboo='echo booboo' 40 | 41 | makealias(){ 42 | booboo 43 | eval "alias bambo='ls'" 44 | } 45 | 46 | makealias 47 | 48 | bambo -------------------------------------------------------------------------------- /example/v1/tmp/types/examples.sh: -------------------------------------------------------------------------------- 1 | class:Animal() { 2 | 3 | extends Object 4 | 5 | if $instance 6 | then 7 | 8 | : 9 | 10 | else 11 | Animal::__getter__() { 12 | echo "That is the animal" 13 | } 14 | fi 15 | 16 | } && Type.Load 17 | 18 | class:Human() { 19 | 20 | extends Animal 21 | 22 | public Number height 23 | public Number width 24 | public Number phone 25 | public String name 26 | 27 | methods 28 | 29 | Human::__toString__() { 30 | echo "I'm a human ($this)" 31 | } 32 | 33 | Human::__getter__() { 34 | $this.__toString__ 35 | } 36 | 37 | Human::Eat() { 38 | echo "$this is eating $1" 39 | } 40 | 41 | Human::Example() { 42 | ## TODO: this shouldn't need eval, but for the strangest reason ever, it does. 43 | ## also: subshell protects variables from falling through to another overload 44 | ## perhaps we could use it here? 45 | 46 | : @Array mergeWith 47 | : @Number many 48 | : [...rest] stuff 49 | 50 | @@map && { 51 | echo Testing \"$mergeWith\" at manyCount: $many 52 | echo Stuff: "${stuff[*]}" 53 | return 54 | } 55 | 56 | 57 | ## here we have an overloaded version of this function that takes in Array, Object and params 58 | ## beauty is that by passing different objects we can get 59 | : @Array mergeWith 60 | : @Object overloadedType 61 | 62 | @@map && { 63 | echo Merging \"$mergeWith\", we use the Object: $overloadedType 64 | return 65 | } 66 | 67 | } 68 | 69 | Human::__equals__() { 70 | echo "TODO: Checking if $this equals $1" 71 | } 72 | 73 | 74 | Human::__constructor__() { 75 | subject=level1 Log "Hello, I am the constructor! You have passed these arguments [ $@ ]" 76 | } 77 | 78 | ~methods 79 | 80 | } && Type.Load 81 | 82 | static:Singleton() { 83 | 84 | extends Var 85 | 86 | Number YoMamaNumber = 150 87 | 88 | Singleton::__constructor__() { 89 | echo "Yo Yo. I'm a singleton. Meaning. Static. Yo." 90 | Singleton = "Yo Mama!" 91 | } 92 | 93 | Singleton.PrintYoMama() { 94 | ## prints the stored value, which is set in the constructor above 95 | echo "$(Singleton) $(Singleton.YoMamaNumber)!" 96 | } 97 | 98 | } && Type.Load 99 | 100 | class:BaseTestBase() { 101 | 102 | extends Var 103 | 104 | method BaseTestBase::__setter__() { 105 | echo "I am the setter of the BaseTestBase" 106 | Var::__setter__ "$@" 107 | } 108 | 109 | } && Type.Load 110 | 111 | class:ExtensionTest() { 112 | 113 | extends BaseTestBase 114 | 115 | method ExtensionTest::__setter__() { 116 | echo "That is just the test showing that I can call the base method" 117 | BaseTestBase::__setter__ "$@" 118 | } 119 | 120 | } && Type.Load 121 | 122 | static:Color() { 123 | extends Object 124 | 125 | String Default = $'\033[0m' 126 | String White = $'\033[0;37m' 127 | String Black = $'\033[0;30m' 128 | String Blue = $'\033[0;34m' 129 | 130 | } && Type.Load 131 | -------------------------------------------------------------------------------- /example/v1/try-catch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | #Log.Debug.SetLevel 2 6 | 7 | import lib/types/base 8 | import lib/types/ui 9 | import lib/types/util/test 10 | 11 | 12 | try 13 | echo something 14 | false dupa 15 | catch 16 | echo cought Dupa 17 | 18 | 19 | echo before $__oo__insideTryCatch 20 | try 21 | echo inside $__oo__insideTryCatch 22 | throw dupa 23 | catch 24 | echo cought $__oo__insideTryCatch 25 | 26 | echo after $__oo__insideTryCatch 27 | 28 | try 29 | echo inside $__oo__insideTryCatch 30 | throw dupa 31 | catch 32 | echo cought $__oo__insideTryCatch 33 | 34 | echo after $__oo__insideTryCatch 35 | 36 | try 37 | echo inside $__oo__insideTryCatch 38 | throw dupa 39 | catch 40 | echo cought $__oo__insideTryCatch 41 | 42 | echo after $__oo__insideTryCatch 43 | 44 | throw dupa 45 | 46 | unknown 47 | 48 | 49 | #it 'should try-and-catch nested' 50 | #try { 51 | # try { 52 | # try { 53 | # echo Trying... 54 | # throw "Works" 55 | # echo Not executed 56 | # } catch { 57 | # echo "$__EXCEPTION_SOURCE__: Inner Cought $__EXCEPTION__ at $__EXCEPTION_LINE__" 58 | # throw "Fallback" 59 | # } 60 | # } catch { 61 | # echo "$__EXCEPTION_SOURCE__: Outer Cought $__EXCEPTION__ at $__EXCEPTION_LINE__" 62 | # } 63 | #} 64 | #expectOutputPass 65 | # 66 | ##set -x 67 | #it 'should make an instance of an Object' 68 | #try 69 | # test a = a 70 | # #Object anObject 71 | # #test "$(anObject)" = "[Object] anObject" 72 | # Test.OK 73 | #catch 74 | # echo "$__EXCEPTION_SOURCE__: Cought $__EXCEPTION__ at $__EXCEPTION_LINE__" 75 | # #Test.Errors = true 76 | # #Test.Fail 77 | # 78 | #it 'should throw' 79 | #try 80 | # test a = b 81 | #catch { 82 | # echo "$__EXCEPTION_SOURCE__: Cought $__EXCEPTION__ at $__EXCEPTION_LINE__" 83 | # Test.EchoedOK 84 | #} 85 | #Test.Errors = true 86 | #Test.Fail 87 | 88 | 89 | #try 90 | # echo trying... 91 | # echo 92 | ## blablabla 93 | # throw "This Works YEAH" 94 | # echo Not executed 95 | #catch 96 | # echo "$__EXCEPTION_SOURCE__: Cought $__EXCEPTION__ at $__EXCEPTION_LINE__" -------------------------------------------------------------------------------- /lib/Array/Contains.sh: -------------------------------------------------------------------------------- 1 | Array::Contains() { 2 | local element 3 | for element in "${@:2}" 4 | do 5 | [[ "$element" = "$1" ]] && return 0 6 | done 7 | return 1 8 | } 9 | -------------------------------------------------------------------------------- /lib/Array/Intersect.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters util/type 2 | 3 | Array::Intersect() { 4 | @required [array] arrayA 5 | @required [array] arrayB 6 | 7 | array intersection 8 | 9 | # http://stackoverflow.com/questions/2312762/compare-difference-of-two-arrays-in-bash 10 | for i in "${arrayA[@]}" 11 | do 12 | local skip= 13 | for j in "${arrayB[@]}" 14 | do 15 | [[ "$i" == "$j" ]] && { skip=1; break; } 16 | done 17 | [[ -n $skip ]] || intersection+=("$i") 18 | done 19 | 20 | @get intersection 21 | } 22 | -------------------------------------------------------------------------------- /lib/Array/List.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters 2 | 3 | ## generates a list separated by new lines 4 | Array::List() { 5 | @required [string] variableName 6 | [string] separator=$'\n' 7 | 8 | local indirectAccess="${variableName}[*]" 9 | ( 10 | local IFS="$separator" 11 | echo "${!indirectAccess}" 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /lib/Array/Reverse.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters 2 | 3 | ## TODO: consider making Parameters::Methods 4 | ## since this actually modifies parameters, not arrays 5 | 6 | ## static methods should be Array::Method, with capital letter 7 | 8 | # static version 9 | Array::Reverse() { 10 | [...rest] this 11 | 12 | local -i length=${#this[@]} #$(this length) 13 | local -a outArray 14 | local -i indexFromEnd 15 | local -i index 16 | 17 | for index in "${!this[@]}" 18 | do 19 | indexFromEnd=$(( $length - 1 - $index )) 20 | outArray+=( "${this[$indexFromEnd]}" ) 21 | done 22 | 23 | @get outArray 24 | } 25 | -------------------------------------------------------------------------------- /lib/String/GetSpaces.sh: -------------------------------------------------------------------------------- 1 | String::GetSpaces() { 2 | local howMany="$1" 3 | 4 | if [[ "$howMany" -gt 0 ]] 5 | then 6 | ( printf "%*s" "$howMany" ) 7 | fi 8 | } 9 | -------------------------------------------------------------------------------- /lib/String/IsNumber.sh: -------------------------------------------------------------------------------- 1 | String::IsNumber() { 2 | local input="$1" 3 | 4 | local regex='^-?[0-9]+([.][0-9]+)?$' 5 | if ! [[ "$input" =~ $regex ]] 6 | then 7 | return 1 8 | fi 9 | return 0 10 | } 11 | -------------------------------------------------------------------------------- /lib/String/SanitizeForVariable.sh: -------------------------------------------------------------------------------- 1 | String::SanitizeForVariableName() { 2 | local type="$1" 3 | echo "${type//[^a-zA-Z0-9]/_}" 4 | } 5 | -------------------------------------------------------------------------------- /lib/String/SlashReplacement.sh: -------------------------------------------------------------------------------- 1 | String::ReplaceSlashes() { 2 | local stringToMark="$1" 3 | 4 | # Workaround for a Bash bug that causes string replacement to fail when a \ is in the string 5 | local slash="\\" 6 | local slashReplacement='_%SLASH%_' 7 | echo "${stringToMark/$slash$slash/$slashReplacement}" 8 | } 9 | 10 | String::RestoreSlashes() { 11 | local stringToMark="$1" 12 | 13 | # Workaround for a Bash bug that causes string replacement to fail when a \ is in the string 14 | local slash="\\" 15 | local slashReplacement='_%SLASH%_' 16 | echo "${stringToMark/$slashReplacement/$slash}" 17 | } 18 | -------------------------------------------------------------------------------- /lib/String/UUID.sh: -------------------------------------------------------------------------------- 1 | String::GenerateUUID() { 2 | ## https://gist.github.com/markusfisch/6110640 3 | local N B C='89ab' 4 | 5 | for (( N=0; N < 16; ++N )) 6 | do 7 | B=$(( $RANDOM%256 )) 8 | 9 | case $N in 10 | 6) 11 | printf '4%x' $(( B%16 )) 12 | ;; 13 | 8) 14 | printf '%c%x' ${C:$RANDOM%${#C}:1} $(( B%16 )) 15 | ;; 16 | 3 | 5 | 7 | 9) 17 | printf '%02x-' $B 18 | ;; 19 | *) 20 | printf '%02x' $B 21 | ;; 22 | esac 23 | done 24 | } 25 | -------------------------------------------------------------------------------- /lib/TypePrimitives/array.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters util/type Array 2 | 3 | namespace oo/type 4 | ### ARRAY 5 | 6 | ## these three are same as map - make map extend array and merge in the future 7 | array.get() { 8 | @return:value "${this[$1]}" 9 | } 10 | 11 | array.set() { 12 | this["$1"]="$2" 13 | 14 | @return #this 15 | } 16 | 17 | array.delete() { 18 | unset this["$1"] 19 | 20 | @return #this 21 | } 22 | 23 | array.push() { 24 | [...rest] values 25 | 26 | local value 27 | 28 | for value in "${values[@]}" 29 | do 30 | this+=("$value") 31 | done 32 | 33 | @return 34 | } 35 | 36 | array.length() { 37 | local value="${#this[@]}" 38 | @return value 39 | } 40 | 41 | array.contains() { 42 | local element 43 | 44 | @return # is it required? TODO: test 45 | 46 | ## TODO: probably should return a [boolean] type, not normal return 47 | 48 | for element in "${this[@]}" 49 | do 50 | [[ "$element" == "$1" ]] && return 0 51 | done 52 | return 1 53 | } 54 | 55 | array.indexOf() { 56 | # Log this: $(declare -p this) 57 | 58 | local index 59 | 60 | for index in "${!this[@]}" 61 | do 62 | # Log index: $index "${!this[@]}" 63 | # Log value: "${this[$index]}" 64 | [[ "${this[$index]}" == "$1" ]] && @return:value $index && return 65 | done 66 | @return:value -1 67 | } 68 | 69 | array.reverse() { 70 | # Log reversing: $(@get this) 71 | local -i length=${#this[@]} #$(this length) 72 | local -a outArray 73 | local -i indexFromEnd 74 | local -i index 75 | 76 | for index in "${!this[@]}" 77 | do 78 | indexFromEnd=$(( $length - 1 - $index )) 79 | outArray+=( "${this[$indexFromEnd]}" ) 80 | done 81 | 82 | @return outArray 83 | } 84 | 85 | array.forEach() { 86 | [string] action 87 | 88 | string item 89 | integer index 90 | 91 | string methodName=__array_forEach_temp_method 92 | eval "$methodName() { $action ; }" 93 | 94 | # DEBUG Console::WriteStdErr "escaping: $methodName() { $action ; }" 95 | 96 | for index in "${!this[@]}" 97 | do 98 | item="${this[$index]}" 99 | # eval "$action" 100 | $methodName "$item" "$index" 101 | done 102 | 103 | unset -f $methodName 104 | 105 | @return 106 | } 107 | 108 | array.map() { 109 | [string] action 110 | 111 | string item 112 | integer index 113 | array out 114 | 115 | string methodName=__array_map_temp_method 116 | 117 | eval "$methodName() { $action ; }" 118 | 119 | for index in "${!this[@]}" 120 | do 121 | item="${this[$index]}" 122 | out[$index]=$($methodName "$item" "$index") 123 | done 124 | 125 | unset -f $methodName 126 | 127 | @return out 128 | } 129 | 130 | 131 | array.concatPush() { 132 | @required [array] concatWithArray 133 | 134 | # TODO: why doesn't this work? seems that it is run in a subshell? 135 | # var: concatWithArray forEach 'var: self push "$(var: item)"' 136 | 137 | local index 138 | for index in "${!concatWithArray[@]}" 139 | do 140 | this push "${concatWithArray[$index]}" 141 | done 142 | 143 | @return 144 | } 145 | 146 | array.concat() { 147 | @required [array] concatWithArray 148 | 149 | array outArray=$(this) 150 | 151 | local index 152 | for index in "${!concatWithArray[@]}" 153 | do 154 | var: outArray push "${concatWithArray[$index]}" 155 | done 156 | 157 | # TODO: 158 | # var: concatWithArray forEach 'var: outArray push "$(var: item)"' 159 | 160 | @return outArray 161 | } 162 | 163 | array.getLastElement() { 164 | @return:value "${this[(${#this[@]}-1)]}" 165 | # alternative in bash 4.2: ${this[-1]} 166 | } 167 | 168 | array.withoutLastElement() { 169 | @return:value "${this[@]:0:(${#this[@]}-1)}" 170 | } 171 | 172 | array.toString() { 173 | [string] separator=$'\n' 174 | @return:value "$(Array::List this "$separator")" 175 | } 176 | 177 | array.toJSON() { 178 | string json=$(this forEach 'printf %s "$(var: item toJSON), "') 179 | @return:value "[${json%,*}]" 180 | } 181 | 182 | array.every() { 183 | [integer] every 184 | [integer] startingIndex 185 | 186 | array returnArray 187 | 188 | local -i count=0 189 | 190 | local index 191 | for index in "${!this[@]}" 192 | do 193 | if [[ $index -eq $(( $every * $count + $startingIndex )) ]] 194 | then 195 | #echo "$index: ${this[$index]}" 196 | returnArray+=( "${this[$index]}" ) 197 | count+=1 198 | fi 199 | done 200 | 201 | @return returnArray 202 | } 203 | 204 | Type::InitializePrimitive array 205 | 206 | ### /ARRAY 207 | -------------------------------------------------------------------------------- /lib/TypePrimitives/boolean.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters util/type 2 | 3 | namespace oo/type 4 | ### BOOLEAN 5 | 6 | boolean.__getter__() { 7 | test "$this" == "${__primitive_extension_fingerprint__boolean}:true" 8 | } 9 | 10 | boolean.toString() { 11 | if [[ "$this" == "${__primitive_extension_fingerprint__boolean}:true" ]] 12 | then 13 | @return:value true 14 | else 15 | @return:value false 16 | fi 17 | } 18 | 19 | boolean.=() { 20 | [string] value 21 | 22 | if [[ "$value" == "true" ]] 23 | then 24 | this="${__primitive_extension_fingerprint__boolean}:true" 25 | else 26 | this="${__primitive_extension_fingerprint__boolean}:false" 27 | fi 28 | 29 | @return 30 | } 31 | 32 | Type::InitializePrimitive boolean 33 | ### /BOOLEAN 34 | -------------------------------------------------------------------------------- /lib/TypePrimitives/integer.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters util/type 2 | 3 | namespace oo/type 4 | ## Awaiting pull requests for this one! 5 | 6 | integer.=() { 7 | [string] value 8 | 9 | this="$value" 10 | 11 | @return 12 | } 13 | 14 | Type::InitializePrimitive integer 15 | -------------------------------------------------------------------------------- /lib/TypePrimitives/map.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters util/type 2 | 3 | namespace oo/type 4 | 5 | ### MAP 6 | ## TODO: use vars, not $1-9 so $ref: references are resolved 7 | 8 | map.set() { 9 | this["$1"]="$2" 10 | 11 | @return #this 12 | } 13 | 14 | map.delete() { 15 | unset this["$1"] 16 | 17 | @return #this 18 | } 19 | 20 | map.get() { 21 | @return:value "${this[$1]}" 22 | } 23 | 24 | Type::InitializePrimitive map 25 | 26 | ### /MAP 27 | 28 | 29 | ## TODO: 30 | #Array::Assign() { 31 | # local source="$1" 32 | # local target="$2" 33 | # 34 | # eval "local -a tempMap=\"\$$__assign_paramNo\"" 35 | # local index 36 | # local value 37 | # 38 | # ## copy the array / map item by item 39 | # for index in "${!tempMap[@]}" 40 | # do 41 | # eval "$__assign_varName[\$index]=\"\${tempMap[\$index]}\"" 42 | # done 43 | # 44 | # unset index value tempMap 45 | #} 46 | # 47 | #Map::Assign() { 48 | # ## TODO: test this 49 | # eval "local -$(Variable::GetDeclarationFlagFromType '$__assign_varType') tempMap=\"\$$__assign_paramNo\"" 50 | # local index 51 | # local value 52 | # 53 | # ## copy the array / map item by item 54 | # for index in "${!tempMap[@]}" 55 | # do 56 | # eval "$__assign_varName[\$index]=\"\${tempMap[\$index]}\"" 57 | # done 58 | # 59 | # unset index value tempMap 60 | #} 61 | -------------------------------------------------------------------------------- /lib/TypePrimitives/string.sh: -------------------------------------------------------------------------------- 1 | import util/namedParameters util/type String 2 | 3 | namespace oo/type 4 | ### STRING 5 | 6 | string.=() { 7 | [string] value 8 | 9 | this="$value" 10 | 11 | @return 12 | } 13 | 14 | string.toUpper() { 15 | @return:value "${this^^}" 16 | } 17 | 18 | string.toArray() { 19 | [string] separationCharacter=$'\n' # $'\UFAFAF' 20 | 21 | array returnArray 22 | 23 | local newLine=$'\n' 24 | local string="${this//"$newLine"/"$separationCharacter"}" 25 | local IFS=$separationCharacter 26 | local element 27 | for element in $string 28 | do 29 | returnArray+=( "$element" ) 30 | done 31 | 32 | local newLines=${string//[^$separationCharacter]} 33 | local -i trailingNewLines=$(( ${#newLines} - ${#returnArray[@]} + 1 )) 34 | while (( trailingNewLines-- )) 35 | do 36 | returnArray+=( "" ) 37 | done 38 | 39 | @return returnArray 40 | } 41 | 42 | ## test this: 43 | string.getMatchGroups() { 44 | @handleless @required [string] regex 45 | [string] returnMatchNumber='@' # @ means all 46 | 47 | array returnArray 48 | 49 | subject="matchGroups" Log "string to match on: $this" 50 | 51 | local -i matchNo=0 52 | local string="$this" 53 | while [[ "$string" =~ $regex ]] 54 | do 55 | subject="regex" Log "match $matchNo: ${BASH_REMATCH[*]}" 56 | 57 | if [[ "$returnMatchNumber" == "@" || $matchNo -eq "$returnMatchNumber" ]] 58 | then 59 | returnArray+=( "${BASH_REMATCH[@]}" ) 60 | [[ "$returnMatchNumber" == "@" ]] || { @return returnArray && return 0; } 61 | fi 62 | # cut out the match so we may continue 63 | string="${string/"${BASH_REMATCH[0]}"}" # " 64 | matchNo+=1 65 | done 66 | 67 | @return returnArray 68 | } 69 | 70 | string.match() { 71 | @handleless @required [string] regex 72 | [integer] capturingGroup=0 73 | [string] returnMatchNumber=0 # @ means all 74 | 75 | DEBUG subject="string.match" Log "string to match on: $this" 76 | 77 | array allMatches=$(this getMatchGroups "$regex" "$returnMatchNumber") 78 | 79 | @return:value "${allMatches[$capturingGroup]}" 80 | } 81 | 82 | string.toJSON() { 83 | ## http://stackoverflow.com/a/3020108/595157 84 | 85 | string escaped="$this" 86 | escaped=$(var: escaped forEachChar '(( 16#$(var: char getCharCode) < 20 )) && printf "\\${char}" || printf "$char"') 87 | 88 | escaped="${escaped//\\/\\\\}" ## slashes 89 | escaped="\"${escaped//\"/\\\"}\"" ## quotes 90 | 91 | @return escaped 92 | } 93 | 94 | string.forEachChar() { 95 | [string] action 96 | 97 | string char 98 | integer index 99 | 100 | string methodName=__string_forEachChar_temp_method 101 | 102 | eval "$methodName() { $action ; }" 103 | 104 | for (( index=0; index<${#this}; index++ )) 105 | do 106 | char="${this:$index:1}" 107 | $methodName "$char" "$index" 108 | done 109 | 110 | unset -f $methodName 111 | 112 | @return 113 | } 114 | 115 | string.getCharCode() { 116 | ## returns char code of the first character 117 | @return:value $(printf %x "'$this") 118 | } 119 | 120 | Type::InitializePrimitive string 121 | 122 | ### /STRING 123 | 124 | ## TODO: 125 | 126 | #static String.TabsForSpaces() { 127 | # [string] input 128 | # # TODO: [string] spaceCount=4 129 | # 130 | # # hardcoded 1 tab = 4 spaces 131 | # echo "${input//[ ]/ }" 132 | #} 133 | # 134 | #static String.RegexMatch() { 135 | # [string] text; [string] regex; [string] param 136 | # 137 | # if [[ "$text" =~ $regex ]]; then 138 | # if [[ ! -z $param ]]; then 139 | # echo "${BASH_REMATCH[${param}]}" 140 | # fi 141 | # return 0 142 | # else 143 | # return 1 144 | # # no match 145 | # fi 146 | #} 147 | # 148 | #static String.SpaceCount() { 149 | # [string] text 150 | # 151 | # # note: you shouldn't mix tabs and spaces, we explicitly don't count tabs here 152 | # local spaces="$(String.RegexMatch "$text" "^[ ]*([ ]*)[.]*" 1)" 153 | # echo "${#spaces}" 154 | #} 155 | # 156 | #static String.Trim() { 157 | # [string] text 158 | # 159 | # echo "$(String.RegexMatch "$text" "^[ ]*(.*)" 1)" 160 | # #text="${text#"${text%%[![:space:]]*}"}" # remove leading whitespace characters 161 | # #text="${text%"${text##*[![:space:]]}"}" # remove trailing whitespace characters 162 | # #echo -n "$text" 163 | #} 164 | # 165 | #static String.Contains() { 166 | # [string] string 167 | # [string] match 168 | # 169 | # [[ "$string" == *"$match"* ]] 170 | # return $? 171 | #} 172 | # 173 | #static String.StartsWith() { 174 | # [string] string 175 | # [string] match 176 | # 177 | # [[ "$string" == "$match"* ]] 178 | # return $? 179 | #} 180 | # 181 | #static String.EndsWith() { 182 | # [string] string 183 | # [string] match 184 | # 185 | # [[ "$string" == *"$match" ]] 186 | # return $? 187 | #} 188 | # 189 | #method String::GetSanitizedVariableName() { 190 | # String.GetSanitizedVariableName "$($this)" 191 | #} 192 | # 193 | #method String::RegexMatch() { 194 | # [string] regex 195 | # [string] param 196 | # 197 | # String.RegexMatch "$($this)" "$regex" "$param" 198 | #} 199 | -------------------------------------------------------------------------------- /lib/UI/Color.sh: -------------------------------------------------------------------------------- 1 | alias UI.Color.IsAvailable='[ $(tput colors 2>/dev/null || echo 0) -ge 16 ] && [ -t 1 ]' 2 | if UI.Color.IsAvailable 3 | then 4 | alias UI.Color.Default="echo \$'\033[0m'" 5 | 6 | alias UI.Color.Black="echo \$'\033[0;30m'" 7 | alias UI.Color.Red="echo \$'\033[0;31m'" 8 | alias UI.Color.Green="echo \$'\033[0;32m'" 9 | alias UI.Color.Yellow="echo \$'\033[0;33m'" 10 | alias UI.Color.Blue="echo \$'\033[0;34m'" 11 | alias UI.Color.Magenta="echo \$'\033[0;35m'" 12 | alias UI.Color.Cyan="echo \$'\033[0;36m'" 13 | alias UI.Color.LightGray="echo \$'\033[0;37m'" 14 | 15 | alias UI.Color.DarkGray="echo \$'\033[0;90m'" 16 | alias UI.Color.LightRed="echo \$'\033[0;91m'" 17 | alias UI.Color.LightGreen="echo \$'\033[0;92m'" 18 | alias UI.Color.LightYellow="echo \$'\033[0;93m'" 19 | alias UI.Color.LightBlue="echo \$'\033[0;94m'" 20 | alias UI.Color.LightMagenta="echo \$'\033[0;95m'" 21 | alias UI.Color.LightCyan="echo \$'\033[0;96m'" 22 | alias UI.Color.White="echo \$'\033[0;97m'" 23 | 24 | # flags 25 | alias UI.Color.Bold="echo \$'\033[1m'" 26 | alias UI.Color.Dim="echo \$'\033[2m'" 27 | alias UI.Color.Italics="echo \$'\033[3m'" 28 | alias UI.Color.Underline="echo \$'\033[4m'" 29 | alias UI.Color.Blink="echo \$'\033[5m'" 30 | alias UI.Color.Invert="echo \$'\033[7m'" 31 | alias UI.Color.Invisible="echo \$'\033[8m'" 32 | 33 | alias UI.Color.NoBold="echo \$'\033[21m'" 34 | alias UI.Color.NoDim="echo \$'\033[22m'" 35 | alias UI.Color.NoItalics="echo \$'\033[23m'" 36 | alias UI.Color.NoUnderline="echo \$'\033[24m'" 37 | alias UI.Color.NoBlink="echo \$'\033[25m'" 38 | alias UI.Color.NoInvert="echo \$'\033[27m'" 39 | alias UI.Color.NoInvisible="echo \$'\033[28m'" 40 | else 41 | alias UI.Color.Default="echo" 42 | 43 | alias UI.Color.Black="echo" 44 | alias UI.Color.Red="echo" 45 | alias UI.Color.Green="echo" 46 | alias UI.Color.Yellow="echo" 47 | alias UI.Color.Blue="echo" 48 | alias UI.Color.Magenta="echo" 49 | alias UI.Color.Cyan="echo" 50 | alias UI.Color.LightGray="echo" 51 | 52 | alias UI.Color.DarkGray="echo" 53 | alias UI.Color.LightRed="echo" 54 | alias UI.Color.LightGreen="echo" 55 | alias UI.Color.LightYellow="echo" 56 | alias UI.Color.LightBlue="echo" 57 | alias UI.Color.LightMagenta="echo" 58 | alias UI.Color.LightCyan="echo" 59 | alias UI.Color.White="echo" 60 | 61 | # flags 62 | alias UI.Color.Bold="echo" 63 | alias UI.Color.Dim="echo" 64 | alias UI.Color.Underline="echo" 65 | alias UI.Color.Blink="echo" 66 | alias UI.Color.Invert="echo" 67 | alias UI.Color.Invisible="echo" 68 | 69 | alias UI.Color.NoBold="echo" 70 | alias UI.Color.NoDim="echo" 71 | alias UI.Color.NoUnderline="echo" 72 | alias UI.Color.NoBlink="echo" 73 | alias UI.Color.NoInvert="echo" 74 | alias UI.Color.NoInvisible="echo" 75 | fi 76 | 77 | alias UI.Powerline.IsAvailable="UI.Color.IsAvailable && test -z \${NO_UNICODE-} && (echo -e $'\u1F3B7' | grep -v F3B7) &> /dev/null" 78 | if UI.Powerline.IsAvailable 79 | then 80 | alias UI.Powerline.PointingArrow="echo -e $'\u27a1'" 81 | alias UI.Powerline.ArrowLeft="echo -e $'\u25c0'" 82 | alias UI.Powerline.ArrowRight="echo -e $'\u25b6'" 83 | alias UI.Powerline.ArrowRightDown="echo -e $'\u2198'" 84 | alias UI.Powerline.ArrowDown="echo -e $'\u2B07'" 85 | alias UI.Powerline.PlusMinus="echo -e $'\ue00b1'" 86 | alias UI.Powerline.Branch="echo -e $'\ue0a0'" 87 | alias UI.Powerline.RefersTo="echo -e $'\u27a6'" 88 | alias UI.Powerline.OK="echo -e $'\u2714'" 89 | alias UI.Powerline.Fail="echo -e $'\u2718'" 90 | alias UI.Powerline.Lightning="echo -e $'\u26a1'" 91 | alias UI.Powerline.Cog="echo -e $'\u2699'" 92 | alias UI.Powerline.Heart="echo -e $'\u2764'" 93 | 94 | # colorful 95 | alias UI.Powerline.Star="echo -e $'\u2b50'" 96 | alias UI.Powerline.Saxophone="echo -e $'\U1F3B7'" 97 | alias UI.Powerline.ThumbsUp="echo -e $'\U1F44D'" 98 | else 99 | alias UI.Powerline.PointingArrow="echo '~'" 100 | alias UI.Powerline.ArrowLeft="echo '<'" 101 | alias UI.Powerline.ArrowRight="echo '>'" 102 | alias UI.Powerline.ArrowRightDown="echo '>'" 103 | alias UI.Powerline.ArrowDown="echo '_'" 104 | alias UI.Powerline.PlusMinus="echo '+-'" 105 | alias UI.Powerline.Branch="echo '|}'" 106 | alias UI.Powerline.RefersTo="echo '*'" 107 | alias UI.Powerline.OK="echo '+'" 108 | alias UI.Powerline.Fail="echo 'x'" 109 | alias UI.Powerline.Lightning="echo '!'" 110 | alias UI.Powerline.Cog="echo '{*}'" 111 | alias UI.Powerline.Heart="echo '<3'" 112 | 113 | # colorful 114 | alias UI.Powerline.Star="echo '*''" 115 | alias UI.Powerline.Saxophone="echo '(YEAH)'" 116 | alias UI.Powerline.ThumbsUp="echo '(OK)'" 117 | fi 118 | 119 | UI.Color.Print() { 120 | local -i colorCode="$1" 121 | 122 | if UI.Color.IsAvailable 123 | then 124 | local colorString="\$'\033[${colorCode}m'" 125 | eval echo "${colorString}" 126 | else 127 | echo 128 | fi 129 | } 130 | 131 | UI.Color.256text() { 132 | local -i colorNumber="$1" 133 | 134 | if UI.Color.IsAvailable 135 | then 136 | local colorString="\$'\033[38;5;${colorNumber}m'" 137 | eval echo "${colorString}" 138 | else 139 | echo 140 | fi 141 | } 142 | 143 | UI.Color.256background() { 144 | local -i colorNumber="$1" 145 | 146 | if UI.Color.IsAvailable 147 | then 148 | local colorString="\$'\033[48;5;${colorNumber}m'" 149 | eval echo "${colorString}" 150 | else 151 | echo 152 | fi 153 | } 154 | -------------------------------------------------------------------------------- /lib/UI/Color.var.sh: -------------------------------------------------------------------------------- 1 | alias UI.Color.IsAvailable='[ $(tput colors 2>/dev/null || echo 0) -ge 16 ] && [ -t 1 ]' 2 | if UI.Color.IsAvailable 3 | then 4 | UI_Color_Default=$'\033[0m' 5 | 6 | UI_Color_Black=$'\033[0;30m' 7 | UI_Color_Red=$'\033[0;31m' 8 | UI_Color_Green=$'\033[0;32m' 9 | UI_Color_Yellow=$'\033[0;33m' 10 | UI_Color_Blue=$'\033[0;34m' 11 | UI_Color_Magenta=$'\033[0;35m' 12 | UI_Color_Cyan=$'\033[0;36m' 13 | UI_Color_LightGray=$'\033[0;37m' 14 | 15 | UI_Color_DarkGray=$'\033[0;90m' 16 | UI_Color_LightRed=$'\033[0;91m' 17 | UI_Color_LightGreen=$'\033[0;92m' 18 | UI_Color_LightYellow=$'\033[0;93m' 19 | UI_Color_LightBlue=$'\033[0;94m' 20 | UI_Color_LightMagenta=$'\033[0;95m' 21 | UI_Color_LightCyan=$'\033[0;96m' 22 | UI_Color_White=$'\033[0;97m' 23 | 24 | # flags 25 | UI_Color_Bold=$'\033[1m' 26 | UI_Color_Dim=$'\033[2m' 27 | UI_Color_Italics=$'\033[3m' 28 | UI_Color_Underline=$'\033[4m' 29 | UI_Color_Blink=$'\033[5m' 30 | UI_Color_Invert=$'\033[7m' 31 | UI_Color_Invisible=$'\033[8m' 32 | 33 | UI_Color_NoBold=$'\033[21m' 34 | UI_Color_NoDim=$'\033[22m' 35 | UI_Color_NoItalics=$'\033[23m' 36 | UI_Color_NoUnderline=$'\033[24m' 37 | UI_Color_NoBlink=$'\033[25m' 38 | UI_Color_NoInvert=$'\033[27m' 39 | UI_Color_NoInvisible=$'\033[28m' 40 | else 41 | UI_Color_Default="" 42 | 43 | UI_Color_Black="" 44 | UI_Color_Red="" 45 | UI_Color_Green="" 46 | UI_Color_Yellow="" 47 | UI_Color_Blue="" 48 | UI_Color_Magenta="" 49 | UI_Color_Cyan="" 50 | UI_Color_LightGray="" 51 | 52 | UI_Color_DarkGray="" 53 | UI_Color_LightRed="" 54 | UI_Color_LightGreen="" 55 | UI_Color_LightYellow="" 56 | UI_Color_LightBlue="" 57 | UI_Color_LightMagenta="" 58 | UI_Color_LightCyan="" 59 | UI_Color_White="" 60 | 61 | # flags 62 | UI_Color_Bold="" 63 | UI_Color_Dim="" 64 | UI_Color_Italics="" 65 | UI_Color_Underline="" 66 | UI_Color_Blink="" 67 | UI_Color_Invert="" 68 | UI_Color_Invisible="" 69 | 70 | UI_Color_NoBold="" 71 | UI_Color_NoDim="" 72 | UI_Color_NoItalics="" 73 | UI_Color_NoUnderline="" 74 | UI_Color_NoBlink="" 75 | UI_Color_NoInvert="" 76 | UI_Color_NoInvisible="" 77 | fi 78 | 79 | alias UI.Powerline.IsAvailable="UI.Color.IsAvailable && test -z \${NO_UNICODE-} && (echo -e $'\u1F3B7' | grep -v F3B7) &> /dev/null" 80 | if UI.Powerline.IsAvailable 81 | then 82 | UI_Powerline_PointingArrow=$'\u27a1' 83 | UI_Powerline_ArrowLeft=$'\ue0b2' 84 | UI_Powerline_ArrowRight=$'\ue0b0' 85 | UI_Powerline_ArrowRightDown=$'\u2198' 86 | UI_Powerline_ArrowDown=$'\u2B07' 87 | UI_Powerline_PlusMinus=$'\ue00b1' 88 | UI_Powerline_Branch=$'\ue0a0' 89 | UI_Powerline_RefersTo=$'\u27a6' 90 | UI_Powerline_OK=$'\u2714' 91 | UI_Powerline_Fail=$'\u2718' 92 | UI_Powerline_Lightning=$'\u26a1' 93 | UI_Powerline_Cog=$'\u2699' 94 | UI_Powerline_Heart=$'\u2764' 95 | 96 | # colorful 97 | UI_Powerline_Star=$'\u2b50' 98 | UI_Powerline_Saxophone=$'\U1F3B7' 99 | UI_Powerline_ThumbsUp=$'\U1F44D' 100 | else 101 | UI_Powerline_PointingArrow="'~'" 102 | UI_Powerline_ArrowLeft="'<'" 103 | UI_Powerline_ArrowRight="'>'" 104 | UI_Powerline_ArrowRightDown="'>'" 105 | UI_Powerline_ArrowDown="'_'" 106 | UI_Powerline_PlusMinus="'+-'" 107 | UI_Powerline_Branch="'|}'" 108 | UI_Powerline_RefersTo="'*'" 109 | UI_Powerline_OK="'+'" 110 | UI_Powerline_Fail="'x'" 111 | UI_Powerline_Lightning="'!'" 112 | UI_Powerline_Cog="'{*}'" 113 | UI_Powerline_Heart="'<3'" 114 | 115 | # colorful 116 | UI_Powerline_Star="'*''" 117 | UI_Powerline_Saxophone="'(YEAH)'" 118 | UI_Powerline_ThumbsUp="'(OK)'" 119 | fi 120 | 121 | UI.Color.Print() { 122 | local -i colorCode="$1" 123 | 124 | if UI.Color.IsAvailable 125 | then 126 | local colorString="\$'\033[${colorCode}m'" 127 | eval echo "${colorString}" 128 | else 129 | echo 130 | fi 131 | } 132 | 133 | UI.Color.256text() { 134 | local -i colorNumber="$1" 135 | 136 | if UI.Color.IsAvailable 137 | then 138 | local colorString="\$'\033[38;5;${colorNumber}m'" 139 | eval echo "${colorString}" 140 | else 141 | echo 142 | fi 143 | } 144 | 145 | UI.Color.256background() { 146 | local -i colorNumber="$1" 147 | 148 | if UI.Color.IsAvailable 149 | then 150 | local colorString="\$'\033[48;5;${colorNumber}m'" 151 | eval echo "${colorString}" 152 | else 153 | echo 154 | fi 155 | } 156 | -------------------------------------------------------------------------------- /lib/UI/Console.sh: -------------------------------------------------------------------------------- 1 | import UI/Color 2 | 3 | Console::WriteStdErr() { 4 | # http://stackoverflow.com/questions/2990414/echo-that-outputs-to-stderr 5 | cat <<< "$*" 1>&2 6 | return 7 | } 8 | 9 | Console::WriteStdErrAnnotated() { 10 | local script="$1" 11 | local lineNo=$2 12 | local color=$3 13 | local type=$4 14 | shift; shift; shift; shift 15 | 16 | Console::WriteStdErr "$color[$type] $(UI.Color.Blue)[${script}:${lineNo}]$(UI.Color.Default) $* " 17 | } 18 | -------------------------------------------------------------------------------- /lib/UI/Cursor.sh: -------------------------------------------------------------------------------- 1 | import util/class 2 | 3 | class:UI.Cursor() { 4 | # http://askubuntu.com/questions/366103/saving-more-corsor-positions-with-tput-in-bash-terminal 5 | # http://unix.stackexchange.com/questions/88296/get-vertical-cursor-position 6 | 7 | private integer x 8 | private integer y 9 | 10 | UI.Cursor.capture() { 11 | local x 12 | local y 13 | IFS=';' read -sdR -p $'\E[6n' y x 14 | 15 | this y = $(( ${y#*[} - 1 )) 16 | this x = $(( ${x} - 1 )) 17 | 18 | @return 19 | } 20 | 21 | UI.Cursor.restore() { 22 | [integer] shift=1 23 | 24 | local -i totalHeight=$(tput lines) 25 | local -i y=$(this y) 26 | local -i x=$(this x) 27 | 28 | (( $y + 1 == $totalHeight )) && y+=-$shift 29 | 30 | tput cup $y $x 31 | 32 | @return 33 | } 34 | } 35 | 36 | Type::Initialize UI.Cursor 37 | -------------------------------------------------------------------------------- /lib/oo-bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################### 4 | ### BOOTSTRAP FUNCTIONS ### 5 | ########################### 6 | 7 | if [[ -n "${__INTERNAL_LOGGING__:-}" ]] 8 | then 9 | alias DEBUG=":; " 10 | else 11 | alias DEBUG=":; #" 12 | fi 13 | 14 | System::SourceHTTP() { 15 | local URL="$1" 16 | local -i RETRIES=3 17 | shift 18 | 19 | if hash curl 2>/dev/null 20 | then 21 | builtin source <(curl --fail -sL --retry $RETRIES "${URL}" || { [[ "$URL" != *'.sh' && "$URL" != *'.bash' ]] && curl --fail -sL --retry $RETRIES "${URL}.sh"; } || echo "e='Cannot import $URL' throw") "$@" 22 | else 23 | builtin source <(wget -t $RETRIES -O - -o /dev/null "${URL}" || { [[ "$URL" != *'.sh' && "$URL" != *'.bash' ]] && wget -t $RETRIES -O - -o /dev/null "${URL}.sh"; } || echo "e='Cannot import $URL' throw") "$@" 24 | fi 25 | __oo__importedFiles+=( "$URL" ) 26 | } 27 | 28 | System::SourcePath() { 29 | local libPath="$1" 30 | shift 31 | # echo trying $libPath 32 | if [[ -d "$libPath" ]] 33 | then 34 | local file 35 | for file in "$libPath"/*.sh 36 | do 37 | System::SourceFile "$file" "$@" 38 | done 39 | else 40 | System::SourceFile "$libPath" "$@" || System::SourceFile "${libPath}.sh" "$@" 41 | fi 42 | } 43 | 44 | declare -g __oo__fdPath=$(dirname <(echo)) 45 | declare -gi __oo__fdLength=$(( ${#__oo__fdPath} + 1 )) 46 | 47 | System::ImportOne() { 48 | local libPath="$1" 49 | local __oo__importParent="${__oo__importParent-}" 50 | local requestedPath="$libPath" 51 | shift 52 | 53 | if [[ "$requestedPath" == 'github:'* ]] 54 | then 55 | requestedPath="https://raw.githubusercontent.com/${requestedPath:7}" 56 | elif [[ "$requestedPath" == './'* ]] 57 | then 58 | requestedPath="${requestedPath:2}" 59 | elif [[ "$requestedPath" == "$__oo__fdPath"* ]] # starts with /dev/fd 60 | then 61 | requestedPath="${requestedPath:$__oo__fdLength}" 62 | fi 63 | 64 | # [[ "$__oo__importParent" == 'http://'* || "$__oo__importParent" == 'https://'* ]] && 65 | if [[ "$requestedPath" != 'http://'* && "$requestedPath" != 'https://'* ]] 66 | then 67 | requestedPath="${__oo__importParent}/${requestedPath}" 68 | fi 69 | 70 | if [[ "$requestedPath" == 'http://'* || "$requestedPath" == 'https://'* ]] 71 | then 72 | __oo__importParent=$(dirname "$requestedPath") System::SourceHTTP "$requestedPath" 73 | return 74 | fi 75 | 76 | # try relative to parent script 77 | # try with parent 78 | # try without parent 79 | # try global library 80 | # try local library 81 | { 82 | local localPath="$( cd "${BASH_SOURCE[1]%/*}" && pwd )" 83 | localPath="${localPath}/${libPath}" 84 | System::SourcePath "${localPath}" "$@" 85 | } || \ 86 | System::SourcePath "${requestedPath}" "$@" || \ 87 | System::SourcePath "${libPath}" "$@" || \ 88 | System::SourcePath "${__oo__libPath}/${libPath}" "$@" || \ 89 | System::SourcePath "${__oo__path}/${libPath}" "$@" || e="Cannot import $libPath" throw 90 | } 91 | 92 | System::Import() { 93 | local libPath 94 | for libPath in "$@" 95 | do 96 | System::ImportOne "$libPath" 97 | done 98 | } 99 | 100 | File::GetAbsolutePath() { 101 | # http://stackoverflow.com/questions/3915040/bash-fish-command-to-print-absolute-path-to-a-file 102 | # $1 : relative filename 103 | local file="$1" 104 | if [[ "$file" == "/"* ]] 105 | then 106 | echo "$file" 107 | else 108 | echo "$(cd "$(dirname "$file")" && pwd)/$(basename "$file")" 109 | fi 110 | } 111 | 112 | System::WrapSource() { 113 | local libPath="$1" 114 | shift 115 | 116 | builtin source "$libPath" "$@" || throw "Unable to load $libPath" 117 | } 118 | 119 | System::SourceFile() { 120 | local libPath="$1" 121 | shift 122 | 123 | # DEBUG subject=level3 Log "Trying to load from: ${libPath}" 124 | [[ ! -f "$libPath" ]] && return 1 # && e="Cannot import $libPath" throw 125 | 126 | libPath="$(File::GetAbsolutePath "$libPath")" 127 | 128 | # echo "importing $libPath" 129 | 130 | # [ -e "$libPath" ] && echo "Trying to load from: ${libPath}" 131 | if [[ -f "$libPath" ]] 132 | then 133 | ## if already imported let's return 134 | # if declare -f "Array::Contains" &> /dev/null && 135 | if [[ "${__oo__allowFileReloading-}" != true ]] && [[ ! -z "${__oo__importedFiles[*]}" ]] && Array::Contains "$libPath" "${__oo__importedFiles[@]}" 136 | then 137 | # DEBUG subject=level3 Log "File previously imported: ${libPath}" 138 | return 0 139 | fi 140 | 141 | # DEBUG subject=level2 Log "Importing: $libPath" 142 | 143 | __oo__importedFiles+=( "$libPath" ) 144 | __oo__importParent=$(dirname "$libPath") System::WrapSource "$libPath" "$@" 145 | # eval "$(<"$libPath")" 146 | 147 | else 148 | : 149 | # DEBUG subject=level2 Log "File doesn't exist when importing: $libPath" 150 | fi 151 | } 152 | 153 | System::Bootstrap() { 154 | ## note: aliases are visible inside functions only if 155 | ## they were initialized AFTER they were created 156 | ## this is the reason why we have to load files in a specific order 157 | if ! System::Import Array/Contains 158 | then 159 | cat <<< "FATAL ERROR: Unable to bootstrap (missing lib directory?)" 1>&2 160 | exit 1 161 | fi 162 | } 163 | 164 | ######################## 165 | ### INITIALZE SYSTEM ### 166 | ######################## 167 | 168 | # From: http://wiki.bash-hackers.org/scripting/debuggingtips 169 | export PS4='+(${BASH_SOURCE##*/}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 170 | 171 | # Bash will remember & return the highest exitcode in a chain of pipes. 172 | # This way you can catch the error inside pipes, e.g. mysqldump | gzip 173 | set -o pipefail 174 | 175 | shopt -s expand_aliases 176 | declare -g __oo__libPath="$( cd "${BASH_SOURCE[0]%/*}" && pwd )" 177 | declare -g __oo__path="${__oo__libPath}/.." 178 | declare -ag __oo__importedFiles 179 | 180 | ## stubs in case either exception or log is not loaded 181 | namespace() { :; } 182 | throw() { eval 'cat <<< "Exception: $e ($*)" 1>&2; read -s;'; } 183 | 184 | System::Bootstrap 185 | 186 | alias import="__oo__allowFileReloading=false System::Import" 187 | alias source="__oo__allowFileReloading=true System::ImportOne" 188 | alias .="__oo__allowFileReloading=true System::ImportOne" 189 | 190 | declare -g __oo__bootstrapped=true 191 | -------------------------------------------------------------------------------- /lib/util/bash4.sh: -------------------------------------------------------------------------------- 1 | [[ "${BASH_VERSINFO[0]}" -lt 4 ]] && echo "The module you are trying to load requires bash >= 4" && exit 1 || true 2 | -------------------------------------------------------------------------------- /lib/util/class.sh: -------------------------------------------------------------------------------- 1 | namespace util/type 2 | import util/type String/SanitizeForVariable 3 | # ------------------------ # 4 | 5 | Type::DefineProperty() { 6 | local visibility="$1" 7 | local class="$2" 8 | local type="$3" 9 | local property="$4" 10 | local assignment="$5" 11 | local defaultValue="$6" 12 | 13 | class="${class//[^a-zA-Z0-9]/_}" 14 | 15 | eval "__${class}_property_names+=( '$property' )" 16 | eval "__${class}_property_types+=( '$type' )" 17 | eval "__${class}_property_visibilities+=( '$visibility' )" 18 | # if [[ "$assignment" == '=' && ! -z "$defaultValue" ]] 19 | # then 20 | eval "__${class}_property_defaults+=( \"\$defaultValue\" )" 21 | # fi 22 | } 23 | 24 | private() { 25 | # ${FUNCNAME[1]} contains the name of the class 26 | local class=${FUNCNAME[1]#*:} 27 | 28 | Type::DefineProperty private $class "$@" 29 | } 30 | 31 | public() { 32 | # ${FUNCNAME[1]} contains the name of the class 33 | local class=${FUNCNAME[1]#*:} 34 | 35 | Type::DefineProperty public $class "$@" 36 | } 37 | 38 | Type::Initialize() { 39 | local name="$1" 40 | local style="${2:-default}" 41 | 42 | Function::Exists class:$name && class:$name || true 43 | 44 | Type::ConvertAllOfTypeToMethodsIfNeeded "$name" 45 | 46 | case "$style" in 47 | 'primitive') ;; 48 | 'static') 49 | declare -Ag __oo_static_instance_${name}="$(Type::Construct $name)" 50 | eval "${name}"'(){ '"Type::Handle __oo_static_instance_${name}"' "$@"; }' 51 | ;; 52 | *) 53 | ## add alias for parameters 54 | alias [$name]="_type=$name Variable::TrapAssign local -A" 55 | 56 | ## add alias for creating vars 57 | alias $name="_type=$name Type::TrapAssign declare -A" 58 | ;; 59 | esac 60 | } 61 | 62 | Type::InitializeStatic() { 63 | local name="$1" 64 | 65 | Type::Initialize "$name" static 66 | } 67 | 68 | Type::Construct() { 69 | local type="$1" 70 | local typeSanitized=$(String::SanitizeForVariableName $type) 71 | local assignToVariable="$2" 72 | 73 | if [[ ! -z "${__constructor_recursion+x}" ]] 74 | then 75 | __constructor_recursion=$(( ${__constructor_recursion} + 1 )) 76 | fi 77 | 78 | local -A constructedType=( [__object_type]="$type" ) 79 | # else 80 | # echo "$assignToVariable[__object_type]=\"$type\"" 81 | # fi 82 | 83 | if Variable::Exists "__${typeSanitized}_property_names" 84 | then 85 | local propertyIndexesIndirect="__${typeSanitized}_property_names[@]" 86 | local -i propertyIndex=0 87 | local propertyName 88 | for propertyName in "${!propertyIndexesIndirect}" 89 | do 90 | # local propertyNameIndirect=__${typeSanitized}_property_names[$propertyIndex] 91 | # local propertyName="${!propertyNameIndirect}" 92 | 93 | local propertyTypeIndirect=__${typeSanitized}_property_types[$propertyIndex] 94 | local propertyType="${!propertyTypeIndirect}" 95 | 96 | local defaultValueIndirect=__${typeSanitized}_property_defaults[$propertyIndex] 97 | local defaultValue="${!defaultValueIndirect}" 98 | 99 | if [[ $propertyType == 'boolean' ]] && [[ "$defaultValue" == 'false' || "$defaultValue" == 'true' ]] 100 | then 101 | defaultValue="${__primitive_extension_fingerprint__boolean}:$defaultValue" 102 | fi 103 | 104 | local constructedPropertyDefinition="$defaultValue" 105 | 106 | DEBUG Log "iterating type: ${typeSanitized}, property: [$propertyIndex] $propertyName = $defaultValue" 107 | 108 | ## AUTOMATICALLY CONSTRUCTS THE PROPERTIES: 109 | # case "$propertyType" in 110 | # 'array'|'map'|'string'|'integer'|'integerArray') ;; 111 | # # 'integer') constructedPropertyDefinition="${__integer_fingerprint}$defaultValue" ;; 112 | # # 'integerArray') constructedPropertyDefinition="${__integer_array_fingerprint}$defaultValue" ;; 113 | # * ) 114 | # if [[ -z "$defaultValue" && "$__constructor_recursion" -lt 15 ]] 115 | # then 116 | # constructedPropertyDefinition=$(Type::Construct "$propertyType") 117 | # fi 118 | # ;; 119 | # esac 120 | 121 | if [[ ! -z "$constructedPropertyDefinition" ]] 122 | then 123 | ## initialize non-empty fields 124 | 125 | DEBUG Log "Will exec: constructedType+=( [\"$propertyName\"]=\"$constructedPropertyDefinition\" )" 126 | constructedType+=( ["$propertyName"]="$constructedPropertyDefinition" ) 127 | # eval 'constructedType+=( ["$propertyName"]="$constructedPropertyDefinition" )' 128 | fi 129 | 130 | propertyIndex+=1 131 | done 132 | fi 133 | 134 | if [[ -z "$assignToVariable" ]] 135 | then 136 | Variable::PrintDeclaration constructedType 137 | else 138 | local constructedIndex 139 | for constructedIndex in "${!constructedType[@]}" 140 | do 141 | eval "$assignToVariable[\"\$constructedIndex\"]=\"\${constructedType[\"\$constructedIndex\"]}\"" 142 | done 143 | fi 144 | } 145 | 146 | alias new='Type::Construct' 147 | -------------------------------------------------------------------------------- /lib/util/command.sh: -------------------------------------------------------------------------------- 1 | # no dependencies 2 | 3 | Command::GetType() { 4 | local name="$1" 5 | local typeMatch=$(type -t "$name" 2> /dev/null || true) 6 | echo "$typeMatch" 7 | } 8 | 9 | Command::Exists(){ 10 | local name="$1" 11 | local typeMatch=$(Command::GetType "$name") 12 | [[ "$typeMatch" == "alias" || "$typeMatch" == "function" || "$typeMatch" == "builtin" ]] 13 | } 14 | 15 | Alias::Exists(){ 16 | local name="$1" 17 | local typeMatch=$(Command::GetType "$name") 18 | [[ "$typeMatch" == "alias" ]] 19 | } 20 | 21 | Function::Exists(){ 22 | local name="$1" 23 | declare -f "$name" &> /dev/null 24 | } 25 | 26 | Function::GetAllStartingWith() { 27 | local startsWith="$1" 28 | compgen -A 'function' "$startsWith" || true 29 | } 30 | 31 | Function::InjectCode() { 32 | local functionName="$1" 33 | local injectBefore="$2" 34 | local injectAfter="$3" 35 | local body=$(declare -f "$functionName") 36 | body="${body#*{}" # trim start 37 | body="${body%\}}" # trim end 38 | local enter=$'\n' 39 | eval "${functionName}() { ${enter}${injectBefore}${body}${injectAfter}${enter} }" 40 | } 41 | -------------------------------------------------------------------------------- /lib/util/exception.sh: -------------------------------------------------------------------------------- 1 | namespace util/exception 2 | import String/GetSpaces String/SlashReplacement UI/Color UI/Console 3 | 4 | ######################### 5 | ### HANDLE EXCEPTIONS ### 6 | ######################### 7 | 8 | trap "__EXCEPTION_TYPE__=\"\$_\" command_not_found_handle \$? \$BASH_COMMAND" ERR 9 | set -o errtrace # trace ERR through 'time command' and other functions 10 | 11 | # unalias throw 2> /dev/null || true 12 | unset -f throw 2> /dev/null || true 13 | alias throw="__EXCEPTION_TYPE__=\${e:-Manually invoked} command_not_found_handle" 14 | 15 | Exception::CustomCommandHandler() { 16 | ## this method can be overridden to create a custom, unknown command handler 17 | return 1 18 | } 19 | 20 | Exception::FillExceptionWithTraceElements() { 21 | local IFS=$'\n' 22 | for traceElement in $(Exception::DumpBacktrace ${skipBacktraceCount:-3}) 23 | do 24 | exception+=( "$traceElement" ) 25 | done 26 | } 27 | 28 | command_not_found_handle() { 29 | # USE DEFAULT IFS IN CASE IT WAS CHANGED 30 | local IFS=$' \t\n' 31 | 32 | # ignore the error from the catch subshell itself 33 | if [[ "$*" = '( set -'*'; true'* ]] ## TODO: refine with a regex and test 34 | then 35 | return 0 36 | fi 37 | 38 | Exception::CustomCommandHandler "$@" && return 0 || true 39 | 40 | local exit_code="${1}" 41 | shift || true # there might have not been any parameter, in which case "shift" would fail 42 | local script="${BASH_SOURCE[1]#./}" 43 | local lineNo="${BASH_LINENO[0]}" 44 | local undefinedObject="$*" 45 | local type="${__EXCEPTION_TYPE__:-"Undefined command"}" 46 | 47 | if [[ "$undefinedObject" == "("*")" ]] 48 | then 49 | type="Subshell returned a non-zero value" 50 | fi 51 | 52 | if [[ -z "$undefinedObject" ]] 53 | then 54 | undefinedObject="$type" 55 | fi 56 | 57 | if [[ $__oo__insideTryCatch -gt 0 ]] 58 | then 59 | subject=level3 Log "inside Try No.: $__oo__insideTryCatch" 60 | 61 | if [[ ! -s $__oo__storedExceptionLineFile ]]; then 62 | echo "$lineNo" > $__oo__storedExceptionLineFile 63 | fi 64 | if [[ ! -s $__oo__storedExceptionFile ]]; then 65 | echo "$undefinedObject" > $__oo__storedExceptionFile 66 | fi 67 | if [[ ! -s $__oo__storedExceptionSourceFile ]]; then 68 | echo "$script" > $__oo__storedExceptionSourceFile 69 | fi 70 | if [[ ! -s $__oo__storedExceptionBacktraceFile ]]; then 71 | Exception::DumpBacktrace 2 > $__oo__storedExceptionBacktraceFile 72 | fi 73 | 74 | return 1 # needs to be return 1 75 | fi 76 | 77 | if [[ $BASH_SUBSHELL -ge 25 ]] ## TODO: configurable 78 | then 79 | echo "ERROR: Call stack exceeded (25)." 80 | Exception::ContinueOrBreak || exit 1 81 | fi 82 | 83 | local -a exception=( "$lineNo" "$undefinedObject" "$script" ) 84 | 85 | Exception::FillExceptionWithTraceElements 86 | 87 | Console::WriteStdErr 88 | Console::WriteStdErr " $(UI.Color.Red)$(UI.Powerline.Fail) $(UI.Color.Bold)UNCAUGHT EXCEPTION: $(UI.Color.LightRed)${type} $(UI.Color.Yellow)$(UI.Color.Italics)(${exit_code})$(UI.Color.Default)" 89 | Exception::PrintException "${exception[@]}" 90 | 91 | Exception::ContinueOrBreak 92 | } 93 | 94 | Exception::PrintException() { 95 | # [...rest] exception 96 | local -a exception=("$@") 97 | 98 | local -i backtraceIndentationLevel=${backtraceIndentationLevel:-0} 99 | 100 | local -i counter=0 101 | local -i backtraceNo=0 102 | 103 | local -a backtraceLine 104 | local -a backtraceCommand 105 | local -a backtraceFile 106 | 107 | #for traceElement in Exception::GetLastException 108 | while [[ $counter -lt ${#exception[@]} ]] 109 | do 110 | backtraceLine[$backtraceNo]="${exception[$counter]}" 111 | counter+=1 112 | backtraceCommand[$backtraceNo]="${exception[$counter]}" 113 | counter+=1 114 | backtraceFile[$backtraceNo]="${exception[$counter]}" 115 | counter+=1 116 | 117 | backtraceNo+=1 118 | done 119 | 120 | local -i index=1 121 | 122 | while [[ $index -lt $backtraceNo ]] 123 | do 124 | Console::WriteStdErr "$(Exception::FormatExceptionSegment "${backtraceFile[$index]}" "${backtraceLine[$index]}" "${backtraceCommand[($index - 1)]}" $(( $index + $backtraceIndentationLevel )) )" 125 | index+=1 126 | done 127 | } 128 | 129 | Exception::CanHighlight() { 130 | # [string] errLine 131 | # [string] stringToMark 132 | local errLine="$1" 133 | local stringToMark="$2" 134 | 135 | local stringToMarkWithoutSlash="$(String::ReplaceSlashes "$stringToMark")" 136 | errLine="$(String::ReplaceSlashes "$errLine")" 137 | 138 | if [[ "$errLine" == *"$stringToMarkWithoutSlash"* ]] 139 | then 140 | return 0 141 | else 142 | return 1 143 | fi 144 | } 145 | 146 | Exception::HighlightPart() { 147 | # [string] errLine 148 | # [string] stringToMark 149 | local errLine="$1" 150 | local stringToMark="$2" 151 | 152 | # Workaround for a Bash bug that causes string replacement to fail when a \ is in the string 153 | local stringToMarkWithoutSlash="$(String::ReplaceSlashes "$stringToMark")" 154 | errLine="$(String::ReplaceSlashes "$errLine")" 155 | 156 | local underlinedObject="$(Exception::GetUnderlinedPart "$stringToMark")" 157 | local underlinedObjectInLine="${errLine/$stringToMarkWithoutSlash/$underlinedObject}" 158 | 159 | # Bring back the slash: 160 | underlinedObjectInLine="$(String::RestoreSlashes "$underlinedObjectInLine")" 161 | 162 | # Trimming: 163 | underlinedObjectInLine="${underlinedObjectInLine#"${underlinedObjectInLine%%[![:space:]]*}"}" # " 164 | 165 | echo "$underlinedObjectInLine" 166 | } 167 | 168 | Exception::GetUnderlinedPart() { 169 | # [string] stringToMark 170 | local stringToMark="$1" 171 | 172 | echo "$(UI.Color.LightGreen)$(UI.Powerline.RefersTo) $(UI.Color.Magenta)$(UI.Color.Underline)$stringToMark$(UI.Color.White)$(UI.Color.NoUnderline)" 173 | } 174 | 175 | Exception::FormatExceptionSegment() { 176 | local script="$1" 177 | local -i lineNo="$2" 178 | local stringToMark="$3" 179 | local -i callPosition="${4:-1}" 180 | # [string] script 181 | # [integer] lineNo 182 | # [string] stringToMark 183 | # [integer] callPosition=1 184 | 185 | local errLine="$(sed "${lineNo}q;d" "$script")" 186 | local originalErrLine="$errLine" 187 | 188 | local -i linesTried=0 189 | 190 | ## TODO: when line ends with slash \ it is a multiline statement 191 | ## TODO: when eval or alias 192 | # In case it's a multiline eval, sometimes bash gives a line that's offset by a few 193 | while [[ $linesTried -lt 5 && $lineNo -gt 0 ]] && ! Exception::CanHighlight "$errLine" "$stringToMark" 194 | do 195 | linesTried+=1 196 | lineNo+=-1 197 | errLine="$(sed "${lineNo}q;d" "$script")" 198 | done 199 | 200 | # Cut out the path, leave the script name 201 | script="${script##*/}" 202 | 203 | local prefix=" $(UI.Powerline.Branch)$(String::GetSpaces $(($callPosition * 3 - 3)) || true) " 204 | 205 | if [[ $linesTried -ge 5 ]] 206 | then 207 | # PRINT THE ORGINAL OBJECT AND ORIGINAL LINE # 208 | #local underlinedObject="$(Exception::HighlightPart "$errLine" "$stringToMark")" 209 | local underlinedObject="$(Exception::GetUnderlinedPart "$stringToMark")" 210 | echo "${prefix}$(UI.Color.White)${underlinedObject}$(UI.Color.Default) [$(UI.Color.Blue)${script}:${lineNo}$(UI.Color.Default)]" 211 | prefix="$prefix$(UI.Powerline.Fail) " 212 | errLine="$originalErrLine" 213 | fi 214 | 215 | local underlinedObjectInLine="$(Exception::HighlightPart "$errLine" "$stringToMark")" 216 | 217 | echo "${prefix}$(UI.Color.White)${underlinedObjectInLine}$(UI.Color.Default) [$(UI.Color.Blue)${script}:${lineNo}$(UI.Color.Default)]" 218 | } 219 | 220 | Exception::ContinueOrBreak() ( 221 | ## TODO: Exceptions that happen in commands that are piped to others do not HALT the execution 222 | ## TODO: Add a workaround for this ^ 223 | ## probably it's enough to -pipefail, check for a pipe in command_not_found - and if yes - return 1 224 | 225 | # if in a terminal 226 | if [ -t 0 ] 227 | then 228 | trap "stty sane; exit 1" INT 229 | Console::WriteStdErr 230 | Console::WriteStdErr " $(UI.Color.Yellow)$(UI.Powerline.Lightning)$(UI.Color.White) Press $(UI.Color.Bold)[CTRL+C]$(UI.Color.White) to exit or $(UI.Color.Bold)[Return]$(UI.Color.White) to continue execution." 231 | read -s 232 | Console::WriteStdErr "$(UI.Color.Blue)$(UI.Powerline.Cog)$(UI.Color.White) Continuing...$(UI.Color.Default)" 233 | return 0 234 | else 235 | Console::WriteStdErr 236 | exit 1 237 | fi 238 | ) 239 | 240 | Exception::DumpBacktrace() { 241 | local -i startFrom="${1:-1}" 242 | # [integer] startFrom=1 243 | # inspired by: http://stackoverflow.com/questions/64786/error-handling-in-bash 244 | 245 | # USE DEFAULT IFS IN CASE IT WAS CHANGED 246 | local IFS=$' \t\n' 247 | 248 | local -i i=0 249 | 250 | while caller $i > /dev/null 251 | do 252 | if (( $i + 1 >= $startFrom )) 253 | then 254 | local -a trace=( $(caller $i) ) 255 | 256 | echo "${trace[0]}" 257 | echo "${trace[1]}" 258 | echo "${trace[@]:2}" 259 | fi 260 | i+=1 261 | done 262 | } 263 | -------------------------------------------------------------------------------- /lib/util/exits.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # exits 4 | # 5 | # Those values are come from /usr/include/sysexits.h 6 | # 7 | 8 | # successful termination 9 | Util_ExitCode_OK=0 10 | Util_ExitCode_USAGE=64 # command line usage error 11 | Util_ExitCode_DATAERR=65 # data format error 12 | Util_ExitCode_NOINPUT=66 # cannot open input 13 | Util_ExitCode_NOUSER=67 # addressee unknown 14 | Util_ExitCode_NOHOST=68 # host name unknown 15 | Util_ExitCode_UNAVAILABLE=69 # service unavailable 16 | Util_ExitCode_SOFTWARE=70 # internal software error 17 | Util_ExitCode_OSERR=71 # system error (e.g., can't fork) 18 | Util_ExitCode_OSFILE=72 # critical OS file missing 19 | Util_ExitCode_CANTCREAT=73 # can't create (user) output file 20 | Util_ExitCode_IOERR=74 # input/output error 21 | Util_ExitCode_TEMPFAIL=75 # temp failure; user is invited to retry 22 | Util_ExitCode_PROTOCOL=76 # remote error in protocol 23 | Util_ExitCode_NOPERM=77 # permission denied 24 | Util_ExitCode_CONFIG=78 # configuration error 25 | -------------------------------------------------------------------------------- /lib/util/log.sh: -------------------------------------------------------------------------------- 1 | import util/bash4 2 | import UI/Color UI/Console 3 | 4 | declare -Ag __oo__logScopes 5 | declare -Ag __oo__logScopeOutputs 6 | declare -Ag __oo__logDisabledFilter 7 | declare -Ag __oo__loggers 8 | 9 | Log::NameScope() { 10 | local scopeName="$1" 11 | local script="${BASH_SOURCE[1]}" 12 | __oo__logScopes["$script"]="$scopeName" 13 | } 14 | 15 | Log::AddOutput() { 16 | local scopeName="$1" 17 | local outputType="${2:-STDERR}" 18 | __oo__logScopeOutputs["$scopeName"]+="$outputType;" 19 | } 20 | 21 | Log::ResetOutputsAndFilters() { 22 | local scopeName="$1" 23 | unset __oo__logScopeOutputs["$scopeName"] 24 | unset __oo__logDisabledFilter["$scopeName"] 25 | } 26 | 27 | Log::ResetAllOutputsAndFilters() { 28 | unset __oo__logScopeOutputs 29 | unset __oo__logDisabledFilter 30 | declare -Ag __oo__logScopeOutputs 31 | declare -Ag __oo__logDisabledFilter 32 | } 33 | 34 | Log::DisableFilter() { 35 | __oo__logDisabledFilter["$1"]=true 36 | } 37 | 38 | Log() { 39 | local callingFunction="${FUNCNAME[1]}" 40 | local callingScript="${BASH_SOURCE[1]}" 41 | local scope 42 | if [[ ! -z "${__oo__logScopes["$callingScript"]}" ]] 43 | then 44 | scope="${__oo__logScopes["$callingScript"]}" 45 | else # just the filename without extension 46 | scope="${callingScript##*/}" 47 | scope="${scope%.*}" 48 | fi 49 | local loggerList 50 | local loggers 51 | local logger 52 | local logged 53 | 54 | if [[ ! -z "$subject" ]] 55 | then 56 | if [[ ! -z "${__oo__logScopeOutputs["$scope/$callingFunction/$subject"]}" ]] 57 | then 58 | loggerList="${__oo__logScopeOutputs["$scope/$callingFunction/$subject"]}" 59 | elif [[ ! -z "${__oo__logScopeOutputs["$scope/$subject"]}" ]] 60 | then 61 | loggerList="${__oo__logScopeOutputs["$scope/$subject"]}" 62 | elif [[ ! -z "${__oo__logScopeOutputs["$subject"]}" ]] 63 | then 64 | loggerList="${__oo__logScopeOutputs["$subject"]}" 65 | fi 66 | 67 | loggers=( ${loggerList//;/ } ) 68 | for logger in "${loggers[@]}" 69 | do 70 | subject="${subject:-LOG}" Log::Using "$logger" "$@" 71 | logged=true 72 | done 73 | fi 74 | 75 | if [[ ! -z "${__oo__logScopeOutputs["$scope/$callingFunction"]}" ]] 76 | then 77 | if [[ -z $logged ]] || [[ ${__oo__logDisabledFilter["$scope/$callingFunction"]} == true || ${__oo__logDisabledFilter["$scope"]} == true ]] 78 | then 79 | loggerList="${__oo__logScopeOutputs["$scope/$callingFunction"]}" 80 | loggers=( ${loggerList//;/ } ) 81 | for logger in "${loggers[@]}" 82 | do 83 | subject="${subject:-LOG}" Log::Using "$logger" "$@" 84 | logged=true 85 | done 86 | fi 87 | fi 88 | 89 | if [[ ! -z "${__oo__logScopeOutputs["$scope"]}" ]] 90 | then 91 | if [[ -z $logged ]] || [[ ${__oo__logDisabledFilter["$scope"]} == true ]] 92 | then 93 | loggerList="${__oo__logScopeOutputs["$scope"]}" 94 | loggers=( ${loggerList//;/ } ) 95 | for logger in "${loggers[@]}" 96 | do 97 | subject="${subject:-LOG}" Log::Using "$logger" "$@" 98 | done 99 | fi 100 | fi 101 | } 102 | 103 | Log::RegisterLogger() { 104 | local logger="$1" 105 | local method="$2" 106 | __oo__loggers["$logger"]="$method" 107 | } 108 | 109 | Log::Using() { 110 | local logger="$1" 111 | shift 112 | if [[ ! -z ${__oo__loggers["$logger"]} ]] 113 | then 114 | ${__oo__loggers["$logger"]} "$@" 115 | fi 116 | } 117 | 118 | Logger::DEBUG() { 119 | Console::WriteStdErrAnnotated "${BASH_SOURCE[3]##*/}" ${BASH_LINENO[2]} $(UI.Color.Yellow) DEBUG "$@" 120 | } 121 | Logger::ERROR() { 122 | Console::WriteStdErrAnnotated "${BASH_SOURCE[3]##*/}" ${BASH_LINENO[2]} $(UI.Color.Red) ERROR "$@" 123 | } 124 | Logger::INFO() { 125 | Console::WriteStdErrAnnotated "${BASH_SOURCE[3]##*/}" ${BASH_LINENO[2]} $(UI.Color.Blue) INFO "$@" 126 | } 127 | Logger::WARN() { 128 | Console::WriteStdErrAnnotated "${BASH_SOURCE[3]##*/}" ${BASH_LINENO[2]} $(UI.Color.Yellow) WARN "$@" 129 | } 130 | Logger::CUSTOM() { 131 | Console::WriteStdErr "$(UI.Color.Yellow)[${subject^^}] $(UI.Color.Default)$* " 132 | } 133 | Logger::DETAILED() { 134 | Console::WriteStdErrAnnotated "${BASH_SOURCE[3]##*/}" ${BASH_LINENO[2]} $(UI.Color.Yellow) "${subject^^}" "$@" 135 | } 136 | 137 | Log::RegisterLogger STDERR Console::WriteStdErr 138 | Log::RegisterLogger DEBUG Logger::DEBUG 139 | Log::RegisterLogger ERROR Logger::ERROR 140 | Log::RegisterLogger INFO Logger::INFO 141 | Log::RegisterLogger WARN Logger::WARN 142 | Log::RegisterLogger CUSTOM Logger::CUSTOM 143 | Log::RegisterLogger DETAILED Logger::DETAILED 144 | 145 | alias namespace="Log::NameScope" 146 | namespace oo/log 147 | -------------------------------------------------------------------------------- /lib/util/namedParameters.sh: -------------------------------------------------------------------------------- 1 | namespace oo/type 2 | import util/variable 3 | 4 | # depends on modules: variable, exception 5 | 6 | declare -g ref=D10F7FB728364261BB50A7E818D537C4 7 | declare -g var=A04FB7D7594E479B8CD8D90C5014E37A 8 | 9 | # TODO: required parameters 10 | Variable::TrapAssignNumberedParameter() { 11 | # USE DEFAULT IFS IN CASE IT WAS CHANGED 12 | local IFS=$' \t\n' 13 | 14 | local commandWithArgs=( $1 ) 15 | local command="${commandWithArgs[0]}" 16 | 17 | shift 18 | # Log "TRAP: ${commandWithArgs[@]}" 19 | 20 | if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* || "$command" == "_isRequired="* || "$command" == "_isReadOnly="* || "$command" == "_noHandle="* || "$command" == "_isGlobal="* ]] 21 | then 22 | return 0 23 | fi 24 | 25 | if [[ "${commandWithArgs[*]}" == "true" ]] 26 | then 27 | __assign_next=true 28 | DEBUG subject="parameters-assign" Log "Will assign next one" 29 | 30 | local nextAssignment=$(( ${__assign_paramNo:-0} + 1 )) 31 | if [[ "${!nextAssignment-}" == "$ref:"* ]] 32 | then 33 | DEBUG subject="parameters-reference" Log "next param ($nextAssignment) is an object reference" 34 | __assign_parameters="-n" 35 | ## TODO: type checking 36 | else 37 | __assign_parameters="" 38 | fi 39 | return 0 40 | fi 41 | 42 | local varDeclaration="${commandWithArgs[*]:1}" 43 | if [[ $varDeclaration == '-'* || $varDeclaration == '${__assign'* ]] 44 | then 45 | varDeclaration="${commandWithArgs[*]:2}" 46 | fi 47 | local varName="${varDeclaration%%=*}" 48 | 49 | # var value is only important if making an object later on from it 50 | local varValue="${varDeclaration#*=}" 51 | # TODO: checking for parameter existence or default value 52 | 53 | if [[ "${__assign_varType:-null}" != "null" ]] 54 | then 55 | local requiredType="$__assign_varType" ## TODO: use this information 56 | [[ $__assign_parameters == '-n' ]] && __assign_varType="reference" 57 | 58 | DEBUG subject="parameters-setting" Log "SETTING: [$__assign_varType] $__assign_varName = \$$__assign_paramNo [rq:$__assign_valueRequired]" # [val:${!__assign_paramNo}] 59 | # subject="parameters-setting" Log -- 60 | 61 | if [[ "$__assign_valueRequired" == 'true' && -z "${!__assign_paramNo+x}" ]] 62 | then 63 | e="Value is required for the parameter $__assign_varName ($__assign_paramNo) of type [$__assign_varType]" throw 64 | fi 65 | 66 | unset __assign_valueRequired __assign_valueReadOnly 67 | 68 | local indirectAccess="$__assign_paramNo" 69 | 70 | if [[ "${!indirectAccess-}" == "$var:"* ]] 71 | then 72 | local realVarName="${!indirectAccess#*$var:}" 73 | if Variable::Exists "$realVarName" 74 | then 75 | local __declaration 76 | local __declaration_type 77 | Variable::ExportDeclarationAndTypeToVariables "$realVarName" __declaration 78 | # Log realVarName "${!indirectAccess#*$var:}" type "$declaration_type vs $__assign_varType" declaration: "$__declaration" vs "$(Variable::PrintDeclaration "$realVarName")" 79 | indirectAccess=__declaration 80 | 81 | if [[ "$__declaration_type" != "$__assign_varType" && "$__assign_varType" != 'params' && "$__assign_varType" != 'rest' ]] 82 | then 83 | e="Passed in variable: ($__assign_paramNo) $__assign_varName is of different than its required type [required: $__assign_varType] [actual: $__declaration_type]" throw 84 | fi 85 | fi 86 | fi 87 | 88 | case "$__assign_varType" in 89 | 'params') 90 | # passing array: 91 | eval "__assign_arrLength=$__assign_arrLength" 92 | eval "$__assign_varName=( \"\${@:$__assign_paramNo:$__assign_arrLength}\" )" 93 | 94 | ## TODO: foreach param expand $var: indirectAccess 95 | __assign_paramNo+=$(($__assign_arrLength - 1)) 96 | unset __assign_arrLength 97 | ;; 98 | 'rest') 99 | ## TODO: foreach param expand $var: indirectAccess 100 | eval "$__assign_varName=( \"\${@:$__assign_paramNo}\" )" 101 | ;; 102 | 'boolean') 103 | DEBUG Log passed "${!indirectAccess}", default "${__assign_varValue}" 104 | local boolean_fingerprint="${__primitive_extension_fingerprint__boolean:+__primitive_extension_fingerprint__boolean:}" 105 | 106 | if [[ ! -z "${!indirectAccess-}" ]] 107 | then 108 | if [[ "${!indirectAccess}" == "${boolean_fingerprint}"* ]] 109 | then 110 | __assign_varValue="${!indirectAccess}" 111 | elif [[ "${!indirectAccess}" == 'true' || "${!indirectAccess}" == 'false' ]] 112 | then 113 | __assign_varValue="${boolean_fingerprint}${!indirectAccess}" 114 | else 115 | __assign_varValue="${boolean_fingerprint}false" 116 | fi 117 | elif [[ "${__assign_varValue}" == 'true' || "${__assign_varValue}" == 'false' ]] 118 | then 119 | __assign_varValue="${boolean_fingerprint}${__assign_varValue}" 120 | elif [[ "${__assign_varValue}" != "${boolean_fingerprint}true" && "${__assign_varValue}" != "${boolean_fingerprint}false" ]] 121 | then 122 | __assign_varValue="${boolean_fingerprint}false" 123 | fi 124 | eval "$__assign_varName=\"${__assign_varValue}\"" 125 | ;; 126 | 'string'|'integer'|'reference') 127 | if [[ "$__assign_varType" == "reference" || ! -z "${!indirectAccess-}" ]] 128 | then 129 | if [[ "${!indirectAccess}" == "$ref:"* ]] 130 | then 131 | local refVarName="${!indirectAccess#*$ref:}" 132 | eval "$__assign_varName=$refVarName" 133 | else 134 | DEBUG Log "Will eval $__assign_varName=\"\$$indirectAccess\"" 135 | # escape $indirectAccess with \" 136 | # local escapedAssignment="${!indirectAccess}" 137 | # escapedAssignment="${escapedAssignment//\"/\\\"}" 138 | # execute="$__assign_varName=\"$escapedAssignment\"" 139 | eval "$__assign_varName=\"\$$indirectAccess\"" 140 | fi 141 | 142 | # DEBUG subject="parameters-executing" Log "EXECUTING: $execute" 143 | fi 144 | ;; 145 | *) # 'array'|'map'|objects 146 | if [[ ! -z "${!indirectAccess}" ]] 147 | then 148 | eval "local -$(Variable::GetDeclarationFlagFromType '$__assign_varType') tempMap=\"\$$indirectAccess\"" 149 | local index 150 | local value 151 | 152 | ## copy the array / map item by item 153 | for index in "${!tempMap[@]}" 154 | do 155 | eval "$__assign_varName[\$index]=\"\${tempMap[\$index]}\"" 156 | done 157 | 158 | unset index value tempMap 159 | fi 160 | ;; 161 | esac 162 | 163 | unset __assign_varType 164 | unset __assign_parameters 165 | 166 | if [[ "$__assign_valueGlobal" == "true" ]]; then 167 | local declaration="$(declare -p $__assign_varName)" 168 | declaration="${declaration/declare/declare -g}" 169 | eval "$declaration" 170 | fi 171 | unset __assign_valueGlobal 172 | 173 | if [[ "$__assign_noHandle" != 'true' && ! -z ${__oo__bootstrapped+x} ]] && declare -f 'Type::CreateHandlerFunction' &> /dev/null 174 | then 175 | DEBUG Log "Will create handle for $__assign_varName" 176 | Type::CreateHandlerFunction "$__assign_varName" # 2> /dev/null || true 177 | fi 178 | fi 179 | 180 | if [[ "$command" != "local" || "${__assign_next-}" != "true" ]] 181 | then 182 | __assign_normalCodeStarted+=1 183 | 184 | DEBUG subject="parameters-nopass" Log "NOPASS ${commandWithArgs[*]}" 185 | DEBUG subject="parameters-nopass" Log "normal code count ($__assign_normalCodeStarted)" 186 | # subject="parameters-nopass" Log -- 187 | else 188 | unset __assign_next 189 | 190 | __assign_normalCodeStarted=0 191 | __assign_varName="$varName" 192 | __assign_varValue="$varValue" 193 | __assign_varType="$__capture_type" 194 | __assign_arrLength="$__capture_arrLength" 195 | __assign_valueRequired="$__capture_valueRequired" 196 | __assign_valueReadOnly="$__capture_valueReadOnly" 197 | __assign_valueGlobal="$__capture_valueGlobal" 198 | __assign_noHandle="$__capture_noHandle" 199 | 200 | DEBUG subject="parameters-pass" Log "PASS ${commandWithArgs[*]}" 201 | # subject="parameters-pass" Log -- 202 | 203 | __assign_paramNo+=1 204 | fi 205 | } 206 | 207 | Variable::InTrapCaptureParameters() { 208 | DEBUG subject="parameters" Log "Capturing Type $_type" 209 | # subject="parameters" Log -- 210 | 211 | __capture_type="$_type" 212 | __capture_arrLength="${l-'${#@}'}" 213 | __capture_valueRequired="${_isRequired-false}" 214 | __capture_valueReadOnly="${_isReadOnly-false}" 215 | __capture_valueGlobal="${_isGlobal-false}" 216 | __capture_noHandle="${_noHandle-false}" 217 | } 218 | 219 | ## ARGUMENT RESOLVERS ## 220 | 221 | # NOTE: true; true; at the end is required to workaround an edge case where TRAP doesn't behave properly 222 | alias Variable::TrapAssign='Variable::InTrapCaptureParameters; local -i __assign_normalCodeStarted=0; trap "declare -i __assign_paramNo; Variable::TrapAssignNumberedParameter \"\$BASH_COMMAND\" \"\$@\"; [[ \$__assign_normalCodeStarted -ge 2 ]] && trap - DEBUG && unset __assign_varType __assign_varName __assign_varValue __assign_paramNo __assign_valueRequired __assign_valueReadOnly __assign_valueGlobal __assign_noHandle" DEBUG; true; true; ' 223 | alias [reference]='_type=reference Variable::TrapAssign local -n' 224 | alias Variable::TrapAssignLocal='Variable::TrapAssign local ${__assign_parameters}' 225 | alias [string]="_type=string Variable::TrapAssignLocal" 226 | # alias [string]="_type=string Variable::TrapAssign local \${__assign_parameters}" 227 | alias [integer]='_type=integer Variable::TrapAssign local ${__assign_parameters:--i}' 228 | alias [array]='_type=array Variable::TrapAssign local ${__assign_parameters:--a}' 229 | alias [map]='_type=map Variable::TrapAssign local ${__assign_parameters:--A}' 230 | # TODO: alias [integerArray]='_type=array Variable::TrapAssign local ${__assign_parameters:--ai}' 231 | alias [boolean]='_type=boolean Variable::TrapAssignLocal' 232 | alias [string[]]='_type=params Variable::TrapAssignLocal' 233 | alias [string[1]]='l=1 _type=params Variable::TrapAssignLocal' 234 | alias [string[2]]='l=2 _type=params Variable::TrapAssignLocal' 235 | alias [string[3]]='l=3 _type=params Variable::TrapAssignLocal' 236 | alias [string[4]]='l=4 _type=params Variable::TrapAssignLocal' 237 | alias [string[5]]='l=5 _type=params Variable::TrapAssignLocal' 238 | alias [string[6]]='l=6 _type=params Variable::TrapAssignLocal' 239 | alias [string[7]]='l=7 _type=params Variable::TrapAssignLocal' 240 | alias [string[8]]='l=8 _type=params Variable::TrapAssignLocal' 241 | alias [string[9]]='l=9 _type=params Variable::TrapAssignLocal' 242 | alias [string[10]]='l=10 _type=params Variable::TrapAssignLocal' 243 | alias [...rest]='_type=rest Variable::TrapAssignLocal' 244 | alias @required='_isRequired=true' 245 | alias @handleless='_noHandle=true' 246 | alias @global='_isGlobal=true' 247 | # TODO: alias @readonly='_isReadOnly=true ' 248 | -------------------------------------------------------------------------------- /lib/util/pipe.sh: -------------------------------------------------------------------------------- 1 | # no dependencies 2 | 3 | Pipe::Capture() { 4 | read -r -d '' $1 || true 5 | } 6 | 7 | Pipe::CaptureFaithful() { 8 | IFS= read -r -d '' $1 || true 9 | } 10 | -------------------------------------------------------------------------------- /lib/util/test.sh: -------------------------------------------------------------------------------- 1 | import util/class util/tryCatch UI/Cursor 2 | 3 | class:Test() { 4 | private UI.Cursor onStartCursor 5 | private string groupName 6 | public string errors 7 | # public boolean errors = false 8 | 9 | Test.Start() { 10 | [string] verb 11 | [string] description 12 | 13 | this onStartCursor capture 14 | echo "$(UI.Color.Yellow)$(UI.Powerline.PointingArrow) $(UI.Color.Yellow)[$(UI.Color.LightGray)$(UI.Color.Bold)TEST$(UI.Color.NoBold)$(UI.Color.Yellow)] $(UI.Color.White)${verb} ${description}$(UI.Color.Default)" 15 | @return 16 | } 17 | 18 | Test.OK() { 19 | [string] printInPlace=true 20 | 21 | [[ $printInPlace == true ]] && this onStartCursor restore 22 | 23 | echo "$(UI.Color.Green)$(UI.Powerline.OK) $(UI.Color.Yellow)[ $(UI.Color.Green)$(UI.Color.Bold)OK$(UI.Color.NoBold) $(UI.Color.Yellow)]$(UI.Color.Default)" 24 | @return 25 | } 26 | 27 | Test.EchoedOK() { 28 | this OK false 29 | } 30 | 31 | Test.Fail() { 32 | [string] line 33 | [string] error 34 | [string] source 35 | echo "$(UI.Color.Red)$(UI.Powerline.Fail) $(UI.Color.Yellow)[$(UI.Color.Red)$(UI.Color.Bold)FAIL$(UI.Color.NoBold)$(UI.Color.Yellow)]$(UI.Color.Default) in $(UI.Color.Yellow)${source}$(UI.Color.Default):$(UI.Color.Blue)${line}$(UI.Color.Default) $(UI.Powerline.RefersTo) $(UI.Color.Red)${error}$(UI.Color.Default)" 36 | @return 37 | } 38 | 39 | Test.DisplaySummary() { 40 | if [[ $(this errors) == true ]] 41 | # if this errors 42 | then 43 | echo "$(UI.Powerline.ArrowLeft) $(UI.Color.Magenta)Completed [$(UI.Color.White)$(this groupName)$(UI.Color.Magenta)]: $(UI.Color.Default)$(UI.Color.Red)There were errors $(UI.Color.Default)$(UI.Powerline.Lightning)" 44 | this errors = false 45 | else 46 | echo "$(UI.Powerline.ArrowLeft) $(UI.Color.Magenta)Completed [$(UI.Color.White)$(this groupName)$(UI.Color.Magenta)]: $(UI.Color.Default)$(UI.Color.Yellow)Test group completed successfully $(UI.Color.Default)$(UI.Powerline.ThumbsUp)" 47 | fi 48 | @return 49 | } 50 | 51 | Test.NewGroup() { 52 | [string] groupName 53 | 54 | echo "$(UI.Powerline.ArrowRight)" $(UI.Color.Magenta)Testing [$(UI.Color.White)${groupName}$(UI.Color.Magenta)]: $(UI.Color.Default) 55 | 56 | this groupName = "$groupName" 57 | 58 | @return 59 | } 60 | } 61 | 62 | Type::InitializeStatic Test 63 | 64 | ### TODO: special case for static classes 65 | ### for storage use a generated variable name (hash of class name?) 66 | ### for execution use class' name, e.g. Test Start 67 | 68 | alias describe='Test NewGroup' 69 | alias summary='Test DisplaySummary' 70 | alias caught="echo \"CAUGHT: $(UI.Color.Red)\$__BACKTRACE_COMMAND__$(UI.Color.Default) in \$__BACKTRACE_SOURCE__:\$__BACKTRACE_LINE__\"" 71 | alias it="Test Start it" 72 | alias expectPass="Test OK; catch { Test errors = true; Test Fail \"\${__EXCEPTION__[@]}\"; }" 73 | alias expectOutputPass="Test EchoedOK; catch { Test errors = true; Test Fail; }" 74 | alias expectFail='catch { caught; Test EchoedOK; }; test $? -eq 1 && Test errors = false; ' 75 | -------------------------------------------------------------------------------- /lib/util/tryCatch.sh: -------------------------------------------------------------------------------- 1 | # no dependencies 2 | declare -ig __oo__insideTryCatch=0 3 | declare -g __oo__presetShellOpts="$-" 4 | 5 | # in case try-catch is nested, we set +e before so the parent handler doesn't catch us instead 6 | alias try='[[ $__oo__insideTryCatch -eq 0 ]] || __oo__presetShellOpts="$(echo $- | sed 's/[is]//g')"; __oo__insideTryCatch+=1; set +e; ( set -e; true; ' 7 | alias catch='); declare __oo__tryResult=$?; __oo__insideTryCatch+=-1; [[ $__oo__insideTryCatch -lt 1 ]] || set -${__oo__presetShellOpts:-e} && Exception::Extract $__oo__tryResult || ' 8 | 9 | Exception::SetupTemp() { 10 | declare -g __oo__storedExceptionLineFile="$(mktemp -t stored_exception_line.$$.XXXXXXXXXX)" 11 | declare -g __oo__storedExceptionSourceFile="$(mktemp -t stored_exception_source.$$.XXXXXXXXXX)" 12 | declare -g __oo__storedExceptionBacktraceFile="$(mktemp -t stored_exception_backtrace.$$.XXXXXXXXXX)" 13 | declare -g __oo__storedExceptionFile="$(mktemp -t stored_exception.$$.XXXXXXXXXX)" 14 | } 15 | 16 | Exception::CleanUp() { 17 | local exitVal=$? 18 | rm -f $__oo__storedExceptionLineFile $__oo__storedExceptionSourceFile $__oo__storedExceptionBacktraceFile $__oo__storedExceptionFile || exit 1 19 | exit $exitVal 20 | } 21 | 22 | Exception::ResetStore() { 23 | > $__oo__storedExceptionLineFile 24 | > $__oo__storedExceptionFile 25 | > $__oo__storedExceptionSourceFile 26 | > $__oo__storedExceptionBacktraceFile 27 | } 28 | 29 | Exception::GetLastException() { 30 | if [[ -s $__oo__storedExceptionFile ]] 31 | then 32 | cat $__oo__storedExceptionLineFile 33 | cat $__oo__storedExceptionFile 34 | cat $__oo__storedExceptionSourceFile 35 | cat $__oo__storedExceptionBacktraceFile 36 | 37 | Exception::ResetStore 38 | else 39 | echo -e "${BASH_LINENO[1]}\n \n${BASH_SOURCE[2]#./}" 40 | fi 41 | } 42 | 43 | Exception::Extract() { 44 | local retVal=$1 45 | unset __oo__tryResult 46 | 47 | if [[ $retVal -gt 0 ]] 48 | then 49 | local IFS=$'\n' 50 | __EXCEPTION__=( $(Exception::GetLastException) ) 51 | 52 | local -i counter=0 53 | local -i backtraceNo=0 54 | 55 | while [[ $counter -lt ${#__EXCEPTION__[@]} ]] 56 | do 57 | __BACKTRACE_LINE__[$backtraceNo]="${__EXCEPTION__[$counter]}" 58 | counter+=1 59 | __BACKTRACE_COMMAND__[$backtraceNo]="${__EXCEPTION__[$counter]}" 60 | counter+=1 61 | __BACKTRACE_SOURCE__[$backtraceNo]="${__EXCEPTION__[$counter]}" 62 | counter+=1 63 | backtraceNo+=1 64 | done 65 | 66 | return 1 # so that we may continue with a "catch" 67 | fi 68 | return 0 69 | } 70 | 71 | Exception::SetupTemp 72 | trap Exception::CleanUp EXIT INT TERM 73 | -------------------------------------------------------------------------------- /lib/util/variable.sh: -------------------------------------------------------------------------------- 1 | import util/command 2 | namespace util/variable 3 | 4 | declare __declaration_type ## for Variable::ExportDeclarationAndTypeToVariables (?) 5 | 6 | Variable::Exists() { 7 | local variableName="$1" 8 | declare -p "$variableName" &> /dev/null 9 | } 10 | 11 | Variable::GetAllStartingWith() { 12 | local startsWith="$1" 13 | compgen -A 'variable' "$startsWith" || true 14 | } 15 | 16 | Variable::GetDeclarationFlagFromType() { 17 | DEBUG subject="GetParamFromType" Log 'getting param from type' "$@" 18 | 19 | local typeInfo="$1" 20 | local fallback="$2" 21 | 22 | if [[ "$typeInfo" == "map" ]] || Function::Exists "class:${typeInfo}" 23 | then 24 | echo A 25 | else 26 | case "$typeInfo" in 27 | "reference") 28 | echo n 29 | ;; 30 | "array") 31 | echo a 32 | ;; 33 | "string" | "boolean") 34 | echo - 35 | ;; 36 | "integer") 37 | echo i 38 | ;; 39 | "integerArray") 40 | echo ai 41 | ;; 42 | *) 43 | echo "${fallback:-A}" 44 | ;; 45 | esac 46 | fi 47 | } 48 | 49 | Variable::GetPrimitiveTypeFromDeclarationFlag() { 50 | local typeInfo="$1" 51 | 52 | case "$typeInfo" in 53 | "n"*) 54 | echo reference 55 | ;; 56 | "a"*) 57 | echo array 58 | ;; 59 | "A"*) 60 | echo map 61 | ;; 62 | "i"*) 63 | echo integer 64 | ;; 65 | "ai"*) 66 | echo integerArray 67 | ;; 68 | "Ai"*) 69 | echo integerMap 70 | ;; 71 | *) 72 | echo string 73 | ;; 74 | esac 75 | } 76 | 77 | Variable::ExportDeclarationAndTypeToVariables() { 78 | local variableName="$1" 79 | local targetVariable="$2" 80 | local dereferrence="${3:-true}" 81 | 82 | # TODO: rename for a safer, less common variablename so parents can output to declaration 83 | local declaration 84 | local regexArray="declare -([a-zA-Z-]+) $variableName='(.*)'" 85 | local regex="declare -([a-zA-Z-]+) $variableName=\"(.*)\"" 86 | local regexArrayBash4_4="declare -([a-zA-Z-]+) $variableName=(.*)" 87 | local definition=$(declare -p $variableName 2> /dev/null || true) 88 | 89 | local escaped="'\\\'" 90 | local escapedQuotes='\\"' 91 | local singleQuote='"' 92 | 93 | local doubleSlashes='\\\\' 94 | local singleSlash='\' 95 | 96 | [[ -z "$definition" ]] && e="Variable $variableName not defined" throw 97 | 98 | if [[ "$definition" =~ $regexArray ]] 99 | then 100 | declaration="${BASH_REMATCH[2]//$escaped/}" 101 | elif [[ "$definition" =~ $regex ]] 102 | then 103 | declaration="${BASH_REMATCH[2]//$escaped/}" ## TODO: is this transformation needed? 104 | declaration="${declaration//$escapedQuotes/$singleQuote}" 105 | declaration="${declaration//$doubleSlashes/$singleSlash}" 106 | elif [[ "$definition" =~ $regexArrayBash4_4 ]] 107 | then 108 | declaration="${BASH_REMATCH[2]}" 109 | fi 110 | 111 | local variableType 112 | 113 | DEBUG Log "Variable Is $variableName = $definition ==== ${BASH_REMATCH[1]}" 114 | 115 | local primitiveType=${BASH_REMATCH[1]} 116 | 117 | local objectTypeIndirect="$variableName[__object_type]" 118 | if [[ "$primitiveType" =~ [A] && ! -z "${!objectTypeIndirect}" ]] 119 | then 120 | DEBUG Log "Object Type $variableName[__object_type] = ${!objectTypeIndirect}" 121 | variableType="${!objectTypeIndirect}" 122 | # elif [[ ! -z ${__primitive_extension_fingerprint__boolean+x} && "$primitiveType" == '-' && "${!variableName}" == "${__primitive_extension_fingerprint__boolean}"* ]] 123 | # then 124 | # variableType="boolean" 125 | else 126 | variableType="$(Variable::GetPrimitiveTypeFromDeclarationFlag "$primitiveType")" 127 | DEBUG Log "Primitive Type $primitiveType Resolved ${variableType}" 128 | fi 129 | 130 | if [[ "$variableType" == 'string' ]] && Function::Exists 'Type::GetPrimitiveExtensionFromVariable' 131 | then 132 | local extensionType=$(Type::GetPrimitiveExtensionFromVariable "${variableName}") 133 | if [[ ! -z "$extensionType" ]] 134 | then 135 | variableType="$extensionType" 136 | fi 137 | fi 138 | 139 | DEBUG Log "Variable $variableName is typeof $variableType" 140 | 141 | if [[ "$variableType" == 'reference' && "$dereferrence" == 'true' ]] 142 | then 143 | local dereferrencedVariableName="$declaration" 144 | Variable::ExportDeclarationAndTypeToVariables "$dereferrencedVariableName" "$targetVariable" "$dereferrence" 145 | else 146 | eval "$targetVariable=\"\$declaration\"" 147 | eval "${targetVariable}_type=\$variableType" 148 | fi 149 | } 150 | 151 | Variable::PrintDeclaration() { 152 | local variableName="${1}" 153 | local dereferrence="${2:-true}" 154 | 155 | local __declaration 156 | local __declaration_type 157 | Variable::ExportDeclarationAndTypeToVariables "$variableName" __declaration "$dereferrence" 158 | echo "$__declaration" 159 | } 160 | 161 | alias @get='Variable::PrintDeclaration' 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bash-oo-framework", 3 | "version": "2.1.0", 4 | "description": "Bash Infinity is a modern boilerplate ", 5 | "repo": "niieani/bash-oo-framework", 6 | "files": [ 7 | "lib/Array/Contains.sh", 8 | "lib/Array/Intersect.sh", 9 | "lib/Array/List.sh", 10 | "lib/Array/Reverse.sh", 11 | "lib/String/GetSpaces.sh", 12 | "lib/String/IsNumber.sh", 13 | "lib/String/SanitizeForVariable.sh", 14 | "lib/String/SlashReplacement.sh", 15 | "lib/String/UUID.sh", 16 | "lib/TypePrimitives/array.sh", 17 | "lib/TypePrimitives/boolean.sh", 18 | "lib/TypePrimitives/integer.sh", 19 | "lib/TypePrimitives/map.sh", 20 | "lib/TypePrimitives/string.sh", 21 | "lib/UI/Color.sh", 22 | "lib/UI/Color.var.sh", 23 | "lib/UI/Console.sh", 24 | "lib/UI/Cursor.sh", 25 | "lib/util/bash4.sh", 26 | "lib/util/class.sh", 27 | "lib/util/command.sh", 28 | "lib/util/exception.sh", 29 | "lib/util/log.sh", 30 | "lib/util/namedParameters.sh", 31 | "lib/util/pipe.sh", 32 | "lib/util/test.sh", 33 | "lib/util/tryCatch.sh", 34 | "lib/util/type.sh", 35 | "lib/util/variable.sh", 36 | "lib/oo-bootstrap.sh" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/escaping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/../lib/oo-bootstrap.sh" 4 | 5 | import util/exception util/class 6 | # import util 7 | 8 | echoedEscapes() { 9 | string escapes='hey \" dude \" \" cool \\ \\ awesome \\' 10 | array someArray=( "$escapes" ) 11 | 12 | [[ "$(var: someArray forEach 'printf %s "$escapes"')" == "$escapes" ]] 13 | 14 | # printf %s "${escapes}" 15 | # someArray forEach 'printf "$item"' 16 | # someArray forEach '>&2 printf "$item"' 17 | } 18 | 19 | strings() { 20 | string temp='this is a "string" to be \jsonized\.' 21 | $var:temp getCharCode 22 | echo 23 | $var:temp forEachChar 'printf "$char"' 24 | echo 25 | $var:temp toJSON 26 | echo 27 | } 28 | 29 | strings 30 | 31 | # echoedEscapes 32 | -------------------------------------------------------------------------------- /test/test-imports.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib/oo-bootstrap.sh" 5 | 6 | ## MAIN ## 7 | import util/log util/exception util/tryCatch util/namedParameters util/class 8 | import UI/Cursor 9 | 10 | # lalala 11 | lala() { 12 | [string] something=yo 13 | 14 | echo $something 15 | 16 | UI.Cursor cursor 17 | $var:cursor capture 18 | echo yo 19 | sleep 1 20 | $var:cursor restore 21 | echo yopa 22 | } 23 | 24 | # lala 25 | 26 | lala2() { 27 | [string] something=yo 28 | 29 | echo $something 30 | 31 | UI.Cursor cursor 32 | $var:cursor capture 33 | echo yo 34 | sleep 1 35 | $var:cursor restore 36 | echo yopa 37 | } 38 | 39 | lala2 40 | 41 | 42 | ## YOUR CODE GOES HERE ## 43 | -------------------------------------------------------------------------------- /your-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## BOOTSTRAP ## 4 | source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib/oo-bootstrap.sh" 5 | 6 | ## MAIN ## 7 | # import util/log util/exception util/tryCatch util/namedParameters util/class 8 | 9 | ## YOUR CODE GOES HERE ## 10 | --------------------------------------------------------------------------------