├── .gitmodules ├── COPYRIGHT ├── LICENSE ├── README.md └── overdrive ├── gearbox.json ├── lib └── shellfire │ └── overdrive │ └── overdrive.functions └── overdrive /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "overdrive/etc/shellfire/paths.d"] 2 | path = overdrive/etc/shellfire/paths.d 3 | url = https://github.com/shellfire-dev/paths.d.git 4 | [submodule "overdrive/lib/shellfire/core"] 5 | path = overdrive/lib/shellfire/core 6 | url = https://github.com/shellfire-dev/core.git 7 | [submodule "overdrive/lib/shellfire/jsonreader"] 8 | path = overdrive/lib/shellfire/jsonreader 9 | url = https://github.com/shellfire-dev/jsonreader.git 10 | [submodule "overdrive/lib/shellfire/unicode"] 11 | path = overdrive/lib/shellfire/unicode 12 | url = https://github.com/shellfire-dev/unicode.git 13 | [submodule "overdrive/lib/shellfire/xmlwriter"] 14 | path = overdrive/lib/shellfire/xmlwriter 15 | url = https://github.com/shellfire-dev/xmlwriter.git 16 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Comment: Distribution Compilation Copyright and License 3 | Copyright: Copyright © 2014-2015, Raphael Cohn 4 | License: MIT 5 | The MIT License (MIT) 6 | . 7 | Copyright © 2014-2015, Raphael Cohn 8 | . 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | . 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | . 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | Files: * 28 | Copyright: Copyright © 2014-2015, Raphael Cohn 29 | License: MIT 30 | The MIT License (MIT) 31 | . 32 | Copyright © 2014-2015, Raphael Cohn 33 | . 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | . 41 | The above copyright notice and this permission notice shall be included in all 42 | copies or substantial portions of the Software. 43 | . 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 50 | SOFTWARE. 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE terms are documented in the COPYRIGHT file at the top-level directory of this distribution and at https://raw.githubusercontent.com/shellfire-dev/tutorial/master/COPYRIGHT. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A 10 min tutorial to create the [shellfire] application 'overdrive' 2 | If you'd rather not follow along, or if you'd prefer to see a complete application, take a look at the files in `overdrive`. (On a Mac with TextMate, `mate tutorial/overdrive`). 3 | 4 | 'overdrive' is intended to be a simple application that converts 'GearBox' JSON files to XML. It shows how to quickly parse command lines, validate arguments and use the JSON and XML libraries. 5 | 6 | ## Create a skeleton folder structure 7 | Create a new repository on GitHub. For this example, we'll assume you called it `overdrive` and you are `normanville`. The [shellfire] application is called `overdrive`. Now, let's create the following folder structure:- 8 | 9 | ```bash 10 | overdrive\ 11 | .git\ 12 | overdrive # your shellfire application script 13 | etc\ 14 | shellfire\ 15 | paths.d\ # git submodule add https://github.com/shellfire-dev/paths.d 16 | lib\ 17 | shellfire\ 18 | core\ # git submodule add https://github.com/shellfire-dev/core 19 | jsonreader\ # git submodule add https://github.com/shellfire-dev/jsonreader 20 | unicode\ # git submodule add https://github.com/shellfire-dev/unicode 21 | xmlwriter\ # git submodule add https://github.com/shellfire-dev/xmlwriter 22 | overdrive\ # any code for your application broken out into namespaces 23 | output\ 24 | ``` 25 | 26 | So, let's do it:- 27 | ```bash 28 | repository=overdrive 29 | user=normanville 30 | git clone "git@github.com:$user/$repository.git" 31 | cd "$repository" 32 | 33 | mkdir -p etc/shellfire 34 | cd etc/shellfire 35 | git submodule add "https://github.com/shellfire-dev/paths.d" 36 | cd - 37 | 38 | mkdir -p lib/shellfire 39 | cd lib/shellfire 40 | git submodule add "https://github.com/shellfire-dev/core" 41 | git submodule add "https://github.com/shellfire-dev/jsonreader" 42 | git submodule add "https://github.com/shellfire-dev/unicode" 43 | git submodule add "https://github.com/shellfire-dev/xmlwriter" 44 | mkdir overdrive 45 | cd - 46 | 47 | git submodule update --init 48 | 49 | touch overdrive 50 | chmod +x overdrive 51 | 52 | cd .. 53 | ``` 54 | 55 | 56 | ## Creating Hello World 57 | 58 | Now all that's left is to add some boilerplate to the `overdrive` executable. This is unfortunate, but there's nothing to be done about it. We need _something_ so [shellfire] can bootstrap. Open `overdrive` in a text editor, and paste in this boilerplate to create a 'Hello World':- 59 | 60 | ```bash 61 | #!/usr/bin/env sh 62 | 63 | _program() 64 | { 65 | overdrive() 66 | { 67 | echo "Hello World" 68 | } 69 | } 70 | 71 | _program_path_find() 72 | { 73 | if [ "${_program_fattening_program_path+set}" = 'set' ]; then 74 | printf '%s\n' "$_program_fattening_program_path" 75 | 76 | elif [ "${0%/*}" = "$0" ]; then 77 | 78 | # We've been invoked by the interpreter as, say, bash program 79 | if [ -r "$0" ]; then 80 | pwd -P 81 | # Clutching at straws; probably run via a download, anonymous script, etc, weird execve, etc 82 | else 83 | printf '\n' 84 | fi 85 | 86 | else 87 | 88 | # We've been invoked with a relative or absolute path (also when invoked via PATH in a shell) 89 | 90 | _program_path_find_parentPath() 91 | { 92 | parentPath="${scriptPath%/*}" 93 | if [ -z "$parentPath" ]; then 94 | parentPath='/' 95 | fi 96 | cd "$parentPath" 1>/dev/null 97 | } 98 | 99 | # pdksh / mksh have problems with unsetting a variable that was never set... 100 | if [ "${CDPATH+set}" = 'set' ]; then 101 | unset CDPATH 102 | fi 103 | 104 | if command -v realpath 1>/dev/null 2>/dev/null; then 105 | ( 106 | scriptPath="$(realpath "$0")" 107 | 108 | _program_path_find_parentPath 109 | pwd -P 110 | ) 111 | elif command -v readlink 1>/dev/null 2>/dev/null; then 112 | ( 113 | scriptPath="$0" 114 | 115 | while [ -L "$scriptPath" ] 116 | do 117 | _program_path_find_parentPath 118 | scriptPath="$(readlink "$scriptPath")" 119 | done 120 | 121 | _program_path_find_parentPath 122 | pwd -P 123 | ) 124 | else 125 | # This approach will fail in corner cases where the script itself is a symlink in a path not parallel with the concrete script 126 | ( 127 | scriptPath="$0" 128 | 129 | _program_path_find_parentPath 130 | pwd -P 131 | ) 132 | fi 133 | 134 | fi 135 | } 136 | 137 | _program_name='overdrive' 138 | _program_version='unversioned' 139 | _program_package_or_build='' 140 | _program_path="$(_program_path_find)" 141 | _program_libPath="${_program_path}/lib" 142 | _program_etcPath="${_program_path}/etc" 143 | _program_varPath="${_program_path}/var" 144 | _program_entrypoint='overdrive' 145 | 146 | 147 | # Assumes pwd, and so requires this code to be running from this folder 148 | . "$_program_libPath"/shellfire/core/init.functions "$@" 149 | ``` 150 | 151 | Now run it with `./overdrive` - you should see `Hello World`. Try `./overdrive --help` and `./overdrive --version`. Of course, this isn't a very useful program. Let's at least give it a purpose. 152 | 153 | ## Parsing the command line 154 | In many programs, parsing the command line is probably a large proportion of the logic. Its complex, brittle and frequently just hard work. [shellfire] aims to make it a little easier. 155 | 156 | Let's start by taking some arguments using [core]'s command line parser. We're going to modify our hello world program to one that reads JSON gear box files and writes them as XML gear box files. So it'd be useful to be able to do something like `./overdrive /path/to/gearbox.json`. 157 | 158 | Let's add the function `_program_commandLine_handleNonOptions()`. This is called back by the parser to let us handle non-options. We could use this take a list of files to work on. Let's use [shellfire] arrays:- 159 | 160 | ```bash 161 | # Place all code above the last line: 162 | # '. "$_program_libPath"/shellfire/core/init.functions "$@"' 163 | _program_commandLine_handleNonOptions() 164 | { 165 | core_variable_array_initialise overdrive_jsonGearBoxFiles 166 | 167 | local jsonGearBoxFile 168 | for jsonGearBoxFile in "$@" 169 | do 170 | core_variable_array_append overdrive_jsonGearBoxFiles "$jsonGearBoxFile" 171 | done 172 | } 173 | 174 | # Assumes pwd, and so requires this code to be running from this folder 175 | . "$_program_libPath"/shellfire/core/init.functions "$@" 176 | ``` 177 | 178 | Note, it's very important that the very last line of your program is always `. "$_program_libPath"/shellfire/core/init.functions "$@"`. It's magic. Sorry. Actually, when [fatten]ed, this line disappears - but you do want to run your code first, don't you? 179 | 180 | Is it an error to not have any files? Well it, certainly isn't useful. Let's issue a warning. 181 | 182 | ```bash 183 | # Replace _program_commandLine_handleNonOptions() with 184 | _program_commandLine_handleNonOptions() 185 | { 186 | core_variable_array_initialise overdrive_jsonGearBoxFiles 187 | 188 | local jsonGearBoxFile 189 | for jsonGearBoxFile in "$@" 190 | do 191 | core_variable_array_append overdrive_jsonGearBoxFiles "$jsonGearBoxFile" 192 | done 193 | 194 | if core_variable_array_isEmpty overdrive_jsonGearBoxFiles; then 195 | core_message WARN "You haven't specified any JSON gear box files - are you sure this is what you wanted?" 196 | fi 197 | } 198 | ``` 199 | 200 | Actually, let's make that an error after all:- 201 | 202 | ```bash 203 | # Replace _program_commandLine_handleNonOptions() with 204 | _program_commandLine_handleNonOptions() 205 | { 206 | core_variable_array_initialise overdrive_jsonGearBoxFiles 207 | 208 | local jsonGearBoxFile 209 | for jsonGearBoxFile in "$@" 210 | do 211 | core_variable_array_append overdrive_jsonGearBoxFiles "$jsonGearBoxFile" 212 | done 213 | 214 | if core_variable_array_isEmpty overdrive_jsonGearBoxFiles; then 215 | core_exitError $core_commandLine_exitCode_USAGE "You haven't specified any JSON gear box files - are you sure this is what you wanted?" 216 | fi 217 | } 218 | ``` 219 | 220 | We need somewhere to store out output. How about an option `--output-path`? Let's tell the parser what to do:- 221 | 222 | ```bash 223 | # Place this code above _program_commandLine_handleNonOptions() 224 | _program_commandLine_optionExists() 225 | { 226 | case "$optionName" in 227 | 228 | output-path) 229 | echo 'yes-argumented' 230 | ;; 231 | 232 | *) 233 | echo 'no' 234 | ;; 235 | 236 | esac 237 | } 238 | ``` 239 | 240 | Of course, we want to actually get the value of that option! In this case, the parser will call `_program_commandLine_processOptionWithArgument()`:- 241 | 242 | ```bash 243 | # Place this code below _program_commandLine_optionExists() 244 | _program_commandLine_processOptionWithArgument() 245 | { 246 | case "$optionName" in 247 | 248 | output-path) 249 | overdrive_outputPath="$optionValue" 250 | ;; 251 | 252 | esac 253 | } 254 | ``` 255 | 256 | By convention, we name variables set through command line options as `${_program_name}_lowerTitle`. Of course, it'd be nice to have a short option, `-o`, too, so let's do that:- 257 | 258 | ```bash 259 | # Replace _program_commandLine_optionExists() and _program_commandLine_processOptionWithArgument() with 260 | _program_commandLine_optionExists() 261 | { 262 | case "$optionName" in 263 | 264 | o|output-path) 265 | echo 'yes-argumented' 266 | ;; 267 | 268 | *) 269 | echo 'no' 270 | ;; 271 | 272 | esac 273 | } 274 | 275 | _program_commandLine_processOptionWithArgument() 276 | { 277 | case "$optionName" in 278 | 279 | o|output-path) 280 | overdrive_outputPath="$optionValue" 281 | ;; 282 | 283 | esac 284 | } 285 | ``` 286 | 287 | Now, we really ought to validate that output path. Do we need to create it? Possibly. Let's use one of the convenience functions in `core_validate`:- 288 | 289 | ```bash 290 | # Replace _program_commandLine_processOptionWithArgument() with 291 | _program_commandLine_processOptionWithArgument() 292 | { 293 | case "$optionName" in 294 | 295 | o|output-path) 296 | core_validate_folderPathIsReadableAndSearchableAndWritableOrCanBeCreated $core_commandLine_exitCode_USAGE 'option' "$optionNameIncludingHyphens" "$optionValue" 297 | overdrive_outputPath="$optionValue" 298 | ;; 299 | 300 | esac 301 | } 302 | ``` 303 | 304 | Now, we always need an output path. We can't know for sure until all the options have been parsed. Of course, the parser let's us manage that in `_program_commandLine_validate()`:- 305 | 306 | ```bash 307 | # Place this below _program_commandLine_handleNonOptions() 308 | _program_commandLine_validate() 309 | { 310 | if core_variable_isUnset overdrive_outputPath; then 311 | core_exitError $core_commandLine_exitCode_USAGE "Please specify --output-path" 312 | fi 313 | } 314 | ``` 315 | 316 | That's a bit tough, though. Why don't we let an administrator set a value in configuration? Configuration is automatically parsed and loaded immediately prior to command line parsing. Of course, if that's the case, we'll need to validate what they've chosen. And, in this case, just because it makes sense, we could default the output path to the current working directory, but let the user know. 317 | 318 | ```bash 319 | # Replace _program_commandLine_validate() with 320 | _program_commandLine_validate() 321 | { 322 | if core_variable_isSet overdrive_outputPath; then 323 | core_validate_folderPathIsReadableAndSearchableAndWritableOrCanBeCreated $core_commandLine_exitCode_CONFIG 'configuration setting' 'overdrive_outputPath' "$overdrive_outputPath" 324 | else 325 | core_message INFO "Defaulting --output-path to current working directory" 326 | overdrive_outputPath="$(pwd)" 327 | fi 328 | } 329 | ``` 330 | 331 | Of course, we ought to write an useful help message after all of this. Let's do that with `_program_commandLine_helpMessage()`:- 332 | 333 | ```bash 334 | # Place this above _program_commandLine_optionExists() 335 | _program_commandLine_helpMessage() 336 | { 337 | _program_commandLine_helpMessage_usage="[OPTION]... -- [JSON GEAR BOX FILE]..." 338 | _program_commandLine_helpMessage_description="Turns JSON into XML." 339 | _program_commandLine_helpMessage_options=" 340 | -o, --output-path PATH PATH to output to. 341 | Defaults to current working directory:- 342 | $(pwd)" 343 | _program_commandLine_helpMessage_optionsSpacing=' ' 344 | _program_commandLine_helpMessage_configurationKeys=" 345 | swaddle_outputPath Equivalent to --output-path 346 | " 347 | _program_commandLine_helpMessage_examples=" 348 | ${_program_name} -o /some/path -- some-json-gear-box-file.json 349 | " 350 | } 351 | ``` 352 | 353 | Let's check out our new help: `./overdrive --help`. 354 | 355 | Now, we're repeating our self with the default value for the output path - once in `_program_commandLine_helpMessage()`, once in `_program_commandLine_validate()`. It's also a dynamic value. In a normal shell script, we might put that in a global value. But because of the way [shellfire] works, that's a bad idea (as it is in most normal programs). It'll be lost when the program's fattened, as all expression outside of functions aren't preserved ordinarily. And even if it wasn't, it'd be the value on the development machine. Instead, let's use an initialisation function:- 356 | 357 | ```bash 358 | # Place this above _program_commandLine_helpMessage() 359 | _program_commandLine_parseInitialise() 360 | { 361 | overdrive_outputPath_default="$(pwd)" 362 | } 363 | 364 | # Replace _program_commandLine_helpMessage() with 365 | _program_commandLine_helpMessage() 366 | { 367 | _program_commandLine_helpMessage_usage="[OPTION]... -- [JSON GEAR BOX FILE]..." 368 | _program_commandLine_helpMessage_description="Turns JSON into XML." 369 | _program_commandLine_helpMessage_options=" 370 | -o, --output-path PATH PATH to output to. 371 | Defaults to current working directory:- 372 | $overdrive_outputPath_default" 373 | _program_commandLine_helpMessage_optionsSpacing=' ' 374 | _program_commandLine_helpMessage_configurationKeys=" 375 | swaddle_outputPath Equivalent to --output-path 376 | " 377 | _program_commandLine_helpMessage_examples=" 378 | ${_program_name} -o /some/path -- some-json-gear-box-file.json 379 | " 380 | } 381 | 382 | # Replace _program_commandLine_validate() with 383 | _program_commandLine_validate() 384 | { 385 | if core_variable_isSet overdrive_outputPath; then 386 | core_validate_folderPathIsReadableAndSearchableAndWritableOrCanBeCreated $core_commandLine_exitCode_CONFIG 'configuration setting' 'overdrive_outputPath' "$overdrive_outputPath" 387 | else 388 | core_message INFO "Defaulting --output-path to current working directory" 389 | overdrive_outputPath="$overdrive_outputPath_default" 390 | fi 391 | } 392 | ``` 393 | 394 | Let's check out our new help: `./overdrive --help`. To make use of the configuration, you could create a file at, say, `$HOME/.overdrive/rc`:- 395 | 396 | ```bash 397 | overdrive_outputPath="~/overdrive-output" 398 | ``` 399 | 400 | Now we might want to be able to force the output to overwrite files. Let's add a `--force` long option, with `-f` for short hand, with the last function the parser uses, `core_commandLine_processOptionWithoutArgument`:- 401 | 402 | ```bash 403 | # Replace _program_commandLine_optionExists() with 404 | _program_commandLine_optionExists() 405 | { 406 | case "$optionName" in 407 | 408 | o|output-path) 409 | echo 'yes-argumented' 410 | ;; 411 | 412 | f|force) 413 | echo 'yes-argumentless' 414 | ;; 415 | 416 | *) 417 | echo 'no' 418 | ;; 419 | 420 | esac 421 | } 422 | 423 | # Place this below _program_commandLine_optionExists() 424 | _program_commandLine_processOptionWithoutArgument() 425 | { 426 | case "$optionName" in 427 | 428 | f|force) 429 | overdrive_force='yes' 430 | ;; 431 | 432 | esac 433 | } 434 | 435 | # Replace _program_commandLine_validate() with 436 | _program_commandLine_validate() 437 | { 438 | if core_variable_isSet overdrive_outputPath; then 439 | core_validate_folderPathIsReadableAndSearchableAndWritableOrCanBeCreated $core_commandLine_exitCode_CONFIG 'configuration setting' 'overdrive_outputPath' "$overdrive_outputPath" 440 | else 441 | core_message INFO "Defaulting --output-path to current working directory" 442 | overdrive_outputPath="$overdrive_outputPath_default" 443 | fi 444 | 445 | if core_variable_isSet overdrive_force; then 446 | core_validate_isBoolean $core_commandLine_exitCode_CONFIG 'configuration setting' 'overdrive_force' "$overdrive_force" 447 | else 448 | overdrive_force='no' 449 | fi 450 | } 451 | ``` 452 | 453 | Of course, there's more we could do. We could validate that the JSON files in `_program_commandLine_handleNonOptions()` are extant, readable and not empty. We should document `--force`. We leave that as an exercise for you. 454 | 455 | ## Doing something useful 456 | 457 | Recall in our boilerplate we had the following at the top of our program:- 458 | 459 | ```bash 460 | _program() 461 | { 462 | overdrive() 463 | { 464 | echo "Hello World" 465 | } 466 | } 467 | ``` 468 | 469 | Let's replace that with something more useful. Let's start by importing the namespaces we need:- 470 | 471 | ```bash 472 | # Replace _program() with 473 | _program() 474 | { 475 | core_usesIn jsonreader 476 | core_usesIn xmlwriter 477 | 478 | overdrive() 479 | { 480 | echo "Hello World" 481 | } 482 | } 483 | ``` 484 | 485 | We don't import `unicode`, even though `jsonreader` depends on it - it has a `core_usesIn` line in its logic. 486 | 487 | Now, what's our program going to do? It's going to loop over each JSON file, and write each to a XML file. We need to create the output path, check if the XML files exist, and only overwrite if `--force` is specified. Let's write a loop in `overdrive()`:- 488 | 489 | ```bash 490 | # Replace _program() with 491 | _program() 492 | { 493 | core_usesIn jsonreader 494 | core_usesIn xmlwriter 495 | 496 | # document dependency 497 | core_dependency_requires '*' mkdir 498 | overdrive() 499 | { 500 | mkdir -m 0755 -p "$overdrive_outputPath" 501 | core_variable_array_iterate overdrive_jsonGearBoxFiles overdrive_convertJsonFileToXml 502 | } 503 | } 504 | ``` 505 | 506 | `overdrive_convertJsonFileToXml` is a callback that'll be passed each JSON file path. It's the name of a function we'll define (very few people seem to know that callbacks are both easy and powerful in shell script). Now, we could write this in our [shellfire] application:- 507 | 508 | ```bash 509 | # Replace _program() with 510 | _program() 511 | { 512 | core_dependency_requires '*' mkdir 513 | overdrive() 514 | { 515 | mkdir -m 0755 -p "$overdrive_outputPath" 516 | core_variable_array_iterate overdrive_jsonGearBoxFiles overdrive_convertJsonFileToXml 517 | } 518 | 519 | overdrive_convertJsonFileToXml() 520 | { 521 | : 522 | } 523 | } 524 | ``` 525 | 526 | But it's getting to get large, quickly. We should use a module. Let's create a private one for ourselves. Create the file `overdrive/lib/shellfire/overdrive/overdrive.functions`, and put the logic in there:- 527 | 528 | ```bash 529 | core_usesIn jsonreader 530 | core_usesIn xmlwriter 531 | overdrive_convertJsonFileToXml() 532 | { 533 | : 534 | } 535 | ``` 536 | 537 | Now, let's import the module like any other:- 538 | 539 | ```bash 540 | # Replace _program() with 541 | _program() 542 | { 543 | core_usesIn overdrive 544 | core_dependency_requires '*' mkdir 545 | overdrive() 546 | { 547 | mkdir -m 0755 -p "$overdrive_outputPath" 548 | core_variable_array_iterate overdrive_jsonGearBoxFiles overdrive_convertJsonFileToXml 549 | } 550 | } 551 | ``` 552 | 553 | Right, let's add some logic to `overdrive_convertJsonFileToXml()` in `overdrive/lib/shellfire/overdrive/overdrive.functions`:- 554 | 555 | ```bash 556 | # Replace overdrive_convertJsonFileToXml() with 557 | overdrive_convertJsonFileToXml() 558 | { 559 | # core_variable_array_element is set by core_variable_array_iterate 560 | local jsonGearBoxFilePath="$core_variable_array_element" 561 | 562 | local jsonGearBoxFileName="$(core_compatibility_basename "$jsonGearBoxFilePath")" 563 | # Of course, you could use the file program 564 | local extension='.json' 565 | if ! core_variable_endsWith "$jsonGearBoxFileName" "$extension"; then 566 | core_exitError $core_commandLine_exitCode_DATAERR "The JSON gear box file '$jsonGearBoxFilePath' doesn't end in '.json'" 567 | fi 568 | 569 | # Strip .json 570 | local gearBoxFileNameWithoutExtension="$(core_variable_allButLastN "$jsonGearBoxFileName" ${#extension})" 571 | 572 | local xmlOutputFilePath="$overdrive_outputPath"/"$gearBoxFileNameWithoutExtension".xml 573 | 574 | # Don't overwrite 575 | if [ -e "$xmlOutputFilePath" ]; then 576 | if core_variable_isFalse "$overdrive_force"; then 577 | core_message WARN "Skipping conversion of '$jsonGearBoxFileName' to XML as output file already exists" 578 | return 0 579 | fi 580 | fi 581 | 582 | { 583 | xmlwriter_declaration '1.0' 'UTF-8' 'no' 584 | xmlwriter_open JsonGearBox creator overdrive 585 | jsonreader_parse "$jsonGearBoxFilePath" overdrive_convertJsonFileToXml_callback 586 | xmlwriter_close JsonGearBox 587 | } >"$xmlOutputFilePath" 588 | } 589 | ``` 590 | 591 | Let's write that conversion code:- 592 | 593 | ```bash 594 | # Place below overdrive_convertJsonFileToXml() 595 | overdrive_convertJsonFileToXml_callback() 596 | { 597 | case "$eventKind" in 598 | 599 | root) 600 | xmlwriter_leaf value type "$eventVariant" "$eventValue" 601 | ;; 602 | 603 | object) 604 | 605 | case "$eventVariant" in 606 | 607 | start) 608 | if [ "$eventValue" = 'object' ]; then 609 | xmlwriter_open object key "$eventKey" index "$eventIndex" 610 | else 611 | xmlwriter_open object index "$eventIndex" 612 | fi 613 | ;; 614 | 615 | boolean|number|string) 616 | # eg true 617 | xmlwriter_leaf value key "$eventKey" index "$eventIndex" type "$eventVariant" "$eventValue" 618 | ;; 619 | 620 | end) 621 | xmlwriter_close object 622 | ;; 623 | 624 | esac 625 | 626 | ;; 627 | 628 | array) 629 | 630 | case "$eventVariant" in 631 | 632 | start) 633 | if [ "$eventValue" = 'object' ]; then 634 | xmlwriter_open array key "$eventKey" index "$eventIndex" 635 | else 636 | xmlwriter_open array index "$eventIndex" 637 | fi 638 | ;; 639 | 640 | boolean|number|string) 641 | # eg true 642 | xmlwriter_leaf value index "$eventIndex" type "$eventVariant" "$eventValue" 643 | ;; 644 | 645 | end) 646 | xmlwriter_close array 647 | ;; 648 | 649 | esac 650 | 651 | ;; 652 | 653 | esac 654 | } 655 | ``` 656 | 657 | Now, let's try it out. Copy this JSON to `overdrive/gearbox.json:- 658 | 659 | ```bash 660 | { 661 | "hello": "world", 662 | "array": 663 | [ 664 | -0.5e+6, 665 | true, 666 | null, 667 | false, 668 | "something", 669 | { 670 | "nested": "value" 671 | } 672 | ], 673 | "number": 50, 674 | "boolean": true 675 | } 676 | ``` 677 | 678 | Let's convert the data: `./overdrive --output-path ~/output-path -- ./gearbox.json`. Take a look at `~/output-path/gearbox.xml`. Right, now, let's try again: `./overdrive --output-path ~/output-path -- ./gearbox.json`. Good, our logic stops an overwrite. Specify `-f` and try again: `./overdrive --output-path ~/output-path -f -- ./gearbox.json`. 679 | 680 | ## [fatten]ing and [swaddle] using [build] 681 | 682 | [fatten]ing is the process of turning our [shellfire] application into a standalone program. [swaddle] can then take this and create packages, tarballs, Apt repositories and Yum repos, release notes on GitHub, etc. [shellfire] has a [build] framework that you can use to [fatten], [swaddle] and more: [build] scripts are just regular [shellfire] code, so you can incorporate whatever you want. To see how to add [build] to your project, see the [Quick Tutorial](https://github.com/shellfire-dev/build#overview). To incorporate [swaddle], you can then follow the [Build with swaddle Tutorial](https://github.com/shellfire-dev/build/tree/development#build-with-swaddle-tutorial). 683 | 684 | 685 | [shellfire]: https://github.com/shellfire-dev "shellfire homepage" 686 | [fatten]: https://github.com/shellfire-dev/fatten "fatten homepage" 687 | [swaddle]: https://github.com/raphaelcohn/swaddle "Swaddle homepage" 688 | [bish-bosh]: https://github.com/raphaelcohn/bish-bosh "bish-bosh homepage" 689 | [core]: https://github.com/shellfire-dev/core "shellfire core module homepage" 690 | [configure]: https://github.com/shellfire-dev/configure "shellfire configure module homepage" 691 | [cpucount]: https://github.com/shellfire-dev/cpucount "shellfire cpucount module homepage" 692 | [github api]: https://github.com/shellfire-dev/github "shellfire github api module homepage" 693 | [jsonreader]: https://github.com/shellfire-dev/jsonreader "shellfire jsonreader module homepage" 694 | [jsonwriter]: https://github.com/shellfire-dev/jsonwriter "shellfire jsonwriter module homepage" 695 | [unicode]: https://github.com/shellfire-dev/unicode "shellfire unicode module homepage" 696 | [urlencode]: https://github.com/shellfire-dev/urlencode "shellfire urlencode module homepage" 697 | [version]: https://github.com/shellfire-dev/version "shellfire version module homepage" 698 | [xmlwriter]: https://github.com/shellfire-dev/xmlwriter "shellfire xmlwriter module homepage" 699 | [paths.d]: https://github.com/shellfire-dev/paths.d "shellfire paths.d path data homepage" 700 | [build]: https://github.com/shellfire-dev/build "shellfire build module homepage" 701 | -------------------------------------------------------------------------------- /overdrive/gearbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world", 3 | "array": 4 | [ 5 | -0.5e+6, 6 | true, 7 | null, 8 | false, 9 | "something", 10 | { 11 | "nested": "value" 12 | } 13 | ], 14 | "number": 50, 15 | "boolean": true 16 | } -------------------------------------------------------------------------------- /overdrive/lib/shellfire/overdrive/overdrive.functions: -------------------------------------------------------------------------------- 1 | # This file is part of shellfire tutorial. It is subject to the licence terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/shellfire-dev/shellfire/tutorial/COPYRIGHT. No part of shellfire tutorial, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file. 2 | # Copyright © 2014-2015 The developers of shellfire tutorial. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/shellfire-dev/shellfire/tutorial/COPYRIGHT. 3 | 4 | 5 | core_usesIn jsonreader 6 | core_usesIn xmlwriter 7 | overdrive_convertJsonFileToXml() 8 | { 9 | # core_variable_array_element is set by core_variable_array_iterate 10 | local jsonGearBoxFilePath="$core_variable_array_element" 11 | 12 | local jsonGearBoxFileName="$(core_compatibility_basename "$jsonGearBoxFilePath")" 13 | # Of course, you could use the file program 14 | local extension='.json' 15 | if ! core_variable_endsWith "$jsonGearBoxFileName" "$extension"; then 16 | core_exitError $core_commandLine_exitCode_DATAERR "The JSON gear box file '$jsonGearBoxFilePath' doesn't end in '.json'" 17 | fi 18 | 19 | # Strip .json 20 | local gearBoxFileNameWithoutExtension="$(core_variable_allButLastN "$jsonGearBoxFileName" ${#extension})" 21 | 22 | local xmlOutputFilePath="$overdrive_outputPath"/"$gearBoxFileNameWithoutExtension".xml 23 | 24 | # Don't overwrite 25 | if [ -e "$xmlOutputFilePath" ]; then 26 | if core_variable_isFalse "$overdrive_force"; then 27 | core_message WARN "Skipping conversion of '$jsonGearBoxFileName' to XML as output file already exists" 28 | return 0 29 | fi 30 | fi 31 | 32 | { 33 | xmlwriter_declaration '1.0' 'UTF-8' 'no' 34 | xmlwriter_open JsonGearBox creator overdrive 35 | jsonreader_parse "$jsonGearBoxFilePath" overdrive_convertJsonFileToXml_callback 36 | xmlwriter_close JsonGearBox 37 | } >"$xmlOutputFilePath" 38 | } 39 | 40 | overdrive_convertJsonFileToXml_callback() 41 | { 42 | case "$eventKind" in 43 | 44 | root) 45 | xmlwriter_leaf value type "$eventVariant" "$eventValue" 46 | ;; 47 | 48 | object) 49 | 50 | case "$eventVariant" in 51 | 52 | start) 53 | if [ "$eventValue" = 'object' ]; then 54 | xmlwriter_open object key "$eventKey" index "$eventIndex" 55 | else 56 | xmlwriter_open object index "$eventIndex" 57 | fi 58 | ;; 59 | 60 | boolean|number|string) 61 | # eg true 62 | xmlwriter_leaf value key "$eventKey" index "$eventIndex" type "$eventVariant" "$eventValue" 63 | ;; 64 | 65 | end) 66 | xmlwriter_close object 67 | ;; 68 | 69 | esac 70 | 71 | ;; 72 | 73 | array) 74 | 75 | case "$eventVariant" in 76 | 77 | start) 78 | if [ "$eventValue" = 'object' ]; then 79 | xmlwriter_open array key "$eventKey" index "$eventIndex" 80 | else 81 | xmlwriter_open array index "$eventIndex" 82 | fi 83 | ;; 84 | 85 | boolean|number|string) 86 | # eg true 87 | xmlwriter_leaf value index "$eventIndex" type "$eventVariant" "$eventValue" 88 | ;; 89 | 90 | end) 91 | xmlwriter_close array 92 | ;; 93 | 94 | esac 95 | 96 | ;; 97 | 98 | esac 99 | } 100 | -------------------------------------------------------------------------------- /overdrive/overdrive: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # This file is part of shellfire tutorial. It is subject to the licence terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/shellfire-dev/shellfire/tutorial/COPYRIGHT. No part of shellfire tutorial, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file. 3 | # Copyright © 2014-2015 The developers of shellfire tutorial. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/shellfire-dev/shellfire/tutorial/COPYRIGHT. 4 | 5 | 6 | _program() 7 | { 8 | core_usesIn overdrive 9 | core_dependency_requires '*' mkdir 10 | overdrive() 11 | { 12 | mkdir -m 0755 -p "$overdrive_outputPath" 13 | core_variable_array_iterate overdrive_jsonGearBoxFiles overdrive_convertJsonFileToXml 14 | } 15 | } 16 | 17 | _program_name='overdrive' 18 | _program_version='unversioned' 19 | _program_package_or_build='' 20 | _program_path="$([ "${_program_fattening_program_path+set}" = 'set' ] && printf '%s\n' "$_program_fattening_program_path" || ([ "${0%/*}" = "${0}" ] && printf '%s\n' '.' || printf '%s\n' "${0%/*}"))" 21 | _program_libPath="${_program_path}/lib" 22 | _program_etcPath="${_program_path}/etc" 23 | _program_varPath="${_program_path}/var" 24 | _program_entrypoint='overdrive' 25 | 26 | _program_commandLine_parseInitialise() 27 | { 28 | overdrive_outputPath_default="$(pwd)" 29 | } 30 | 31 | _program_commandLine_helpMessage() 32 | { 33 | _program_commandLine_helpMessage_usage="[OPTION]... -- [JSON GEAR BOX FILE]..." 34 | _program_commandLine_helpMessage_description="Turns JSON into XML." 35 | _program_commandLine_helpMessage_options=" 36 | -s, --output-path PATH PATH to output to. 37 | Defaults to current working directory:- 38 | $overdrive_outputPath_default" 39 | _program_commandLine_helpMessage_optionsSpacing=' ' 40 | _program_commandLine_helpMessage_configurationKeys=" 41 | swaddle_outputPath Equivalent to --output-path 42 | " 43 | _program_commandLine_helpMessage_examples=" 44 | ${_program_name} -o /some/path -- some-json-gear-box-file.json 45 | " 46 | } 47 | 48 | _program_commandLine_optionExists() 49 | { 50 | case "$optionName" in 51 | 52 | o|output-path) 53 | echo 'yes-argumented' 54 | ;; 55 | 56 | f|force) 57 | echo 'yes-argumentless' 58 | ;; 59 | 60 | *) 61 | echo 'no' 62 | ;; 63 | 64 | esac 65 | } 66 | 67 | _program_commandLine_processOptionWithoutArgument() 68 | { 69 | case "$optionName" in 70 | 71 | f|force) 72 | overdrive_force='yes' 73 | ;; 74 | 75 | esac 76 | } 77 | 78 | _program_commandLine_processOptionWithArgument() 79 | { 80 | case "$optionName" in 81 | 82 | o|output-path) 83 | core_validate_folderPathIsReadableAndSearchableAndWritableOrCanBeCreated $core_commandLine_exitCode_USAGE 'option' "$optionNameIncludingHyphens" "$optionValue" 84 | overdrive_outputPath="$optionValue" 85 | ;; 86 | 87 | esac 88 | } 89 | 90 | _program_commandLine_handleNonOptions() 91 | { 92 | core_variable_array_initialise overdrive_jsonGearBoxFiles 93 | 94 | local jsonGearBoxFile 95 | for jsonGearBoxFile in "$@" 96 | do 97 | core_variable_array_append overdrive_jsonGearBoxFiles "$jsonGearBoxFile" 98 | done 99 | 100 | if core_variable_array_isEmpty overdrive_jsonGearBoxFiles; then 101 | core_exitError $core_commandLine_exitCode_USAGE "You haven't specified any JSON gear box files - are you sure this is what you wanted?" 102 | fi 103 | } 104 | 105 | _program_commandLine_validate() 106 | { 107 | if core_variable_isSet overdrive_outputPath; then 108 | core_validate_folderPathIsReadableAndSearchableAndWritableOrCanBeCreated $core_commandLine_exitCode_CONFIG 'configuration setting' 'overdrive_outputPath' "$overdrive_outputPath" 109 | else 110 | core_message INFO "Defaulting --output-path to current working directory" 111 | overdrive_outputPath="$overdrive_outputPath_default" 112 | fi 113 | 114 | if core_variable_isSet overdrive_force; then 115 | core_validate_isBoolean $core_commandLine_exitCode_CONFIG 'configuration setting' 'overdrive_force' "$overdrive_force" 116 | else 117 | overdrive_force='no' 118 | fi 119 | } 120 | 121 | # Assumes pwd, and so requires this code to be running from this folder 122 | . "$_program_libPath"/shellfire/core/init.functions "$@" --------------------------------------------------------------------------------