├── .gitignore ├── .rvmrc ├── Gemfile ├── Gemfile.lock ├── README.md ├── config.ru ├── intro ├── 01_intro.md ├── book_cover.jpg ├── challenge.png ├── comic.png ├── ok.png ├── rage1.jpg ├── rage2.jpg └── rage3.jpg ├── make_awesome ├── 01_making_awesome.md ├── 03_bad_ui.md ├── 04_option_parser.md ├── 05_gli.md ├── 06_summary.md ├── 07_other.md └── zz_bad_system.md ├── opower.png ├── showoff.json ├── styles.css └── what_is_awesome ├── 01_awesome_defined.md ├── 02_first_class.md ├── 03_play_well.md ├── 04_be_helpful.md ├── cuke_fail.png └── cuke_pass.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use 1.9.2@showoff --create 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem "gli", ">= 1.3.2" 4 | gem "showoff" 5 | gem "heroku" 6 | gem "pdfkit" 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | bluecloth (2.1.0) 5 | gli (1.3.2) 6 | heroku (2.3.6) 7 | launchy (>= 0.3.2) 8 | rest-client (~> 1.6.1) 9 | term-ansicolor (~> 1.0.5) 10 | json (1.5.3) 11 | launchy (2.0.3) 12 | mime-types (1.16) 13 | nokogiri (1.5.0) 14 | pdfkit (0.5.2) 15 | rack (1.3.1) 16 | rest-client (1.6.3) 17 | mime-types (>= 1.16) 18 | showoff (0.4.2) 19 | bluecloth 20 | gli (>= 1.2.5) 21 | json 22 | nokogiri 23 | sinatra 24 | sinatra (1.2.6) 25 | rack (~> 1.1) 26 | tilt (>= 1.2.2, < 2.0) 27 | term-ansicolor (1.0.6) 28 | tilt (1.3.2) 29 | 30 | PLATFORMS 31 | ruby 32 | 33 | DEPENDENCIES 34 | gli (>= 1.3.2) 35 | heroku 36 | pdfkit 37 | showoff 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awesome Command Line Applications in Ruby 2 | 3 | Slides for my talk at OSCON, July, 2011 4 | 5 | # View 6 | 7 | * [View slides online](https://speakerdeck.com/davetron5000/build-awesome-command-line-applications-with-ruby) 8 | 9 | # Building 10 | 11 | git clone http://github.com/davetron5000/awesome-cli-ruby.git 12 | cd awesome-cli-ruby 13 | gem install bundler # use sudo if not using rvm 14 | bundle install # again, use sudo if not using rvm 15 | showoff serve # browse to http://localhost:9090 16 | 17 | # License 18 | 19 | Creative Commons License
Awesome Command Line Applications in Ruby by David Copeland is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
Based on a work at www.github.com. 20 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require "showoff" 3 | run ShowOff.new 4 | -------------------------------------------------------------------------------- /intro/01_intro.md: -------------------------------------------------------------------------------- 1 | !SLIDE smbullets 2 | # Use Ruby to Start Making Awesome Command Line Applications 3 | ## Dave Copeland 4 | * [@davetron5000](http://www.twitter.com/davetron5000) 5 | * `davidcopeland (at) naildrivin5.com` 6 | * `david.copeland (at) livingsocial.com` 7 | * [naildrivin5.com/blog](http://www.naildrivin5.com/blog) 8 | * [www.awesomecommandlineapps.com](http://www.awesomecommandlineapps.com) 9 | 10 | !SLIDE commandline incremental 11 | # Who am I? # 12 | 13 | $ who am i 14 | Dave Copeland / @davetron5000 15 | $ ps 16 | Engineer at LivingSocial 17 | 18 | !SLIDE center 19 | 20 | 21 | 22 | !SLIDE commandline incremental 23 | # Who am I? # 24 | 25 | $ history 26 | 1986 c64 basic 27 | 1991 C # vi cursor keys didn't work 28 | 1995 Perl 29 | 1998 Java 30 | 2008 Ruby 31 | $ history | grep IDE 32 | 2010 history | grep IDE 33 | 34 | !SLIDE 35 | # Why care about command-line apps? # 36 | 37 | !SLIDE 38 | # An all too familiar tale... 39 | 40 | !SLIDE commandline incremental 41 | # Emergency! # 42 | 43 | $ vi one_off_script.sh 44 | $ ./one_off_script.sh -x /tmp/foo.csv > /top/magic.txt 45 | done. 46 | $ git add one_off_script.sh 47 | $ git commit -m 'automated stuff in time for launch' 48 | 49 | !SLIDE center 50 | # 6 Months Later... # 51 | 52 | 53 | !SLIDE center 54 | # 6 Months Later... # 55 | 56 | 57 | !SLIDE center 58 | # 6 Months Later... # 59 | 60 | 61 | !SLIDE center 62 | # Challenge Accepted 63 | 64 | 65 | !SLIDE commandline incremental 66 | # Challenge Accepted 67 | $ ./one_off_script.sh 68 | -bash: /tmp/intermediate.xml: not found 69 | $ ./one_off_script.sh help 70 | -bash: help: command not found 71 | $ ./one_off_script.sh --help 72 | done. 73 | $ ./one_off_script.sh /? 74 | ls: /? No such file or directory 75 | 76 | !SLIDE center 77 | ## vi ./one\_of\_script.sh 78 | 79 | 80 | 81 | !SLIDE bullets incremental 82 | # Why care about command-line apps? # 83 | * You *will* have to write them 84 | * And they *will* become someone's job 85 | * And you *will* be on the hook to fix them 86 | * Think about "future you" 87 | 88 | !SLIDE 89 | # Also, the command-line is an **infinitely flexible** way to **glue disparate systems** together and being able to write them makes you a **better, more versatile** developer 90 | !SLIDE bullets incremental 91 | # Make even your one-off scripts awesome # 92 | 93 | * No script is really a one-off 94 | * Be kind to "future you" 95 | * Ruby can make it easy 96 | -------------------------------------------------------------------------------- /intro/book_cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/book_cover.jpg -------------------------------------------------------------------------------- /intro/challenge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/challenge.png -------------------------------------------------------------------------------- /intro/comic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/comic.png -------------------------------------------------------------------------------- /intro/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/ok.png -------------------------------------------------------------------------------- /intro/rage1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/rage1.jpg -------------------------------------------------------------------------------- /intro/rage2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/rage2.jpg -------------------------------------------------------------------------------- /intro/rage3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/intro/rage3.jpg -------------------------------------------------------------------------------- /make_awesome/01_making_awesome.md: -------------------------------------------------------------------------------- 1 | !SLIDE subsection 2 | # Why Ruby? 3 | 4 | !SLIDE bullets incremental 5 | # Ruby, The Language 6 | * High-level abstractions 7 | * Still close to the metal (e.g. `FileUtils`) 8 | * Fast (no heavyweight VM to start up) 9 | * Simple packaging/distribution with RubyGems 10 | 11 | !SLIDE bullets incremental 12 | # Ruby, The Ecosystem 13 | * CLI is part of the culture 14 | * Great open-source gems for command-line 15 | * Great open-source gems for everything else 16 | 17 | !SLIDE subsection 18 | # Give me something I can use 19 | 20 | !SLIDE bullets incremental 21 | # Big sign of un-awesome 22 | * Crappy user interface 23 | 24 | -------------------------------------------------------------------------------- /make_awesome/03_bad_ui.md: -------------------------------------------------------------------------------- 1 | !SLIDE 2 | # Poor User Interface 3 | ## Simple Apps 4 | 5 | @@@Ruby 6 | 7 | if ARGV[0] == '-f' 8 | filename = ARGV[1] 9 | end 10 | 11 | !SLIDE small 12 | # Poor User Interface 13 | ## Command-suite 14 | 15 | @@@ Ruby 16 | command = ARGV.shift 17 | 18 | case command 19 | when 'create' 20 | ShowOffUtils.create 21 | when 'heroku' 22 | ShowOffUtils.heroku 23 | when 'serve' 24 | ShowOff.run! :host => 'localhost', :port => 9090 25 | when 'static' 26 | ShowOff.do_static(ARGV) 27 | else 28 | ShowOffUtils.help 29 | end 30 | 31 | !SLIDE subsection 32 | # If you manipulate `ARGV`, you're doing it wrong 33 | 34 | !SLIDE bullets incremental 35 | # Poor User Interface 36 | * Hard to maintain & enhance 37 | * Hard for _others_ to maintain & enhance 38 | * *More* work than using libraries 39 | 40 | -------------------------------------------------------------------------------- /make_awesome/04_option_parser.md: -------------------------------------------------------------------------------- 1 | !SLIDE small 2 | # Simple Apps 3 | ## `OptionParser` 4 | 5 | @@@ Ruby 6 | require 'optparse' 7 | 8 | options = {} 9 | opts = OptionParser.new do |opts| 10 | executable_name = File.split($0)[1] 11 | opts.banner = <<-EOS 12 | Jekyll is a static site generator 13 | 14 | Usage: #{executable_name} [options] args... 15 | EOS 16 | 17 | # option specifications go here 18 | 19 | end 20 | opts.parse! 21 | 22 | !SLIDE 23 | # OptionParser 24 | ## Parsing switches 25 | 26 | @@@ Ruby 27 | opts.on("--auto", "Auto-regenerate") do 28 | options['auto'] = true 29 | end 30 | 31 | !SLIDE small 32 | # OptionParser 33 | ## Negatable switches 34 | 35 | @@@ Ruby 36 | opts.on("--[no-]auto", "Auto-regenerate") do |auto| 37 | options['auto'] = auto 38 | end 39 | 40 | !SLIDE smaller 41 | # OptionParser 42 | ## Parsing flags 43 | 44 | @@@ Ruby 45 | opts.on("-s","--server PORT", 46 | "Start web server on PORT") do |port| 47 | 48 | # Do some sanity checking 49 | if port && port.to_i < 1000 50 | raise "Port must be >= 1000" 51 | end 52 | 53 | options['server_port'] = port.to_i 54 | end 55 | 56 | !SLIDE smaller 57 | # OptionParser 58 | ## Awesome Validation 59 | 60 | @@@ Ruby 61 | opts.on("--ip IP", 62 | /^\d+\.\d+\.\d+\.\d+$/, 63 | "IP address to bind to") do |ip| 64 | options['ip'] = ip # <- matches the regex 65 | end 66 | 67 | !SLIDE commandline small incremental 68 | # OptionParser 69 | ## Awesome Help 70 | $ awesome_app -h 71 | My awesome app is awesome 72 | 73 | Usage: awesome_app [options] args... 74 | 75 | --[no-]auto Auto-regenerate 76 | -s, --server PORT Start web server (def 4000) 77 | --ip IP IP address to bind to 78 | $ awesome_app --help 79 | # same thing 80 | 81 | !SLIDE bullets incremental 82 | # `OptionParser` 83 | * Easy for others to modify, too 84 | * No excuse, really 85 | -------------------------------------------------------------------------------- /make_awesome/05_gli.md: -------------------------------------------------------------------------------- 1 | !SLIDE bullets incremental 2 | # What about command-suite? 3 | * `OptionParser` not well suited 4 | * A few other options 5 | 6 | !SLIDE bullets incremental 7 | # `GLI` 8 | * Tailor-made for command-suite 9 | * Make it easy for a polished UI 10 | * Easy to get started 11 | 12 | !SLIDE commandline incremental 13 | 14 | $ gem install gli 15 | Successfully installed gli 16 | $ gli init todo new list complete 17 | Creating dir ./todo/lib... 18 | Creating dir ./todo/bin... 19 | Creating dir ./todo/test... 20 | Created ./todo/bin/todo 21 | $ cd todo ; ls 22 | Gemfile README.rdoc Rakefile 23 | bin/ lib/ test/ 24 | todo.gemspec todo.rdoc 25 | 26 | !SLIDE commandline small incremental 27 | $ bin/todo 28 | usage: todo [global options] command [command options] 29 | 30 | Version: 0.0.1 31 | 32 | Global Options: 33 | -f, --flagname=The name of the argument - Describe some flag here 34 | (default: the default) 35 | -s, --switch - Describe some switch here 36 | 37 | Commands: 38 | complete - Describe complete here 39 | help - Shows list of commands or help for one command 40 | list - Describe list here 41 | new - Describe new here 42 | $ bin/todo help list 43 | list 44 | Describe list here 45 | 46 | !SLIDE bullets incremental 47 | # What did we just get? 48 | * Command and option parsing 49 | * Complex help formatting 50 | * Boilerplate gem-ified project 51 | 52 | !SLIDE small 53 | # Make UI 54 | 55 | @@@Ruby 56 | desc 'location of the todo file' 57 | arg_name 'todo_file' 58 | default_value '~/.todo.txt' 59 | flag :f 60 | 61 | desc 'List all todo items' 62 | command :list do |c| 63 | c.desc 'List in long form' 64 | c.switch :l 65 | 66 | c.desc 'Show all items, even completed' 67 | c.switch [:a,:all] 68 | 69 | c.desc 'Specify sort order' 70 | c.arg_name '[date|name|completed]' 71 | c.flag [:s,:sort] 72 | c.action do |global_options,options,args| 73 | # Logic goes here 74 | end 75 | end 76 | 77 | !SLIDE commandline small incremental 78 | # And now... 79 | $ bin/todo help 80 | usage: todo command [command options] 81 | 82 | Global Options: 83 | -f - location of the todo file (default ~/.todo.txt) 84 | 85 | Version: 0.0.1 86 | 87 | Commands: 88 | complete - Complete one or more items 89 | help - Shows list of commands or help for one command 90 | list - List all todo items 91 | new - Create a new todo item 92 | $ bin/todo help list 93 | list [command options] 94 | List all todo items 95 | 96 | Command Options: 97 | -a, --all - Show all items, even 98 | completed ones 99 | -l - List in long form 100 | -s, --sort=[date|name|completed] - Specify sort order 101 | 102 | 103 | !SLIDE bullets 104 | # Many other cool features, too 105 | * [github.com/davetron5000/gli](http://github.com/davetron5000/gli) 106 | -------------------------------------------------------------------------------- /make_awesome/06_summary.md: -------------------------------------------------------------------------------- 1 | !SLIDE bullets incremental 2 | # `OptionParser` and `GLI` 3 | * Dead-simple, polished UI 4 | * Spend time on your app, not formatting help text 5 | * Easily maintain documentation 6 | 7 | !SLIDE subsection 8 | # Other CLI Gems 9 | -------------------------------------------------------------------------------- /make_awesome/07_other.md: -------------------------------------------------------------------------------- 1 | !SLIDE smbullets incremental 2 | # CLI-parsing tools 3 | * `OptionParser` - builtin 4 | * [`GLI`](http://github.com/davetron5000/gli/) - mega-easy command-suite 5 | * [`thor`](http://github.com/wycats/thor) - command-suite 6 | * [`trollop`](http://trollop.rubyforge.org/) - simple and command-suite 7 | * [`methadone`](http://github.com/davetron5000/methadone/) - simple + bootstrapping 8 | 9 | !SLIDE smbullets incremental 10 | # User interaction 11 | * `readline` - (builtin) command-line history/editing 12 | * [`highline`](http://highline.rubyforge.org/) - get user I/O cleanly 13 | * [`terminal-tables`](http://github.com/visionmedia/terminal-table) - make formatted ASCII tables 14 | * [`rainbow`](http://github.com/sickill/rainbow) - color your output 15 | 16 | !SLIDE bullets incremental 17 | # Testing & Documentation 18 | * [`aruba`](https://github.com/aslakhellesoy/aruba) - Cuke-style command-line tests 19 | * [`gem-man`](https://github.com/defunkt/gem-man) - Install man pages with your gem 20 | * [`ronn`](http://rtomayko.github.com/ronn/) - Author man pages in markdown 21 | 22 | !SLIDE bullets incremental 23 | # In conclusion... 24 | * Making awesome is *easy* 25 | * Ruby makes it simple 26 | * Tools like GLI and OptionParser make it fast 27 | * "Future you" thanks you 28 | 29 | !SLIDE subsection 30 | # Am I missing something? 31 | 32 | !SLIDE subsection 33 | # or (gasp) wrong? 34 | 35 | !SLIDE center 36 | 37 | 38 | 39 | !SLIDE smbullets 40 | # Thanks! 41 | * [@davetron5000](http://www.twitter.com/davetron5000) 42 | * [www.naildrivin5.com/blog](http://www.naildrivin5.com/blog) 43 | * [www.awesomecommandlineapps.com](http://www.awesomecommandlineapps.com) 44 | * Slides on Github - [http://bit.ly/cli-gh](http://bit.ly/cli-gh) 45 | * Slides on Heroku - [http://bit.ly/cli-slides](http://bit.ly/cli-slides) 46 | * Rate this talk at [http://spkr8.com/t/8839](http://spkr8.com/t/8839) 47 | -------------------------------------------------------------------------------- /make_awesome/zz_bad_system.md: -------------------------------------------------------------------------------- 1 | !SLIDE 2 | # Poor subprocess management 3 | 4 | @@@Ruby 5 | include FileUtils 6 | 7 | date = Time.now.to_s 8 | filename = "#{date}_foo_db.sql" 9 | tmp = "#{filename}.tmp" 10 | 11 | %x[mysqldump foo_db > #{tmp}] 12 | 13 | mv(tmp,filename) 14 | 15 | %x[gzip #{filename}] 16 | 17 | 18 | 19 | !SLIDE bullets incremental 20 | # How you should do it 21 | * *Check exit codes* - plays well! 22 | * *Log the command* - helpful! 23 | * *Capture the output* - helpful! 24 | * Think of "future you" 25 | 26 | !SLIDE smaller 27 | # Use `open3` 28 | 29 | @@@Ruby 30 | command = "/full/path/to/some_external_command.sh" 31 | stdout,stderr,status = Open3.capture3(command) 32 | STDERR.puts(stderr) # Their stderr is our stderr 33 | 34 | if status.success? 35 | stdout.split(/\n/).each do |line| 36 | # Do our work 37 | end 38 | true 39 | else 40 | STDERR.puts("Problem running #{command}") 41 | false 42 | end 43 | 44 | !SLIDE smaller 45 | # Throw it in a method 46 | 47 | @@@Ruby 48 | include FileUtils 49 | 50 | def sh(command) 51 | # Stuff from previous slide... 52 | end 53 | 54 | date = Time.now.to_s 55 | filename = "#{date}_foo_db.sql" 56 | tmp = "#{filename}.tmp" 57 | if sh("mysqldump foo_db > #{tmp}") 58 | if mv(tmp,filename) 59 | if sh("gzip #{filename}") 60 | exit 0 61 | else 62 | exit 1 63 | end 64 | else 65 | rm(tmp) 66 | exit 2 67 | end 68 | else 69 | rm(tmp) 70 | exit 3 71 | end 72 | 73 | !SLIDE bullets incremental 74 | # Awesome subprocess management 75 | * Customize it... 76 | * ...put it in a gem... 77 | * ...and use it instead of `system` or `%x[]` 78 | * External calls are now bulletproof 79 | * And it's the same LOC 80 | 81 | -------------------------------------------------------------------------------- /opower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/opower.png -------------------------------------------------------------------------------- /showoff.json: -------------------------------------------------------------------------------- 1 | [{"section":"intro"},{"section":"what_is_awesome"},{"section":"make_awesome"}] 2 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * LivingSocial Colors: 3 | * 4 | * Primary Palette: 5 | * 6 | * honeycomb - #e9ba2a; 7 | * clementine - #f0812b; 8 | * slushie - #5fb8dd; 9 | * frosting - #ea0b8c; 10 | * 11 | * Secondary Palette: 12 | * 13 | * aloe - #50886b; 14 | * berry - #508395; 15 | * wasabi - #9eae3d; 16 | * grape - #92278f; 17 | * spice - #eb3f3c; 18 | * wine - #832e2f; 19 | * 20 | */ 21 | body, .slide, h1, h2, h3, h4, h5, p, div { 22 | color: black; 23 | } 24 | 25 | h1 { 26 | font-family: Arial rounded mt bold, Helvetica, Arial, sans-serif; 27 | } 28 | 29 | h2, h3, h4, h5 { 30 | font-family: Georgia, Times, serif; 31 | font-style: italic; 32 | } 33 | 34 | p, div, li { 35 | font-family: Helvetica, Arial, sans-serif; 36 | } 37 | 38 | body { 39 | background: #e9ba2a; 40 | } 41 | 42 | div.subsection h1 { 43 | background: #f0812b; 44 | color: white; 45 | padding-top: 20px; 46 | padding-bottom: 20px; 47 | } 48 | 49 | blockquote { 50 | margin: 20px; 51 | padding: 20px; 52 | text-indent: 40px; 53 | border: solid thin; 54 | background: #832e2f; 55 | -webkit-border-radius: 10px; 56 | -moz-border-radius: 10px; 57 | border-radius: 10px; 58 | } 59 | blockquote p em { 60 | color: white; 61 | font-size: 200%; 62 | line-height: 40px; 63 | } 64 | .slide { 65 | /* 66 | * background-image: url("opower.png"); 67 | */ 68 | background-repeat: no-repeat; 69 | background-attachment:fixed; 70 | background-position: 83% 94%; 71 | } 72 | 73 | a:hover, a:visited, a { 74 | color: #508395; 75 | } 76 | 77 | code.result { 78 | font-weight: bold; 79 | color: #262626; 80 | } 81 | 82 | /* 83 | * orig Opower LivingSocial 84 | * color: black black black; 85 | * color: blue #0088CE #508395; 86 | * color: brown #FF5800 #e9ba2a; 87 | * color: cyan #69C8F8 #5fb8dd; 88 | * color: darkblue #005293 #508395; 89 | * color: darkgreen #7AB800 #50886b; 90 | * color: darkred #A30050 #832e2f; 91 | * color: orange #FF5800 #f0812b; 92 | * color: pink #B1059D #ea0b8c; 93 | * color: purple #B1059D #832e2f; 94 | * color: red #A30050 #eb3f3c; 95 | * color: teal #D9E506 #92278f; 96 | */ 97 | 98 | pre.sh_sourceCode { 99 | background: transparent; 100 | } 101 | pre.sh_sourceCode .sh_keyword { color: #508395; font-weight: bold; } /* language keywords */ 102 | pre.sh_sourceCode .sh_type { color: #50886b; } /* basic types */ 103 | pre.sh_sourceCode .sh_usertype { color: #92278f; } /* user defined types */ 104 | pre.sh_sourceCode .sh_string { color: #832e2f; font-family: monospace; } /* strings and chars */ 105 | pre.sh_sourceCode .sh_regexp { color: #e9ba2a; font-family: monospace; } /* regular expressions */ 106 | pre.sh_sourceCode .sh_specialchar { color: #ea0b8c; font-family: monospace; } /* e.g., \n, \t, \\ */ 107 | pre.sh_sourceCode .sh_comment { color: #e9ba2a; font-style: italic; } /* comments */ 108 | pre.sh_sourceCode .sh_number { color: #ea0b8c; } /* literal numbers */ 109 | pre.sh_sourceCode .sh_preproc { color: #508395; font-weight: bold; } /* e.g., #include, import */ 110 | pre.sh_sourceCode .sh_symbol { color: #832e2f; } /* */ 111 | pre.sh_sourceCode .sh_function { color: black; font-weight: bold; } /* function calls and declarations */ 112 | pre.sh_sourceCode .sh_cbracket { color: #832e2f; } /* block brackets (e.g., {, }) */ 113 | pre.sh_sourceCode .sh_todo { font-weight: bold; background-color: #5fb8dd; } /* TODO and FIXME */ 114 | 115 | /* Predefined variables and functions (for instance glsl) */ 116 | pre.sh_sourceCode .sh_predef_var { color: #508395; } 117 | pre.sh_sourceCode .sh_predef_func { color: #508395; font-weight: bold; } 118 | 119 | /* for OOP */ 120 | pre.sh_sourceCode .sh_classname { color: #92278f; } 121 | 122 | 123 | /* other */ 124 | pre.sh_sourceCode .sh_section { color: black; font-weight: bold; } 125 | pre.sh_sourceCode .sh_paren { color: #832e2f; } 126 | pre.sh_sourceCode .sh_attribute { color: #50886b; } 127 | 128 | -------------------------------------------------------------------------------- /what_is_awesome/01_awesome_defined.md: -------------------------------------------------------------------------------- 1 | !SLIDE bullets incremental 2 | # Awesome Defined # 3 | * Act Like a First Class App 4 | * Play Well With Others 5 | * Be Helpful 6 | 7 | -------------------------------------------------------------------------------- /what_is_awesome/02_first_class.md: -------------------------------------------------------------------------------- 1 | !SLIDE bullets incremental subsection 2 | # (1) Act First Class # 3 | 4 | !SLIDE bullets incremental 5 | # Act First Class # 6 | * It's not a "script" 7 | * ...it's an _application_ 8 | * Approach it as you would first-class application 9 | 10 | !SLIDE bullets incremental 11 | # How? 12 | * Decide to have a _real_ UI 13 | * Decide to use good design, write documentation, and use a real distribution system 14 | * Decide to *care* 15 | * If you plan ahead, this stuff isn't that hard 16 | -------------------------------------------------------------------------------- /what_is_awesome/03_play_well.md: -------------------------------------------------------------------------------- 1 | !SLIDE subsection 2 | # (2) Play Well With Others 3 | 4 | !SLIDE bullets incremental 5 | # The UNIX Way 6 | ## [http://www.faqs.org/docs/artu/ch01s06.html](http://www.faqs.org/docs/artu/ch01s06.html) ## 7 | > _(ii) Expect the output of every program to become the input to another, as yet unknown, program. Don't clutter output with extraneous information. Avoid stringently columnar or binary input formats. Don't insist on interactive input._ 8 | 9 | !SLIDE bullets incremental 10 | # The UNIX Way ## 11 | * grepable (One "record" per line) 12 | * cutable (delimited "fields") 13 | * exit codes! (0 for success, nonzero for failure) 14 | * messaging (stderr) vs. output (stdout) 15 | 16 | !SLIDE commandline incremental smaller 17 | # Example - Plays Horribly With Others # 18 | 19 | $ mvn test -Dtest=TestHeatingComparison 20 | [INFO] Scanning for projects... 21 | [INFO] ------------------------------------------------------------------------ 22 | [INFO] Building Standalone Core Library 23 | [INFO] task-segment: [test] 24 | [INFO] ------------------------------------------------------------------------ 25 | [INFO] [resources:copy-resources {execution: default}] 26 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 27 | [INFO] skip non existing resourceDirectory /Users/davec/Projects/opower/core/null/null/trunk/src/main/resources/webclientconfig/clients/null/assets 28 | [INFO] [svn-revision-number:revision {execution: default}] 29 | [INFO] inspecting /Users/davec/Projects/opower/core 30 | [INFO] [resources:resources {execution: default-resources}] 31 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 32 | [INFO] Copying 9 resources 33 | [INFO] Copying 1 resource 34 | [INFO] Copying 332 resources to patches/core 35 | [INFO] Copying 23 resources to postpatches/core 36 | [INFO] [compiler:compile {execution: default-compile}] 37 | [INFO] Compiling 16 source files to /Users/davec/Projects/opower/core/target/classes 38 | [INFO] [checkstyle:checkstyle {execution: checkstyle}] 39 | [INFO] Starting audit... 40 | Audit done. 41 | 42 | [INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'. 43 | [INFO] Setting property: velocimacro.messages.on => 'false'. 44 | [INFO] Setting property: resource.loader => 'classpath'. 45 | [INFO] Setting property: resource.manager.logwhenfound => 'false'. 46 | [INFO] ************************************************************** 47 | [INFO] Starting Jakarta Velocity v1.4 48 | [INFO] RuntimeInstance initializing. 49 | [INFO] Default Properties File: org/apache/velocity/runtime/defaults/velocity.properties 50 | [INFO] Default ResourceManager initializing. (class org.apache.velocity.runtime.resource.ResourceManagerImpl) 51 | [INFO] Resource Loader Instantiated: org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader 52 | 53 | !SLIDE commandline smaller 54 | # Plays Horribly With Others (con't) # 55 | 56 | $ mvn test -Dtest=TestHeatingComparison # con't 57 | [INFO] ClasspathResourceLoader : initialization starting. 58 | [INFO] ClasspathResourceLoader : initialization complete. 59 | [INFO] ResourceCache : initialized. (class org.apache.velocity.runtime.resource.ResourceCacheImpl) 60 | [INFO] Default ResourceManager initialization complete. 61 | [INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Literal 62 | [INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Macro 63 | [INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Parse 64 | [INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Include 65 | [INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Foreach 66 | [INFO] Created: 20 parsers. 67 | [INFO] Velocimacro : initialization starting. 68 | [INFO] Velocimacro : adding VMs from VM library template : VM_global_library.vm 69 | [ERROR] ResourceManager : unable to find resource 'VM_global_library.vm' in any resource loader. 70 | [INFO] Velocimacro : error using VM library template VM_global_library.vm : org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'VM_global_library.vm' 71 | [INFO] Velocimacro : VM library template macro registration complete. 72 | [INFO] Velocimacro : allowInline = true : VMs can be defined inline in templates 73 | [INFO] Velocimacro : allowInlineToOverride = false : VMs defined inline may NOT replace previous VM definitions 74 | [INFO] Velocimacro : allowInlineLocal = false : VMs defined inline will be global in scope if allowed. 75 | [INFO] Velocimacro : initialization complete. 76 | [INFO] Velocity successfully started. 77 | [INFO] [resources:testResources {execution: default-testResources}] 78 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 79 | [INFO] Copying 501 resources 80 | [INFO] Copying 9 resources 81 | [INFO] [compiler:testCompile {execution: default-testCompile}] 82 | [INFO] Compiling 1 source file to /Users/davec/Projects/opower/core/target/test-classes 83 | [INFO] [surefire:test {execution: default-test}] 84 | 85 | !SLIDE commandline smaller 86 | # Plays Horribly With Others (con't) # 87 | 88 | $ mvn test -Dtest=TestHeatingComparison # still con't 89 | [INFO] Surefire report directory: /Users/davec/Projects/opower/core/target/surefire-reports 90 | 91 | ------------------------------------------------------- 92 | T E S T S 93 | ------------------------------------------------------- 94 | Running poscore.model.TestHeatingComparison 95 | Tests run: 6, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.133 sec <<< FAILURE! 96 | 97 | Results : 98 | 99 | Failed tests: 100 | testSetters(poscore.model.TestHeatingComparison) 101 | 102 | Tests run: 6, Failures: 1, Errors: 0, Skipped: 0 103 | 104 | [INFO] ------------------------------------------------------------------------ 105 | [ERROR] BUILD FAILURE 106 | [INFO] ------------------------------------------------------------------------ 107 | [INFO] There are test failures. 108 | 109 | Please refer to /Users/davec/Projects/opower/core/target/surefire-reports for the individual test results. 110 | [INFO] ------------------------------------------------------------------------ 111 | [INFO] For more information, run Maven with the -e switch 112 | [INFO] ------------------------------------------------------------------------ 113 | [INFO] Total time: 13 seconds 114 | [INFO] Finished at: Fri May 07 13:50:01 EDT 2010 115 | [INFO] Final Memory: 54M/527M 116 | [INFO] ------------------------------------------------------------------------ 117 | 118 | $ echo $? 119 | 0 120 | 121 | !SLIDE commandline incremental 122 | # Example - Plays Well With Others # 123 | 124 | $ svn stat 125 | M Rakefile 126 | C README.rdoc 127 | ? bin/my_cmd 128 | C test/tc_cmd.rb 129 | $ vi `svn stat | grep ^C | awk '{print $2}'` 130 | # Now editing all files with conflicts 131 | $ svn stat | grep ^C | awk '{print $2}' | xargs svn resolved 132 | Resolved conflicted state of 'README.rdoc' 133 | Resolved conflicted state of 'test/tc_cmd.rb' 134 | 135 | -------------------------------------------------------------------------------- /what_is_awesome/04_be_helpful.md: -------------------------------------------------------------------------------- 1 | !SLIDE bullets incremental subsection 2 | # (3) Be helpful 3 | 4 | !SLIDE bullets incremental 5 | # Be helpful 6 | * Help users know how to use and understand your app 7 | * Don't punish on user error - be helpful 8 | 9 | !SLIDE commandline smaller incremental 10 | # Example - Not Helpful 11 | 12 | $ latex 13 | This is pdfeTeX, Version 3.141592-1.21a-2.2 (Web2C 7.5.4) 14 | ** 15 | ^C 16 | $ latex help 17 | This is pdfeTeX, Version 3.141592-1.21a-2.2 (Web2C 7.5.4) 18 | entering extended mode 19 | ! I can't find file `help'. 20 | <*> help 21 | 22 | Please type another input file name:^C 23 | ! I can't find file `help'. 24 | <*> help 25 | 26 | Please type another input file name: ^D 27 | ! Emergency stop. 28 | <*> help 29 | 30 | No pages of output. 31 | Transcript written on texput.log. 32 | 33 | !SLIDE commandline small incremental 34 | # Helpful 35 | $ git 36 | usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] 37 | [-p|--paginate|--no-pager] 38 | [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] 39 | [--help] COMMAND [ARGS] 40 | 41 | The most commonly used git commands are: 42 | add Add file contents to the index 43 | bisect Find by binary search the change that introduced a bug 44 | 45 | tag Create, list, delete or verify a tag object signed with GPG 46 | 47 | See 'git help COMMAND' for more information on a specific command. 48 | 49 | !SLIDE bullets incremental 50 | # Two classes of apps 51 | * *Simple* - does-one-thing-UNIX-style app 52 | * Think `ls` or `grep` 53 | * *Command Suite* - complex, does a lot 54 | * Think `git` or `gem` 55 | 56 | !SLIDE bullets incremental 57 | # Be Helpful # 58 | ## Simple ## 59 | ### `my_cmd` 60 | 61 | * Don't do anything destructive by default 62 | * Show a brief help statement 63 | 64 | !SLIDE bullets incremental 65 | # Be Helpful # 66 | ## Simple ## 67 | ### `my_cmd -h` 68 | 69 | * Show a fuller help statement 70 | * Also support `--help` 71 | 72 | !SLIDE bullets incremental 73 | # Be Helpful # 74 | ## Command Suite ## 75 | ### `my_cmd` 76 | 77 | * List available commands 78 | * List globally-applicable options 79 | * The command `help` should do the same (e.g. `my_cmd help`) 80 | 81 | !SLIDE bullets incremental 82 | # Be Helpful # 83 | ## Command Suite ## 84 | ### `my_cmd help command_name` 85 | 86 | * help on `command_name` 87 | * i.e. what it does 88 | * and the options available 89 | 90 | !SLIDE bullets incremental 91 | # Oh, and useful error messages 92 | * "undefined method `[]' for nil:NilClass (NoMethodError)" 93 | * Ruby allows *any number* of `if` statements 94 | -------------------------------------------------------------------------------- /what_is_awesome/cuke_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/what_is_awesome/cuke_fail.png -------------------------------------------------------------------------------- /what_is_awesome/cuke_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davetron5000/awesome-cli-ruby/71a4be7a94970527a3de1c705130ba1f7d3dd3e3/what_is_awesome/cuke_pass.png --------------------------------------------------------------------------------