RubyConf India 2012Pune                      Crafting Beautiful               Command Line Applications                   ...
Shishir Das@shishirdasThoughtWorks
Nikhil Mungel@hyfatherThoughtWorks
Why the command line?
Why the command line?
eyheroku              knife                            puppet  pg_ctl    mysql      rails                               git
bash       zsh        powershell           eyheroku              knife                            puppet  pg_ctl    mysql ...
eyheroku                knife                              puppet  pg_ctl    mysql        rails                           ...
Lightweight
Lightweight
Scriptable
Consistent UX
Consistent UX    *
What makes a CLI app       good?
The Luxury of  Ignorance
~ > rails
~ > railsUsage: rails COMMAND [ARGS]The most common rails commands are: generate    Generate new code (short-cut alias: "g...
~ > rails generate
~ > rails generateUsage: rails generate GENERATOR [args] [options]Please choose a generator below.Rails:  assets  controll...
~ > ssh
~ > sshusage: ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-ccipher_spec]           [-D [bind_address:]port] [-F con...
Least Astonishment
~ > bundle
~ > bundleFetching source index for https://rubygems.org/Using rake (0.9.2.2)Installing i18n (0.6.0)Installing multi_json ...
Reversibility
~ > git commit -am "Added a new framework"
~ > git commit -am "Added a new framework"[master b4a2130] Added a new framework  2 files changed, 1012 insertions(+), 529...
~ > git commit -am "Added a new framework"[master b4a2130] Added a new framework  2 files changed, 1012 insertions(+), 529...
~ > git commit -am "Added a new framework"[master b4a2130] Added a new framework  2 files changed, 1012 insertions(+), 529...
Config Files
~ > knife cookbook upload apache2 -o /User/foo/cookbooks --server-url http://chef-server:4000 --key /etc/chef/my.key --col...
~ > knife cookbook upload apache2 -o /User/foo/cookbooks --server-url http://chef-server:4000 --key /etc/chef/my.key --col...
~ > knife cookbook upload apache2 -o /User/foo/cookbooks --server-url http://chef-server:4000 --key /etc/chef/my.key --col...
Graceful Failure
~ > git pull
~ > git pullYou asked me to pull without telling me which branch youwant to merge with, and branch.master.merge inyour con...
No hidden states
Confirmations should   be scriptable
~ > gem uninstall rspec
~ > gem uninstall rspecYou have requested to uninstall the gem:	 rspec-2.8.0cucumber-1.1.4 depends on [rspec (>= 2.7.0)]If...
~ > gem uninstall rspecYou have requested to uninstall the gem:	 rspec-2.8.0cucumber-1.1.4 depends on [rspec (>= 2.7.0)]If...
~ > gem uninstall rspecYou have requested to uninstall the gem:	 rspec-2.8.0cucumber-1.1.4 depends on [rspec (>= 2.7.0)]If...
Honor Piping
IO#tty?
Why Ruby?
Scripting Language
Easy textmanipulation
Good Abstractions
Plethora of gems to     help you
Examples to learn      from
Structure of CLI      apps
Input   Execution   Output
Input   Execution   Output STDIO = UI/UX
Input
Input        STDIN
Input        ARGV/ENV          STDIN
Input        ARGV/ENV          STDIN
Input        ARGV/ENV          STDIN
Input        OptionParser        ARGV/ENV          STDIN
Input        OptionParser        ARGV/ENV          STDIN
Input   Libraries        OptionParser        ARGV/ENV          STDIN
options = {}OptionParser.new do |opts|  opts.banner = "Usage: example.rb [options]"  opts.on("-v", "--[no-]verbose", "Run ...
options = {}OptionParser.new do |opts|  opts.banner = "Usage: example.rb [options]"  opts.on("-v", "--[no-]verbose", "Run ...
options = {}OptionParser.new do |opts|  opts.banner = "Usage: example.rb [options]"  opts.on("-v", "--[no-]verbose", "Run ...
options = {}OptionParser.new do |opts|  opts.banner = "Usage: example.rb [options]"  opts.on("-v", "--[no-]verbose", "Run ...
options = {}OptionParser.new do |opts|  opts.banner = "Usage: example.rb [options]"  opts.on("-v", "--[no-]verbose", "Run ...
The Mixlib Suite
class MyCLIApp  include Mixlib::CLI  option :config_file,    :short => "-c CONFIG",    :description => "Configuration file...
class MyCLIApp  include Mixlib::CLI  option :config_file,    :short => "-c CONFIG",    :description => "Configuration file...
class MyCLIApp  include Mixlib::CLI  option :config_file,    :short => "-c CONFIG",    :description => "Configuration file...
config.rbserver_url “http://server.remote”username   “elvis”password   “hotdog”
config.rbserver_url “http://server.remote”username   “elvis”password   “hotdog”class MyConfig  extend(Mixlib::Config)  ser...
config.rbserver_url “http://server.remote”username   “elvis”password   “hotdog”class MyConfig  extend(Mixlib::Config)  ser...
Thor
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
class Test < Thor  desc " FILE", "an example task"  method_option :delete,                :aliases => "-d",               ...
Testing
Input   Execution   Output
Input   Execution   Output
Input   Execution   OutputMostly third party libraries
Input   Execution   OutputTest::Unit, rspec etc.
Input   Execution   Output         System
SystemFile System    Network   Process
Isolated       Environments                              ContainerUser               Application           File           ...
Streams & Signals
STDOUTSTDIN        App              STDERR
STDOUTSTDIN        App              STDERR
STDOUTSTDIN        App                STDERR                  STDOUT & STDERR        STDIN                 App
Mixlib::Shellout> ls = Mixlib::ShellOut.new("ls")> ls.run_command> ls.stdout“init.elnREADME.mdn”
Mixlib::Shellout> ls = Mixlib::ShellOut.new("ls")> ls.run_command> ls.stdout“init.elnREADME.mdn”
Plugin Architecture
Hooks
Logging
GNU CLI standards‘--version’ and ‘--help’Input/Output Files --  -O for Output Files
“CLI apps could bethe first consumers                 ” of your services.
“CLI apps could bethe first consumers                 ” of your services.
“CLI apps could bethe first consumers                               ” of your services.      Testing    Development      C...
Ideas    CLI app for a REST API: Github    SCM Plugins for Knife    Transform Rake scripts into first    class CLI appsShi...
Questions              Comments         SuggestionsShishir                      Nikhil@shishirdas               @hyfather
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Crafting Beautiful CLI Applications in Ruby
Próximos SlideShares
Carregando em…5
×

Crafting Beautiful CLI Applications in Ruby

13.336 visualizações

Publicada em

Shishir Das (@shishirdas) and Nikhil Mungel (@hyfather) presented this at RubyConf India 2012.

Publicada em: Tecnologia
  • Seja o primeiro a comentar

Crafting Beautiful CLI Applications in Ruby

  1. 1. RubyConf India 2012Pune Crafting Beautiful Command Line Applications using Ruby Shishir Nikhil @shishirdas @hyfather
  2. 2. Shishir Das@shishirdasThoughtWorks
  3. 3. Nikhil Mungel@hyfatherThoughtWorks
  4. 4. Why the command line?
  5. 5. Why the command line?
  6. 6. eyheroku knife puppet pg_ctl mysql rails git
  7. 7. bash zsh powershell eyheroku knife puppet pg_ctl mysql rails git
  8. 8. eyheroku knife puppet pg_ctl mysql rails git Developers and QAs
  9. 9. Lightweight
  10. 10. Lightweight
  11. 11. Scriptable
  12. 12. Consistent UX
  13. 13. Consistent UX *
  14. 14. What makes a CLI app good?
  15. 15. The Luxury of Ignorance
  16. 16. ~ > rails
  17. 17. ~ > railsUsage: rails COMMAND [ARGS]The most common rails commands are: generate Generate new code (short-cut alias: "g") console Start the Rails console (short-cut alias: "c") server Start the Rails server (short-cut alias: "s")~ >
  18. 18. ~ > rails generate
  19. 19. ~ > rails generateUsage: rails generate GENERATOR [args] [options]Please choose a generator below.Rails: assets controller generator helper integration_test mailer migration model observer performance_test plugin~ >
  20. 20. ~ > ssh
  21. 21. ~ > sshusage: ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-ccipher_spec] [-D [bind_address:]port] [-F configfile] [-I pkcs11] [-i identity_file] [-L [bind_address:]port:host:hostport] [-l login_name] [-O ctl_cmd] [-o option] [-p port] [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] [user@]hostname [command]~ >
  22. 22. Least Astonishment
  23. 23. ~ > bundle
  24. 24. ~ > bundleFetching source index for https://rubygems.org/Using rake (0.9.2.2)Installing i18n (0.6.0)Installing multi_json (1.0.4)Installing activesupport (3.2.1)Installing builder (3.0.0)Installing activemodel (3.2.1)Installing erubis (2.7.0)~ >
  25. 25. Reversibility
  26. 26. ~ > git commit -am "Added a new framework"
  27. 27. ~ > git commit -am "Added a new framework"[master b4a2130] Added a new framework 2 files changed, 1012 insertions(+), 529 deletions(-)~ >
  28. 28. ~ > git commit -am "Added a new framework"[master b4a2130] Added a new framework 2 files changed, 1012 insertions(+), 529 deletions(-)~ > git reset HEAD^
  29. 29. ~ > git commit -am "Added a new framework"[master b4a2130] Added a new framework 2 files changed, 1012 insertions(+), 529 deletions(-)~ > git reset HEAD^Unstaged changes after reset:M javascript/framework.jsM javascripts/support.js~ >
  30. 30. Config Files
  31. 31. ~ > knife cookbook upload apache2 -o /User/foo/cookbooks --server-url http://chef-server:4000 --key /etc/chef/my.key --color~ >
  32. 32. ~ > knife cookbook upload apache2 -o /User/foo/cookbooks --server-url http://chef-server:4000 --key /etc/chef/my.key --color~ >~ > cat knife.rb log_level                :infolog_location             STDOUTnode_name                blitzclient_key               /Users/shishir/.chef/blitz.pemvalidation_client_name   chef-validatorvalidation_key           /etc/chef/validation.pemchef_server_url          http://10.10.100.202:4000cache_type               BasicFilecache_options( :path => /Users/shishir/.chef/checksums )~ >
  33. 33. ~ > knife cookbook upload apache2 -o /User/foo/cookbooks --server-url http://chef-server:4000 --key /etc/chef/my.key --color~ >~ > cat knife.rb log_level                :infolog_location             STDOUTnode_name                blitzclient_key               /Users/shishir/.chef/blitz.pemvalidation_client_name   chef-validatorvalidation_key           /etc/chef/validation.pemchef_server_url          http://10.10.100.202:4000cache_type               BasicFilecache_options( :path => /Users/shishir/.chef/checksums )~ >~ > knife cookbook upload apache2
  34. 34. Graceful Failure
  35. 35. ~ > git pull
  36. 36. ~ > git pullYou asked me to pull without telling me which branch youwant to merge with, and branch.master.merge inyour configuration file does not tell me, either. Pleasespecify which branch you want to use on the command line andtry again (e.g. git pull <repository> <refspec>).See git-pull(1) for details.If you often merge with the same branch, you may want touse something like the following in your configuration file: [branch "master"] remote = <nickname> merge = <remote-ref> [remote "<nickname>"] url = <url> fetch = <refspec>See git-config(1) for details.
  37. 37. No hidden states
  38. 38. Confirmations should be scriptable
  39. 39. ~ > gem uninstall rspec
  40. 40. ~ > gem uninstall rspecYou have requested to uninstall the gem: rspec-2.8.0cucumber-1.1.4 depends on [rspec (>= 2.7.0)]If you remove this gems, one or more dependencies will not be met.Continue with Uninstall? [Yn] n
  41. 41. ~ > gem uninstall rspecYou have requested to uninstall the gem: rspec-2.8.0cucumber-1.1.4 depends on [rspec (>= 2.7.0)]If you remove this gems, one or more dependencies will not be met.Continue with Uninstall? [Yn] n~ > gem uninstall -I rspec
  42. 42. ~ > gem uninstall rspecYou have requested to uninstall the gem: rspec-2.8.0cucumber-1.1.4 depends on [rspec (>= 2.7.0)]If you remove this gems, one or more dependencies will not be met.Continue with Uninstall? [Yn] n~ > gem uninstall -I rspecSuccessfully uninstalled rspec-2.8.0
  43. 43. Honor Piping
  44. 44. IO#tty?
  45. 45. Why Ruby?
  46. 46. Scripting Language
  47. 47. Easy textmanipulation
  48. 48. Good Abstractions
  49. 49. Plethora of gems to help you
  50. 50. Examples to learn from
  51. 51. Structure of CLI apps
  52. 52. Input Execution Output
  53. 53. Input Execution Output STDIO = UI/UX
  54. 54. Input
  55. 55. Input STDIN
  56. 56. Input ARGV/ENV STDIN
  57. 57. Input ARGV/ENV STDIN
  58. 58. Input ARGV/ENV STDIN
  59. 59. Input OptionParser ARGV/ENV STDIN
  60. 60. Input OptionParser ARGV/ENV STDIN
  61. 61. Input Libraries OptionParser ARGV/ENV STDIN
  62. 62. options = {}OptionParser.new do |opts| opts.banner = "Usage: example.rb [options]" opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v endend.parse!
  63. 63. options = {}OptionParser.new do |opts| opts.banner = "Usage: example.rb [options]" opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v endend.parse!~ > ./opt.rb --helpUsage: example.rb [options]   -v, --[no-]verbose               Run verbosely
  64. 64. options = {}OptionParser.new do |opts| opts.banner = "Usage: example.rb [options]" opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v endend.parse!~ > ./opt.rb --helpUsage: example.rb [options]   -v, --[no-]verbose               Run verbosely
  65. 65. options = {}OptionParser.new do |opts| opts.banner = "Usage: example.rb [options]" opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v endend.parse!~ > ./opt.rb --helpUsage: example.rb [options]   -v, --[no-]verbose               Run verbosely
  66. 66. options = {}OptionParser.new do |opts| opts.banner = "Usage: example.rb [options]" opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v endend.parse!~ > ./opt.rb --helpUsage: example.rb [options]   -v, --[no-]verbose               Run verbosely
  67. 67. The Mixlib Suite
  68. 68. class MyCLIApp include Mixlib::CLI option :config_file, :short => "-c CONFIG", :description => "Configuration file", :required => trueend~ > ./mycliapp.rb --helpUsage: ./mix.rb (options) -c, --config CONFIG Configuration file (required) -h, --help Show this message
  69. 69. class MyCLIApp include Mixlib::CLI option :config_file, :short => "-c CONFIG", :description => "Configuration file", :required => trueend~ > ./mycliapp.rb --helpUsage: ./mix.rb (options) -c, --config CONFIG Configuration file (required) -h, --help Show this message
  70. 70. class MyCLIApp include Mixlib::CLI option :config_file, :short => "-c CONFIG", :description => "Configuration file", :required => trueend~ > ./mycliapp.rb --helpUsage: ./mix.rb (options) -c, --config CONFIG Configuration file (required) -h, --help Show this message
  71. 71. config.rbserver_url “http://server.remote”username “elvis”password “hotdog”
  72. 72. config.rbserver_url “http://server.remote”username “elvis”password “hotdog”class MyConfig extend(Mixlib::Config) server_url http://server.local username king password burgerendMyConfig.from_file(‘config.rb’)irb> MyConfig.server_url# ‘http://server.remote’
  73. 73. config.rbserver_url “http://server.remote”username “elvis”password “hotdog”class MyConfig extend(Mixlib::Config) server_url http://server.local username king password burgerendMyConfig.from_file(‘config.rb’)irb> MyConfig.server_url# ‘http://server.remote’
  74. 74. Thor
  75. 75. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend
  76. 76. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  77. 77. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  78. 78. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  79. 79. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  80. 80. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  81. 81. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  82. 82. class Test < Thor desc " FILE", "an example task" method_option :delete, :aliases => "-d", :desc => "Delete the file" def example(file) endend~ > myapp help test:exampleUsage: thor test:example FILEOptions: -d, [--delete=DELETE] # Delete the file after parsing itan example task
  83. 83. Testing
  84. 84. Input Execution Output
  85. 85. Input Execution Output
  86. 86. Input Execution OutputMostly third party libraries
  87. 87. Input Execution OutputTest::Unit, rspec etc.
  88. 88. Input Execution Output System
  89. 89. SystemFile System Network Process
  90. 90. Isolated Environments ContainerUser Application File Memory Processes System
  91. 91. Streams & Signals
  92. 92. STDOUTSTDIN App STDERR
  93. 93. STDOUTSTDIN App STDERR
  94. 94. STDOUTSTDIN App STDERR STDOUT & STDERR STDIN App
  95. 95. Mixlib::Shellout> ls = Mixlib::ShellOut.new("ls")> ls.run_command> ls.stdout“init.elnREADME.mdn”
  96. 96. Mixlib::Shellout> ls = Mixlib::ShellOut.new("ls")> ls.run_command> ls.stdout“init.elnREADME.mdn”
  97. 97. Plugin Architecture
  98. 98. Hooks
  99. 99. Logging
  100. 100. GNU CLI standards‘--version’ and ‘--help’Input/Output Files -- -O for Output Files
  101. 101. “CLI apps could bethe first consumers ” of your services.
  102. 102. “CLI apps could bethe first consumers ” of your services.
  103. 103. “CLI apps could bethe first consumers ” of your services. Testing Development Cheap No rich UI, functional Fast tests
  104. 104. Ideas CLI app for a REST API: Github SCM Plugins for Knife Transform Rake scripts into first class CLI appsShishir Nikhil@shishirdas @hyfather
  105. 105. Questions Comments SuggestionsShishir Nikhil@shishirdas @hyfather

×