Lone Star Ruby Conf
@cowboyd
The FrontSide
July 18, 2013
I'm not going to talk about placing things into other things
-- Jeffrey Lebowski
(comand line interface)
Slop, Trollop, Mixlib, Map, Optitron, Rake, Thor, GLI, and even good old OptionParser
and it is puts()
and ui.info()
and shell.say()
$ jenkins -h nodes
usage: jenkins nodes
--host [HOST] connect to jenkins server on this host
--port [PORT] connect to jenkins server on this port
--ssl connect to jenkins server with ssl
--username [USERNAME] connect to jenkins server with username
--password [PASSWORD] connect to jenkins server with password
class CLI < Thor
module Formatting
module ClassMethods
def task_help(shell, task_name)
meth = normalize_task_name(task_name)
task = all_tasks[meth]
handle_no_task_error(meth) unless task
shell.say "usage: #{banner(task)}"
shell.say
class_options_help(shell, nil => task.options.map { |_, o| o })
end
...
end
end
end
** [out :: 198.61.173.225] .
** [out :: 198.61.173.225] .
** [out :: 198.61.173.225] .
** [out :: 198.61.173.225] Using rake (10.1.0)
** [out :: 198.61.173.225]
** [out :: 198.61.173.225] Using i18n (0.6.1)
** [out :: 198.61.173.225]
** [out :: 198.61.173.225] Using multi_json (1.7.7)
** [out :: 198.61.173.225]
** [out :: 198.61.173.225] Using activesupport (3.2.13)
** [out :: 198.61.173.225]
** [out :: 198.61.173.225] Using builder (3.0.4)
Fetching: rumm-0.0.18.gem (100%)
Successfully installed rumm-0.0.18
'home/cowboyd' => #<Pathname:/home/cowboyd>
decoder: Pathname()
'198.61.173.225' =>#<IPAddr: IPv4:198.61.173.225>
decoder: IPAddr.new()
'cowboyd@thefrontside.net' => #<User id: 1>
decoder: User.find_by_email()
They need to be valid in the bounded context in which they are being used
Colorless green dreams sleep furiously
this is well-formed nonsense
#<Pathname:/home/cowboyd>
.exist?
.owned?
As a community, we have a horrible bias towards the output side of things to the neglect of the input
OptParser::DSL != App Framework
Option parsing is a solved problem. It's the other 98% of the application that sucks
(contains every gotcha and then some)
Attack!
class Command
attr_reader :argv, :input, :output, :log, :env
def initialize(argv, input, output, log, env)
@argv, @input, @output, @log, @env = argv, input, output, log, env
end
end
A Command
def call(command)
# do stuff before
yield command #invoke next middleware
# do stuff after
end
def call(cmd)
yield Command.new(cmd.argv, cmd.input, EmojiFilter.new(cmd.output), cmd.env)
end
class CLI < Thor
module Formatting
module ClassMethods
def task_help(shell, task_name)
meth = normalize_task_name(task_name)
task = all_tasks[meth]
handle_no_task_error(meth) unless task
shell.say "usage: #{banner(task)}"
shell.say
class_options_help(shell, nil => task.options.map { |_, o| o })
end
...
end
end
end
usage: <%= banner task %>
<% indent 4 do %>
<%= render 'options' %>
<% end %>
Fetching: <%= gem.filename %> <%= gem.progress %>
Successfully installed <%= gem.name %>
What is the essence of your input?
app/forms/loadbalancers/create_form.rb
class Loadbalancers::CreateForm < MVCLI::Form
input :name, String, default: -> { naming.generate_name 'l', 'b' }
input :port, Integer, default: 80, decode: ->(s) { Integer s }
input :protocol, String, default: 'HTTP', decode: :upcase
validates(:port) {|port| port >=0 && port < 65535 }
validates(:protocol) {|p| ['HTTP','HTTPS','TCP', 'UDP'].member? p }
end
deriving an option parser for this is left as an exercise for the reader
and that's awesome.
stdin
=> request.body
stdout
=> response.body
argv
=> request.uri
exit_status
=> response.status_code
ENV
=> request.headers
app/routes.rb
macro /(-v|--version)/ => "version"
match 'version' => proc {|cmd| cmd.output << "#{Rumm::VERSION}\n" }
match 'login' => 'authentication#login'
match 'logout' => 'authentication#logout'
resources :servers do
resources :attachments
end
resources :loadbalancers do
resources :nodes
end
resources :dbinstances do
resources :users
resources :databases
end
resources :containers do
resources :files
end
resources :volumes
describes about 50 commands!
Because a command is a process, not an object