├── .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 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 | [](https://travis-ci.com/niieani/bash-oo-framework)
4 | [](https://cirrus-ci.com/github/niieani/bash-oo-framework)
5 | [](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 | 
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 | 
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 |
--------------------------------------------------------------------------------