F1e3ab214a976a39cfd713bc93deb10f

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.

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 !

8fb47582197b2d50cd7d297841c8cc87

chrismo

September 23, 2008, September 23, 2008 21:06, permalink

No rating. Login to rate!

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.

A8d3f35baafdaea851914b17dae9e1fc

Adam

September 24, 2008, September 24, 2008 15:35, permalink

No rating. Login to rate!

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
F1e3ab214a976a39cfd713bc93deb10f

Tj Holowaychuk

September 24, 2008, September 24, 2008 17:57, permalink

No rating. Login to rate!

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

Your refactoring





Format Copy from initial code

or Cancel