├── .gitignore ├── examples ├── help │ ├── helpful │ ├── helpful-subcmd │ │ ├── thank │ │ │ ├── notACmd.txt │ │ │ ├── you │ │ │ ├── lord │ │ │ ├── audience │ │ │ │ ├── gentleman │ │ │ │ └── guys │ │ │ └── help │ │ ├── please │ │ └── help.txt │ └── README.md ├── nested │ ├── sounds │ ├── sounds-subcmd │ │ ├── animals │ │ │ ├── fish │ │ │ └── mammals │ │ │ │ ├── horse │ │ │ │ └── felines │ │ │ │ ├── housecat │ │ │ │ └── lion │ │ └── vehicles │ │ │ ├── plane │ │ │ └── car │ └── README.md ├── pizza │ ├── pizza │ ├── pizza-subcmd │ │ ├── order │ │ │ ├── order.id │ │ │ ├── new │ │ │ │ ├── drinks │ │ │ │ │ ├── beer │ │ │ │ │ ├── soda │ │ │ │ │ └── help.txt │ │ │ │ ├── help.txt │ │ │ │ └── pizza │ │ │ ├── help.txt │ │ │ ├── status │ │ │ └── cancel │ │ ├── payment │ │ │ ├── acct.db │ │ │ ├── help.txt │ │ │ ├── balance │ │ │ └── pay │ │ ├── status │ │ └── help.txt │ └── README.md ├── helloWorld │ ├── hello │ ├── hello-subcmd │ │ └── world │ └── README.md ├── envOptions │ ├── sports │ ├── sports-subcmd │ │ ├── relaxed │ │ ├── youth │ │ │ ├── popular │ │ │ ├── winter │ │ │ │ ├── ice │ │ │ │ ├── warm │ │ │ │ └── env.sh │ │ │ └── env.sh │ │ ├── league │ │ │ ├── evening │ │ │ ├── weekend │ │ │ └── env.sh │ │ └── env.sh │ └── README.md ├── specTree │ ├── srvctl │ ├── webctl │ ├── tools │ │ └── remote │ ├── serverCmds │ │ ├── help.txt │ │ ├── down │ │ ├── web │ │ │ ├── stop │ │ │ └── start │ │ ├── status │ │ ├── listen │ │ ├── backup │ │ └── port │ ├── sysbins │ └── README.md ├── pitfalls │ ├── noComanion │ └── badSpec └── README.md ├── install.sh ├── README.md ├── subcmd └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | work/ 3 | -------------------------------------------------------------------------------- /examples/help/helpful: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/thank/notACmd.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/nested/sounds: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | -------------------------------------------------------------------------------- /examples/pizza/pizza: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | -------------------------------------------------------------------------------- /examples/helloWorld/hello: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/order.id: -------------------------------------------------------------------------------- 1 | 8883 2 | -------------------------------------------------------------------------------- /examples/envOptions/sports: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/payment/acct.db: -------------------------------------------------------------------------------- 1 | Balance=234.53 2 | 3 | -------------------------------------------------------------------------------- /examples/specTree/srvctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | ./serverCmds/ 4 | 5 | -------------------------------------------------------------------------------- /examples/specTree/webctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | serverCmds/web/ 4 | -------------------------------------------------------------------------------- /examples/specTree/tools/remote: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | ../serverCmds 4 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/thank/you: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Thank You!" 4 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/help.txt: -------------------------------------------------------------------------------- 1 | 2 | Mock of a typical server command tree. 3 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/relaxed: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | declareAnswer tennis 4 | -------------------------------------------------------------------------------- /examples/helloWorld/hello-subcmd/world: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Hello, World!" 4 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/please: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo Pretty Please... 4 | 5 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/thank/lord: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Thank the Lord!" 4 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/youth/popular: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | declareAnswer soccer 4 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/down: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Tearing server down. Bye-bye" 4 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/league/evening: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | declareAnswer softball 4 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/league/weekend: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | declareAnswer volleyball 4 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/youth/winter/ice: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | declareAnswer hockey 4 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/web/stop: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Stop serving HTTP..." 4 | 5 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/youth/winter/warm: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | declareAnswer basketball 4 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/status: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Current server status: Make believe" 4 | -------------------------------------------------------------------------------- /examples/specTree/sysbins: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | # Import an absolute directory 4 | /bin/ 5 | 6 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/league/env.sh: -------------------------------------------------------------------------------- 1 | 2 | export SPORTS_TYPE=team 3 | export SPORTS_GOAL=fun 4 | 5 | -------------------------------------------------------------------------------- /examples/nested/sounds-subcmd/animals/fish: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A $(basename $0) says 'Gulp'" 4 | 5 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/listen: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Server up and listening for new clients" 4 | -------------------------------------------------------------------------------- /examples/nested/sounds-subcmd/vehicles/plane: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A $(basename $0) says 'Buuzzzz!" 4 | 5 | -------------------------------------------------------------------------------- /examples/pitfalls/noComanion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | # There's no default subcmd companion directory... 4 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/status: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo Hi $(whoami)! Your Pizza is out for delivery now. 4 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/backup: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Server state snapped to back up. (Good idea!)" 4 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/web/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Serving HTTP on port 80 (not really...)" 4 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/youth/env.sh: -------------------------------------------------------------------------------- 1 | 2 | export SPORTS_TYPE=team 3 | export SPORTS_GOAL="building character" 4 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/thank/audience/gentleman: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Thank you, ladies and gentleman" 4 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/thank/audience/guys: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Thanks guys, it's been great." 4 | 5 | -------------------------------------------------------------------------------- /examples/nested/sounds-subcmd/animals/mammals/horse: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A $(basename $0) says 'Neigh'" 4 | 5 | -------------------------------------------------------------------------------- /examples/nested/sounds-subcmd/vehicles/car: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A $(basename $0) says 'Vroom! Vroom!" 4 | 5 | -------------------------------------------------------------------------------- /examples/pitfalls/badSpec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env subcmd 2 | 3 | # There's no command tree named missing tree 4 | missingTree 5 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/youth/winter/env.sh: -------------------------------------------------------------------------------- 1 | 2 | export SPORTS_TYPE=solo 3 | export SPORTS_LOCATION=inside 4 | 5 | -------------------------------------------------------------------------------- /examples/nested/sounds-subcmd/animals/mammals/felines/housecat: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A $(basename $0) says 'Meow'" 4 | 5 | -------------------------------------------------------------------------------- /examples/nested/sounds-subcmd/animals/mammals/felines/lion: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A $(basename $0) says 'Raawwwrr'" 4 | 5 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/new/drinks/beer: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A six-pack of beer has been added to your order" 4 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/new/drinks/soda: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "A 2 liter of soda has been added to your order" 4 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/new/drinks/help.txt: -------------------------------------------------------------------------------- 1 | pizza-order-new-drinks 2 | 3 | Adds drinks to an existing pizza order. 4 | 5 | soda - Add soda 6 | beer - Add beer 7 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/new/help.txt: -------------------------------------------------------------------------------- 1 | pizza-order-new 2 | 3 | Create a new order for fast delivery. Yum yum! 4 | 5 | pizza - Create a new pizza pie order 6 | drinks - Add drinks to your order 7 | -------------------------------------------------------------------------------- /examples/specTree/serverCmds/port: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | if [[ $# -gt 0 ]] ; then 4 | portNum=$1 5 | else 6 | portNum=0 7 | fi 8 | 9 | echo "Server binding to port $portNum" 10 | 11 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/payment/help.txt: -------------------------------------------------------------------------------- 1 | pizza-payment 2 | 3 | Tool for managing your bills and payments with the pizza shop 4 | 5 | balance - Check your current account balance 6 | payment - Make a payment 7 | 8 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/thank/help: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | echo "Sub-module for giving and receiving thanks" 4 | echo "Try it out $(whoami)" 5 | echo 6 | echo "Help docunmentation generated on $(date '+%Y-%m-%d')" 7 | 8 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/payment/balance: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | acctDb=$(dirname $0)/acct.db 4 | balance=$(grep Balance $acctDb | cut -d = -f 2) 5 | 6 | echo "Your current balance at the pizza shop is \$$balance" 7 | 8 | -------------------------------------------------------------------------------- /examples/help/helpful-subcmd/help.txt: -------------------------------------------------------------------------------- 1 | 2 | helpful 3 | 4 | A nice little subcmd with some nice helpful help files. 5 | 6 | Root Level Commands: 7 | 8 | please - asks extra nice 9 | thank - gratitude system 10 | 11 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/help.txt: -------------------------------------------------------------------------------- 1 | pizza 2 | 3 | An interactive app for ordering pizza delivery. 4 | 5 | order - manage or create orders 6 | payment - billing and payments 7 | status - get status of your pizza delivery 8 | 9 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/help.txt: -------------------------------------------------------------------------------- 1 | pizza-order 2 | 3 | Create new orders or manage existing ones. Feeling hungry? 4 | 5 | cancel - Cancel your current pizza order 6 | status - Get the status of your latest order 7 | new - New order for delivery 8 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/status: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | orderId=$(cat $(dirname $0)/order.id) 4 | 5 | if [[ -z $orderId ]] ; then 6 | echo "You have no order open" 7 | else 8 | echo "Your order (#$orderId) is on the way!" 9 | fi 10 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/cancel: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | orderDb=$(dirname $0)/order.id 4 | orderId=$(cat $orderDb) 5 | 6 | if [[ -z $orderId ]] ; then 7 | echo "You have no order to cancel" 8 | else 9 | rm $orderDb && touch $orderDb 10 | echo "Your order (#$orderId) has been cancelled" 11 | fi 12 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/payment/pay: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | if [[ $# -gt 0 ]] ; then 4 | payment=$1 5 | else 6 | echo "You must apply a payment over 0.0" >&2 7 | exit 1 8 | fi 9 | 10 | echo "You've paid \$$payment to your pizza bill." 11 | echo "Please wait several days for payment to clear." 12 | 13 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | # Installs to /usr/local/bin/ by default, but if a positional argument 4 | # is set will install to that directory. 5 | 6 | if [[ $# -gt 0 ]] ; then 7 | installTgt=$1 8 | else 9 | installTgt=/usr/local/bin/ 10 | fi 11 | 12 | subcmdSrc=$(dirname $0)/subcmd 13 | 14 | mkdir -p $installTgt 15 | cp $subcmdSrc -t $installTgt 16 | 17 | -------------------------------------------------------------------------------- /examples/envOptions/sports-subcmd/env.sh: -------------------------------------------------------------------------------- 1 | 2 | export SPORTS_LOCATION=outside 3 | export SPORTS_GOAL=health 4 | export SPORTS_TYPE=solo 5 | export SPORTS_COST=cheap 6 | 7 | function declareAnswer() { 8 | echo "A good $SPORTS_TYPE sport for $SPORTS_GOAL is $1." 9 | echo "It's usually played $SPORTS_LOCATION" 10 | echo "The cost to get started is $SPORTS_COST" 11 | } 12 | 13 | export -f declareAnswer 14 | -------------------------------------------------------------------------------- /examples/helloWorld/README.md: -------------------------------------------------------------------------------- 1 | 2 | # helloWorld 3 | 4 | Creates a single sub-command. Entrypoint is `./hello`. Let's try it: 5 | 6 | $ ./hello CMD 7 | 8 | We see one subcommand: 9 | 10 | ------ Valid Sub-Commands for hello-subcmd ----- 11 | 12 | world 13 | 14 | -------------------------------------------- 15 | 16 | Let's try it: 17 | 18 | $ ./hello world 19 | 20 | Hello, World! 21 | 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Examples 3 | 4 | Simple examples to help illustrate how to use subcmd. 5 | 6 | * helloWorld - Simplest possible hello world app 7 | * nested - Illustrates how to use nested sub-directories of arbitrary depth 8 | * envOptions - Demonstration of how to use the env.sh system 9 | * help - Illustrates how to create help messages 10 | * specTree - Example of non-default user-specified command trees. 11 | * pizza - Full fledged app combining most of subcmd's features 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/pizza/README.md: -------------------------------------------------------------------------------- 1 | 2 | # pizza 3 | 4 | A simple app incorporating most of subcmd's features. How about a pizza? 5 | Yum yum! 6 | 7 | By now, you should be pretty familar with most of subcmd's functionality, 8 | so we'll let you explore this in a little less structured way. But here 9 | are some cool commands to get you started: 10 | 11 | $ ./pizza CMD 12 | $ ./pizza order CMD 13 | $ ./pizza order new CMD 14 | $ ./pizza order new pizza --xl --qty 2 pepperoni mushrooms 15 | $ ./pizza order cancel 16 | $ ./pizza status 17 | $ ./pizza payment HELP 18 | $ ./pizza payment balance 19 | -------------------------------------------------------------------------------- /examples/nested/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## sounds 3 | 4 | This example shows how the nested directory structure is mirrored 5 | in the subcommand tree. 6 | 7 | First let's take a look at the command tree directory: 8 | 9 | $ tree ./sounds-subcmd/ 10 | 11 | ./sounds-subcmd/ 12 | ├── animals 13 | │   ├── fish 14 | │   └── mammals 15 | │   ├── felines 16 | │   │   ├── housecat 17 | │   │   └── lion 18 | │   └── horse 19 | └── vehicles 20 | ├── car 21 | └── plane 22 | 23 | To call the plane subcommand we'd use 24 | 25 | $ ./sounds vehicles plane 26 | 27 | To call the fish subcommand we'd use 28 | 29 | $ ./sounds animals fish 30 | 31 | To call the horse we use 32 | 33 | $ ./sounds animals mammals horse 34 | 35 | To call the lion we use 36 | 37 | $ ./sounds animals mammals felines lion 38 | -------------------------------------------------------------------------------- /examples/pizza/pizza-subcmd/order/new/pizza: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import argparse 5 | import random 6 | 7 | parser = argparse 8 | parser = argparse.ArgumentParser(description='Orders a new Pizza.') 9 | parser.add_argument('--xl', action='store_true', 10 | help='Mzke it an extra large') 11 | parser.add_argument('--deepDish', action='store_true', 12 | help="Deep dish style") 13 | parser.add_argument('--qty', type=int, default=1, 14 | help="Qty in order") 15 | parser.add_argument('toppings', nargs='*', default=["cheese"], 16 | help='Toppings to add') 17 | 18 | args = parser.parse_args() 19 | 20 | desc = "piping hot" 21 | if (args.xl): 22 | desc = desc + " extra-large" 23 | if (args.deepDish): 24 | desc = desc + " deep-dish" 25 | 26 | print "----New Order----" 27 | print "%d %s pizza with the following toppings: " % (args.qty, desc) 28 | for top in args.toppings: 29 | print " - %s" % top 30 | print "-----------------" 31 | 32 | cmd_dir = os.path.dirname(os.path.realpath(__file__)) 33 | order_id_path = cmd_dir + "/../order.id" 34 | 35 | order_id = random.randint(1,9999) 36 | 37 | fd = open(order_id_path, 'w') 38 | print >>fd, order_id 39 | fd.close() 40 | 41 | print "OrderID: %d" % order_id 42 | print 43 | 44 | -------------------------------------------------------------------------------- /examples/specTree/README.md: -------------------------------------------------------------------------------- 1 | 2 | This example directory contains several different executable entrypoints, which 3 | each serves to show a different way that the companion command tree can be 4 | specified. 5 | 6 | To start the `srvctl` file contains: 7 | 8 | #!/usr/bin/env subcmd 9 | 10 | ./serverCmds/ 11 | 12 | The last line is a command tree spec, and contains a path relative to the entry 13 | point. In this case it's the `serverCmds/` directory found in this example dir. 14 | 15 | A relative path doesn't need to be in the same directory as its companion 16 | directory. This can be seen with the `./tools/remote` entryPoint which imports the 17 | same `serverCmds` command tree: 18 | 19 | #!/usr/bin/env subcmd 20 | 21 | ../serverCmds 22 | 23 | (Yes, multiple entrypoints can share the same command tree directory) 24 | 25 | An example of how to use an absolute path as a tree spec is the `sysbins` entry 26 | point: 27 | 28 | #!/usr/bin/env subcmd 29 | 30 | /bin/ 31 | 32 | Finally there's no firm requirement that the command directory always starts 33 | at the same root. The `webctl` entrypoint shows us how we can root our command 34 | tree within the sub-directory of another command tree: 35 | 36 | #!/usr/bin/env subcmd 37 | 38 | serverCmds/web/ 39 | 40 | That's a cool trick because it makes `webctl` equivalent to running the 41 | subcommand `srvctl web`. For example these two commands are identical 42 | 43 | $ ./srvctl web start 44 | $ ./webctl start 45 | -------------------------------------------------------------------------------- /examples/help/README.md: -------------------------------------------------------------------------------- 1 | ## helpful 2 | 3 | Simple app to illustrate subcmd help messages. 4 | 5 | Let's try to the root level message: 6 | 7 | $ ./helpful --help 8 | ------ Command Help: helpful-subcmd ------- 9 | 10 | helpful 11 | 12 | A nice little subcmd with some nice helpful help files. 13 | 14 | Root Level Commands: 15 | 16 | please - asks extra nice 17 | thank - gratitude system 18 | 19 | Notice this is the exact content of the help.txt contained at the root 20 | directory 21 | 22 | |./helpful-subcmd/ 23 | |--help.txt <----- HERE 24 | 25 | Let's try help with the thanks subcommand: 26 | 27 | $ ./helpful thank --help 28 | ------ Command Help: thank ------- 29 | Sub-module for giving and receiving thanks 30 | Try it out vagrant 31 | 32 | Help docunmentation generated on 2019-02-19 33 | 34 | Notice that instead of a static message, this contains dynamic content. 35 | That's because this subcommand contains a `help` file, not a `help.txt`. 36 | `help` files are executed, not read, to get the help message. 37 | 38 | |./helpful-subcmd/ 39 | |--/thank/ 40 | |----help <------- HERE 41 | 42 | `help` files are executed rather than read. 43 | 44 | Finally let's try help with the please subcommand 45 | 46 | $ ./helpful please --help 47 | Pretty Please... 48 | 49 | No help message was generated. That's because please is a terminal subcommand 50 | (an exectuable file) not a composite subcommand (a directory). Therefore it 51 | handles all of its own arguments including its help flags. 52 | 53 | |./helpful-subcmd/ 54 | |--/please 55 | -------------------------------------------------------------------------------- /examples/envOptions/README.md: -------------------------------------------------------------------------------- 1 | ## Sports 2 | 3 | A toy example to show how `env.sh` can be used to share common environments within the command tree. 4 | 5 | The layout of the subcommand tree for this app looks like 6 | 7 | sports-subcmd/ 8 | ├── env.sh 9 | ├── league 10 | │   ├── env.sh 11 | │   ├── evening 12 | │   └── weekend 13 | ├── relaxed 14 | └── youth 15 | ├── env.sh 16 | ├── popular 17 | └── winter 18 | ├── env.sh 19 | ├── ice 20 | └── warm 21 | 22 | You'll notice that each sub-directory contains its own (non-exectuable) `env.sh` file. Each of these 23 | files are always '.'-sourced by the bash environment before executing any subcommand underneath 24 | that directory. 25 | 26 | Let's take a look at the root's env.sh: 27 | 28 | export SPORTS_LOCATION=outside 29 | export SPORTS_GOAL=health 30 | export SPORTS_TYPE=solo 31 | export SPORTS_COST=cheap 32 | 33 | function declareAnswer() { 34 | ... 35 | } 36 | 37 | We see a couple things here. First there's some environment variables to be used in the app. Second 38 | it exports a (very small) library of shell functions. Those functions aren't run default, but can be 39 | called by from any shell-script subcommand. 40 | 41 | Let's take a look at the top level subcommand `relax`: 42 | 43 | #!/bin/bash -eu 44 | 45 | declareAnswer tennis 46 | 47 | It uses the shell function `declareAnswer` defined from the `env.sh` file. If we run it we see 48 | that all the environment settings from `env.sh` are indeed correctly set: 49 | 50 | $ ./sports relaxed 51 | 52 | A good solo sport for health is tennis. 53 | It's usually played outside 54 | The cost to get started is cheap 55 | 56 | Let's take a look at one of the nested `env.sh`. The `youth/env.sh` contravenes some of the 57 | environment exports from the root `env.sh`: 58 | 59 | export SPORTS_TYPE=team 60 | export SPORTS_GOAL="building character" 61 | 62 | So, let's try one of the subcommands in this composite group: 63 | 64 | $ ./sports youth popular 65 | 66 | A good team sport for building character is soccer. 67 | It's usually played outside 68 | The cost to get started is cheap 69 | 70 | The $SPORTS_LOCATION and $SPORTS_COST variables from the root `env.sh` are used. But it's 71 | evident that `youth/env.sh` overrides the $SPORTS_TYPE and $SPORTS_GOAL variables. This 72 | shows that `env.sh` files are sourced in order from shallowest in the tree to deepest. 73 | Therefore any values set at one level of the tree can always be overriden inside a subtree. 74 | 75 | To demonstrate let's take one more step down in the tree and look at `youth/winter/env.sh`: 76 | 77 | export SPORTS_TYPE=solo 78 | export SPORTS_LOCATION=inside 79 | 80 | And run one of the subcommands inside this composite group: 81 | 82 | $ ./sports youth winter ice 83 | 84 | A good solo sport for building character is hockey. 85 | It's usually played inside 86 | The cost to get started is cheap 87 | 88 | Let's trace back where each environment variable is coming from. 89 | 90 | * First root `env.sh` is sourced. It sets all $SPORTS_* environment variables 91 | * Then `youth/env.sh` is sourced. It overrides $SPORTS_TYPE and $SPORTS_GOAL 92 | * Then `youth/winter/env.sh` is sourced. It overrides $SPORTS_TYPE from `youth/env.sh` 93 | and $SPORTS_LOCATION from root. 94 | * Finally after all the env.sh settings are '.'-sourced, the subcommand at `youth/winter/ice` 95 | is run. 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # subcmd 2 | Create Git-style CLI sub-commands from a directory tree of executables. 3 | 4 | subcmd allows you to combine multiple exectuables into a single sub-command structure. The 5 | platform is language agnostic, and works with any executable type. C binaries, shell scripts, 6 | python modules, or any mix of those or anything else. 7 | 8 | A directory tree like this: 9 | 10 | /app 11 | |--gc 12 | |--/run 13 | |----restart 14 | |----stop 15 | |----start 16 | 17 | Turns into CLI commands like this: 18 | 19 | $ app gc 20 | $ app run stop 21 | $ app run start 22 | 23 | ## Quickstart 24 | 25 | Clone the git repository, and step inside 26 | 27 | git clone https://github.com/Mister-Meeseeks/subcmd.git 28 | cd ./subcmd/ 29 | 30 | Run the install script to install to your system environment: 31 | 32 | sudo ./install.sh 33 | 34 | Alternatively, if you're not ready to commit to a system install, just add the git repo 35 | to your shell $PATH. 36 | 37 | export PATH=$PATH:$(readlink -f .) 38 | 39 | ## Hello World 40 | 41 | Let's create our first sub-command app. Initialize a new subcmd tree from the command line with 42 | 43 | $ subcmd init hello 44 | 45 | New subcmd tree initialized. 46 | 47 | To create sub-commands add executables to the cmdTree: 48 | ./hello-subCmd/ 49 | 50 | Command can be invoked by calling the entrypoint at: 51 | ./hello 52 | 53 | Now let's try out our new sub-command. We list out what sub-commands are available with the 54 | `CMD` directive. And see an empty tree. From the shell: 55 | 56 | $ ./hello CMD 57 | ------- Valid Sub-Commands for hello-subcmd ----- 58 | 59 | Let's make a subcommand. Create an exectuable inside the subcommand tree. From the shell: 60 | 61 | touch hello-subcmd/world && chmod u+x hello-subcmd/world 62 | 63 | Now in your favorite add the following to `hello-subcmd/world`: 64 | 65 | #!/bin/bash 66 | echo "Hello, World!" 67 | 68 | Now, let's check our entry point, from the command line: 69 | 70 | $ ./hello CMD 71 | ------ Valid Sub-Commands for hello-subcmd ----- 72 | world 73 | 74 | Alright! Now let's try running the new subcommand. 75 | 76 | $ ./hello world 77 | Hello, World! 78 | 79 | ## Install 80 | 81 | To install in a standard Linux system environment, call the install script included in the repo. 82 | 83 | sudo ./install.sh 84 | 85 | By default the program is installed at `/usr/local/bin/`. But if you pass an argument to the 86 | install script, you can install at any custom location. For example if you don't have sudo access 87 | and want to install in $HOME: 88 | 89 | ./install.sh ~/local/bin/ 90 | 91 | Finally the subcmd runs in a single script. You can bypass git and download the script directly 92 | to your system environmnet. This works well in minimal environments: 93 | 94 | sudo curl https://raw.githubusercontent.com/Mister-Meeseeks/subcmd/master/subcmd /usr/local/bin/ \ 95 | && chmod u+x /usr/local/bin/subcmd 96 | 97 | Or in a Dockerfile add the layer: 98 | 99 | RUN sudo curl https://raw.githubusercontent.com/Mister-Meeseeks/subcmd/master/subcmd /usr/local/bin/ \ 100 | && chmod u+x /usr/local/bin/subcmd 101 | 102 | ## subcmd init 103 | 104 | To create a new subcommand app, invoke from the command line 105 | 106 | subcmd init [path] 107 | 108 | Where `[path]` is the filesystem path, where you want the app's entrypoint exectuable to live. Preferably 109 | some directory inside your environment's $PATH. By default that creates a companion command tree directory 110 | at `[path]-subcmd/`. From that directory you can layout your subcommand structure. 111 | 112 | You can also specify an alternative location for the companion command tree by invoking with: 113 | 114 | subcmd init [path] [cmdTree directory path] 115 | 116 | ## Command Tree Directory 117 | 118 | The command tree is just a normal filesystem directory. Any sub-directory is a composite of subcommand based 119 | on that directory's contents. Any executable file in a terminal subcommand. Subcommands can be arbitrarily 120 | nested. For example, say `pop` is an exectuable inside the command tree at the following path: 121 | 122 | [cmdTree root]/snap/crackle/pop 123 | 124 | Pop is a terminal subcommand of crackle. Crackle is a composite subcommand of snap. Snap is a top-level 125 | subcommand of the app. To call the `pop` command from the subcommand we'd invoke the following at the command 126 | line. 127 | 128 | $ [appCmd] snap crackle pop 129 | 130 | Some other details: 131 | * The command tree follows all symbolic both for directories and files. 132 | * The subcommand name is based on the symbolic link's name, not the name of its target. 133 | * Non-exectuable files are ignored. 134 | * Directory or filenames with whitespace or quotes is not supported. 135 | 136 | ## env.sh 137 | 138 | subcmd provides a hook for consolidating a common environment. It can be set either for the entire command 139 | tree, or a specific subtree. Or both. Just add a non-exectuable bash-compatible source file named `env.sh` 140 | at any directory in the command tree. If present a subcmd call will '.'-source into the shell environment 141 | before executing any terminal command underneath that directory. 142 | 143 | `env.sh` can be added at the root of the command tree and will apply across the entire subcommand app. 144 | Or it can be added inside a composite subcommand and will apply across all child subcommands. Multiple 145 | `env.sh` in the call will be added starting at the root and ending at the node. E.g. the follwing command 146 | 147 | $ myApp snap crackle pop 148 | 149 | Will '.'-source in this order, 150 | 151 | * `[cmdTree root]/env.sh` (if exists) 152 | * `[cmdTree root]/snap/env.sh` (if exists) 153 | * `[cmdTree root]/snap/crackle/env.sh` (if exists) 154 | 155 | One typical use for `env.sh` is to set environment variables. Another is to define and export shell 156 | functions to act as a shared library between all the subcommand scripts. But any bash compatible 157 | code can be used to run before the exectuable. Things like creating temporary files, setting signal 158 | handlers, printing startup messages, etc. 159 | 160 | ## CMD List 161 | 162 | submcd allows for the CMD keyword directive to be added to any composite subcommand in a command tree. 163 | Instead of normal processing, it will list all available commands contained by that subcommand. This 164 | can be used to probe the command tree structure from the command line. E.g. 165 | 166 | $ [appCmd] snap crackle CMD 167 | 168 | ------ Valid Sub-Commands for hello-subcmd ----- 169 | pop 170 | 171 | ## Help Messages 172 | 173 | subcmd gives special consideration to files in the command tree named `help.txt` or `help`. To print out 174 | a help message for a composite subcommand call with --help command: 175 | 176 | $ [appCmd] snap crackle --help 177 | 178 | That will look for one of two files: 179 | 180 | [cmdTree root]/snap/crackle/help.txt 181 | [cmdTree root]/snap/crackle/help 182 | 183 | If `help.txt` exists, subcmd will print out its contents to STDERR and exit. If `help` exists, subcmd 184 | will try to run it as an executable and print its output to STDERR, then exit. 185 | 186 | Please note that help files are specific to their sub-directory and subcommand. I.e. 187 | 188 | $ [appCmd] snap --help 189 | 190 | will look for `snap/help.txt` and give up if it doesn't exist. Regardless of the existence or contents 191 | of any other help files in the command tree. 192 | 193 | Also note that for terminal commands (i.e. executable leaves in the directory tree), help flags are 194 | passed straight through to the executable and not handled by subcmd. 195 | 196 | ## Entrypoints 197 | 198 | subcommand requires an entrypoint exectuable to the command tree. This is created automatically by 199 | subcmd init. But it's simple to create your own. The simplest use is just to create an empty exectuble 200 | script with the shebang line set to subcmd: 201 | 202 | #!/usr/bin/env subcmd 203 | 204 | Nothing else is needed. By default it will look for a companion directory with the same name as the 205 | entrypoint but with `-subcmd` postfix. E.g. 206 | 207 | ./bin/myApp 208 | ./bin/myApp-subcmd/ 209 | 210 | Alternatively an entrypoint can contain one non empty, non-commented line. The value of that line 211 | is the path to the companion command tree directory. The path can be either relative or absolute, 212 | but if its relative, it's always relative to the entrypoint's directory. *Not* the the invoker's 213 | working directory. 214 | 215 | Let's say we wanted an exectuable entry point at 216 | 217 | ./bin/myApp 218 | 219 | And we want a command tree at 220 | 221 | ./src/scripts/myCmds 222 | 223 | Then we can easily set that inside the entrypoint (`./bin/myApp`0 by setting its content to 224 | 225 | #!/usr/bin/env subcmd 226 | 227 | ../src/scripts/myCmds/ 228 | 229 | Another useful mechanism is to install a system environment binary that references an external 230 | command tree. We can create a system-level command called `myApp` that references a locally 231 | created command tree. Just create an exectuable at `/usr/bin/myApp` and set its contents to 232 | 233 | #!/usr/bin/env subcmd 234 | 235 | /home/jsmith/myApp/scripts/cmds/ 236 | 237 | ## Dependencies 238 | 239 | * bash 240 | * Linux/GNU compatible readlink (sorry BSD and MacOS) 241 | -------------------------------------------------------------------------------- /subcmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | if [[ $# -eq 0 ]] ; then 5 | entryPoint=help 6 | else 7 | entryPoint=$1 8 | shift; 9 | fi 10 | cliArgs=$@ 11 | 12 | function helpForCmd() { 13 | cat <&2 35 | exit 1 36 | } 37 | 38 | function initTree() { 39 | if [[ $# -eq 0 ]] ; then 40 | helpForCmd 41 | exit 1 42 | fi 43 | 44 | local entryPath=$1 45 | if isHelpCmd "$entryPath" ; then 46 | helpForCmd 47 | fi 48 | 49 | local cmdTree=$entryPath-subcmd 50 | local specTree="" 51 | if [[ $# -ge 2 ]] ; then 52 | local cmdTree=$2 53 | local specTree=$cmdTree 54 | fi 55 | 56 | assertFreshInit "$entryPath" "$cmdTree" 57 | createFresh "$entryPath" "$cmdTree" "$specTree" 58 | } 59 | 60 | function assertFreshInit() { 61 | local entryPath=$1 62 | local cmdTree=$2 63 | if [[ -e "$entryPath" ]] ; then 64 | echo "subcmd Error: Cannot init - entryPath already exists:" \ 65 | "$entryPath" >&2 66 | exit 1 67 | elif [[ -e "$cmdTree" ]] ; then 68 | echo "subcmd Error: Cannot init - cmdTree already exists:" \ 69 | "$cmdTree" >&2 70 | exit 1 71 | fi 72 | } 73 | 74 | function createFresh() { 75 | local entryPath=$1 76 | local cmdTree=$2 77 | local specTree=$3 78 | formEntryContent "$specTree" > "$entryPath" 79 | chmod a+rx "$entryPath" 80 | mkdir "$cmdTree" 81 | declareInit "$entryPath" "$cmdTree" >&2 82 | } 83 | 84 | function formEntryContent() { 85 | echo "#!/usr/bin/env subcmd" 86 | echo 87 | if [[ $# -ge 1 ]] ; then 88 | echo "$1" 89 | fi 90 | } 91 | 92 | function declareInit() { 93 | local entryPath=$1 94 | local cmdTree=$2 95 | echo "New subcmd tree initialized." 96 | echo 97 | echo "To create sub-commands add executables to the cmdTree:" 98 | echo $(formatExecView "$cmdTree/") 99 | echo 100 | echo "Command can be invoked by calling the entrypoint at: " 101 | echo $(formatExecView "$entryPath") 102 | echo 103 | } 104 | 105 | function formatExecView() { 106 | local entryPath=$1 107 | if isAbsPath "$entryPath" ; then 108 | echo "$entryPath" 109 | else 110 | echo "./$entryPath" 111 | fi 112 | } 113 | 114 | function discoverTree() { 115 | local treeSpec=$(scanTreeSpec) 116 | if [[ -z "$treeSpec" ]] ; then 117 | companionTree "$entryPoint" 118 | else 119 | resolveSpec "$entryPoint" "$treeSpec" 120 | fi 121 | } 122 | 123 | function scanTreeSpec() { 124 | cat "$entryPoint" \ 125 | | grep -E -v "^#" \ 126 | | grep -E -v "^$" \ 127 | | tail -n 1 128 | } 129 | 130 | function companionTree() { 131 | local entryPoint=$1 132 | local companionTree=$(formCompanion "$entryPoint") 133 | assertCompanion "$companionTree" 134 | echo "$companionTree" 135 | } 136 | 137 | function assertCompanion() { 138 | local companionTree=$1 139 | if [[ ! -d "$companionTree" ]] ; then 140 | echo "subcmd Error: No dir-tree specified and companion directory" \ 141 | "does not exist:" "$companionTree" >&2 142 | exit 1 143 | fi 144 | 145 | } 146 | 147 | function formCompanion() { 148 | local entryPoint=$1 149 | local canonEntry=$(readlink -f "$entryPoint") 150 | echo $(dirname "$canonEntry")/$(basename "$canonEntry")-subcmd/ 151 | } 152 | 153 | 154 | function resolveSpec() { 155 | local entryPoint=$1 156 | local treeSpec=$2 157 | local treePath=$(locateTreePath "$entryPoint" "$treeSpec") 158 | assertSpec "$treePath" 159 | echo "$treePath" 160 | } 161 | 162 | function locateTreePath() { 163 | local entryPoint=$1 164 | local treeSpec=$2 165 | if isAbsPath "$treeSpec" ; then 166 | echo "$treeSpec" 167 | else 168 | relatePathFrom "$entryPoint" "$treeSpec" 169 | fi 170 | } 171 | 172 | function isAbsPath() { 173 | local entryPoint=$1 174 | (echo "$entryPoint" | grep -E -q "^[/~]") 175 | } 176 | 177 | function relatePathFrom() { 178 | local entryPoint=$1 179 | local treeSpec=$2 180 | local canonEntry=$(readlink -f "$entryPoint") 181 | local rootDir=$(dirname "$canonEntry") 182 | echo "$rootDir/$treeSpec" 183 | } 184 | 185 | function assertSpec() { 186 | local treePath=$1 187 | if [[ ! -d $treePath ]] ; then 188 | echo "subcmd Error: User specified dir-tree path does not exist:" \ 189 | "$treePath" >&2 190 | exit 1 191 | fi 192 | } 193 | 194 | function isHelpCmd() { 195 | local subCmd=$1 196 | listHelpWords | grep -E -q "^$subCmd$" 197 | } 198 | 199 | function listHelpWords() { 200 | cat <&2 245 | exit 1 246 | fi 247 | } 248 | 249 | function attemptHelp() { 250 | local descentDir=$1 251 | if [[ -f "$descentDir/help" ]] ; then 252 | helpWithStream "$descentDir" <("$descentDir"/help $cliArgs) 253 | elif [[ -f $descentDir/help.txt ]] ; then 254 | helpWithStream "$descentDir" "$descentDir/help.txt" 255 | else 256 | bestEffortNoHelp "$descentDir" 257 | return 1 258 | fi 259 | } 260 | 261 | function helpWithStream() { 262 | local descentDir=$1 263 | local helpStream=$2 264 | local cmdName=$(basename "$descentDir") 265 | headerHelp "$cmdName" "$helpStream" >&2 266 | } 267 | 268 | function bestEffortNoHelp() { 269 | local descentDir=$1 270 | local cmdName=$(basename "$descentDir") 271 | listSubCmds "$descentDir" "$cmdName" >&2 272 | } 273 | 274 | function headerHelp() { 275 | local cmdName=$1 276 | local helpPath=$2 277 | echo "------ Command Help: $cmdName -------" 278 | cat "$helpPath" 279 | } 280 | 281 | function helpSubCmds() { 282 | local descentDir=$1 283 | local cmdName=$(basename "$descentDir") 284 | listSubCmds "$descentDir" "$cmdName" >&2 285 | } 286 | 287 | function listSubCmds() { 288 | local descentDir=$1 289 | local cmdName=$2 290 | echo "------ Valid Sub-Commands for $cmdName -----" 291 | echo 292 | findSubCmds "$descentDir" 293 | echo 294 | echo "--------------------------------------------" 295 | } 296 | 297 | function findSubCmds() { 298 | local descentDir=$1 299 | shopt -s nullglob 300 | for subPath in "$descentDir"/* ; do 301 | if [[ -x "$subPath" || -d "$subPath" ]] ; then 302 | basename "$subPath" 303 | fi 304 | done 305 | } 306 | 307 | function failNonExec() { 308 | failWithReason "$1" "$2" "is not executable" 309 | } 310 | 311 | function failBrokenLink() { 312 | failWithReason "$1" "$2" "is a broken link" 313 | } 314 | 315 | function failMissing() { 316 | failWithReason "$1" "$2" "does not exist" 317 | } 318 | 319 | function failDanglingDir() { 320 | failWithReason "$1" "" "is a directory" 321 | } 322 | 323 | function failWithReason() { 324 | local baseDir=$1 325 | local tryCmd=$2 326 | local reason=$3 327 | echo "subcmd Error: $baseDir/$tryCmd $reason" >&2 328 | attemptHelp "$baseDir" 329 | exit 1 330 | } 331 | 332 | function sourceTreeEnv() { 333 | local descentDir=$1 334 | envPath="$descentDir/env.sh" 335 | if [[ -e "$envPath" && ! -x "$envPath" ]] ; then 336 | . $envPath 337 | fi 338 | } 339 | 340 | ## help mode for subcmd itself 341 | if isHelpCmd "$entryPoint" ; then 342 | helpForCmd 343 | fi 344 | 345 | ## init mode - prepare a new subcmd tree 346 | if [[ "$entryPoint" == "init" ]] ; then 347 | initTree "$@" 348 | exit 0 349 | fi 350 | 351 | ## Standard eval mode - called from shebang line 352 | cmdTree=$(discoverTree) 353 | descent=$cmdTree 354 | 355 | while [[ $# -gt 0 ]] ; do 356 | sourceTreeEnv "$descent" 357 | 358 | if isHelpCmd "$1" ; then 359 | yellForHelp "$descent" 360 | exit 0 361 | 362 | elif isCmdLister "$1" ; then 363 | helpSubCmds "$descent" 364 | exit 0 365 | 366 | elif [[ -d "$descent/$1" ]] ; then 367 | descent="$descent/$1" 368 | shift 369 | 370 | elif [[ -x "$descent/$1" ]] ; then 371 | executor="$descent/$1" 372 | shift 373 | $executor "$@" 374 | exit 0 375 | 376 | elif [[ -e "$descent/$1" ]] ; then 377 | failNonExec "$descent" "$1" 378 | 379 | elif [[ -L "$descent/$1" ]] ; then 380 | failBrokenLink "$descent" "$1" 381 | 382 | else 383 | failMissing "$descent" "$1" 384 | fi 385 | done 386 | 387 | failDanglingDir "$descent" 388 | 389 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------