1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
module CLI class Session # # Initialize # def initialize @commands = [ { :title => 'Help', :description => 'Output command help and usage information.', :pattern => /help|usage|commands/, :syntax => 'help | usage | commands', :callback => 'self.usage', }, { :title => 'Source code analysis', :description => 'Quickly reports on the current source code round within this Shard installation.', :pattern => /analyze source/, :syntax => 'analyze source', :callback => 'self.analyze_source', } ] end # # Start the session. # def start loop do print "Shard >> " @@args = gets @commands.each do |command| eval(command[:callback]) if command[:pattern].match @@args end end end # # Output command usage. # def usage @commands.each{|command| puts self.command_usage(command)} end # # Get command usage. # def command_usage(command) <<-USAGE #{command[:title]} #{command[:description]} Syntax: '#{command[:syntax]}' USAGE end # # Analyze source. # def analyze_source Dir['**/*.rb'].each{ |file| puts `wc -l #{file}` } end end end
Refactorings
No refactoring yet !
chrismo
September 23, 2008, September 23, 2008 21:06, permalink
I did something similar-ish (I think), in some example code for my old XML Serialization library. You can see it here, via Google Code Search: http://tinyurl.com/4hwb8y. Look at line 109 for a good entry point.
Adam
September 24, 2008, September 24, 2008 15:35, permalink
Just having some fun with some DSLs. You could go even further using Procs instead of instance methods.
session.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
require "rubygems" require "activesupport" require "ostruct" module CLI class Session < Base command.usage /help|usage|commands/ do |command| command.title = "Help" command.description = "Output command help and usage information." end command.analyze_source /analyze source/ do |command| command.title = "Source code analysis" command.description = %{Quickly reports on the current source code round within this Shard installation.} end def usage commands.each do |command| puts command.usage end end def analyze_source Dir['**/*.rb'].each{ |file| puts `wc -l #{file}` } end end end if __FILE__ == $0 CLI::Session.start end
base.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
module CLI class Base class << self def commands read_inheritable_attribute(:commands) || write_inheritable_attribute(:commands, []) end def instance read_inheritable_attribute(:instance) || write_inheritable_attribute(:instance, new) end def command(&block) CommandProxy.new(self, &block) end def gets print "Shard >> " Kernel.gets end def start loop do arg = gets commands.each do |command| instance.send(command.callback) if command.pattern.match(arg) end end end end def commands self.class.commands end end end
command_proxy.rb
1 2 3 4 5 6 7 8 9 10 11 12 13
module CLI class CommandProxy < Struct.new(:session) def method_missing(callback, pattern, &block) options = { :callback => callback, :pattern => pattern, :syntax => pattern.to_s[/mix:(.*)\)/, 1].split("|").join(" ") } session.commands << Command.new(options, &block) end end end
command.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
module CLI class Command < OpenStruct def initialize(*args) super(*args) yield(self) end def usage %{ #{title} #{description} Syntax: #{syntax} } end end end
Tj Holowaychuk
September 24, 2008, September 24, 2008 17:57, permalink
I am defiantly not used to the 'ruby way' of doing things, but your example (thanks btw) is much larger, what benefits would that provide? For example I refactored the main CLI::Session portion to allow modules to implement the cli 'hook' to register its own commands like below, would you consider this bad practice? Personally I would not see the need to be extreme with tons of objects when it really just registers a callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
module Builder # # Implementation of hook::cli. # def Builder.cli [ { :title => 'Source code analysis', :description => 'Quickly reports on the current source code round within this Shard installation.', :pattern => /analyze source/, :syntax => 'analyze source', :callback => 'Builder.analyze_source', } ] end # # Analyze source. # def Builder.analyze_source results = [] files = Dir['**/*.rb'].each{ |file| results << `wc -l #{file}` } puts results.sort end end
Again I am a Ruby newb (two days now), but the help is very appreciated in getting to understand the magic behind Ruby and more elegant ways to take care of issues.
I am looking to allow classes to extend CLI:Session and implement their own commands in a unified manor, below is an example fused into the single class, however ideally the only command for CLI:Session would be 'help' where as the rest would simply extend this class.