command-set 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,24 +13,34 @@ module Command
13
13
  #objects. Other methods can be overridden - especially consider
14
14
  # #get_formatter, and #prompt_user
15
15
  class BaseInterpreter
16
- attr_accessor :command_set, :out_io, :logger
17
- attr_reader :subject
16
+ attr_accessor :out_io, :logger
17
+ attr_reader :subject, :command_set
18
18
 
19
- #:section: Client app methods
20
-
21
- #Subject has an intentionally very tight interface (q.v.) the gist of
22
- #which is that you can only assign to fields that commands require, and
23
- #you can only access fields within a command that you declared that
24
- #command required.
25
- def subject_template
26
- raise "CommandSet unset!" if @command_set.nil?
27
- return prep_subject(get_subject)
19
+ def initialize
20
+ @command_set=nil
21
+ @sub_modes = []
22
+ @behavior = {
23
+ :screen_width => 76,
24
+ :warn_no_undo => true
25
+ }
26
+ @out_io = $stdout
27
+ @stop = false
28
+ @subject = nil
29
+ @logger = Logger.new($stderr)
30
+ @logger.level=Logger::FATAL
31
+ @undo_stack = UndoStack.new
32
+ @commands_pending = []
33
+ @pause_decks = Hash.new {|h,k| h[k]=[]}
28
34
  end
29
35
 
36
+ #:section: Client app methods
37
+ alias subject_template subject
38
+
30
39
  #Before running an interpreter on input, you must set the subject.
31
40
  #Get a subject object by calling subject_template, assign it's fields,
32
41
  #and then pass it into subject=
33
42
  def subject= (subject)
43
+ subject
34
44
  begin
35
45
  subject.get_image(subject_requirements())
36
46
  rescue CommandException
@@ -41,8 +51,14 @@ module Command
41
51
  @subject = subject
42
52
  end
43
53
 
54
+
55
+ def command_set=(set)
56
+ @command_set = set
57
+ @subject = prep_subject(get_subject)
58
+ end
59
+
44
60
  def fill_subject
45
- template = subject_template
61
+ template = self.subject
46
62
  yield template
47
63
  self.subject=(template)
48
64
  end
@@ -53,33 +69,17 @@ module Command
53
69
  @behavior.merge!(hash)
54
70
  end
55
71
 
56
- def initialize
57
- @command_set=nil
58
- @sub_modes = []
59
- @behavior = {
60
- :screen_width => 76,
61
- :warn_no_undo => true
62
- }
63
- @out_io = $stdout
64
- @stop = false
65
- @subject = nil
66
- @logger = Logger.new($stderr)
67
- @logger.level=Logger::FATAL
68
- @undo_stack = UndoStack.new
69
- @commands_pending = []
70
- @pause_decks = Hash.new {|h,k| h[k]=[]}
71
- end
72
-
73
72
  #:section: Command behavior related method
74
73
 
75
74
  # Puts a CommandSet ahead of the current one for processing. Useful for command
76
75
  # modes, like Cisco's IOS with configure modes, et al.
77
- def push_mode(mode)
76
+ def push_mode(nesting)
77
+ mode = nesting.pop
78
78
  unless CommandSet === mode
79
79
  raise RuntimeError, "Sub-modes must be CommandSets!"
80
80
  end
81
81
 
82
- @sub_modes.push([mode, mode.most_recent_args])
82
+ @sub_modes.push([mode, mode.most_recent_args, nesting])
83
83
  return nil
84
84
  end
85
85
 
@@ -106,7 +106,8 @@ module Command
106
106
  #interpreter.
107
107
  def prep_subject(subject)
108
108
  @command_set.add_requirements(subject)
109
- subject.required_fields(*subject_requirements())
109
+ @command_set.add_defaults(subject)
110
+ subject.required_fields(subject_requirements())
110
111
  subject.undo_stack = @undo_stack
111
112
  subject.interpreter_behavior = @behavior
112
113
  subject.chain_of_command = @commands_pending
@@ -118,7 +119,8 @@ module Command
118
119
  #You'll almost never want to override this method.
119
120
  def next_command
120
121
  setup = @commands_pending.shift
121
- setup.args_hash = default_arg_hash.merge(setup.args_hash)
122
+ setup.arg_hash = default_arg_hash.merge(setup.arg_hash)
123
+ setup.set_nesting = current_nesting + setup.set_nesting
122
124
  return setup.command_instance(current_command_set, build_subject)
123
125
  end
124
126
 
@@ -193,17 +195,22 @@ module Command
193
195
 
194
196
  def default_arg_hash
195
197
  return {} if @sub_modes.empty?
196
- return @sub_modes.last.last
198
+ return @sub_modes.last[1]
197
199
  end
198
200
 
199
201
  def current_command_set
200
202
  return @command_set if @sub_modes.empty?
201
- return @sub_modes.last.first
203
+ return @sub_modes.last[0]
204
+ end
205
+
206
+ def current_nesting
207
+ return [] if @sub_modes.empty?
208
+ return @sub_modes.last[2]
202
209
  end
203
210
 
204
211
  def build_subject
205
212
  if @subject.nil?
206
- subject=(subject_template())
213
+ self.subject=(subject_template())
207
214
  end
208
215
  return @subject
209
216
  end
@@ -1,4 +1,4 @@
1
- require 'command-set/interpreter'
1
+ require 'command-set/interpreter/base'
2
2
 
3
3
  module Command
4
4
  #A looser Subject, used by QuickInterpreter for testing purposes.
@@ -7,11 +7,11 @@ module Command
7
7
  #easier.
8
8
  class PermissiveSubject < Subject
9
9
  protected
10
- def add_accessor(name)
10
+ def add_field_writer(name)
11
11
  super
12
12
  (class << self; self; end).instance_eval do
13
13
  define_method(name) do
14
- return instance_variable_get("@#{name}")
14
+ return @fields[name]
15
15
  end
16
16
  end
17
17
  end
@@ -84,7 +84,7 @@ module Command
84
84
 
85
85
  #Passes the arguments to process_input directly to CommandSetup
86
86
  def cook_input(words)
87
- CommandSetup.new(words)
87
+ current_command_set.process_terms(words, subject)
88
88
  end
89
89
 
90
90
  def complete_input(terms, word)
@@ -1,7 +1,7 @@
1
1
  require 'readline'
2
2
  require 'command-set'
3
3
  require 'dl/import'
4
- require 'command-set/interpreter'
4
+ require 'command-set/interpreter/base'
5
5
 
6
6
  #This really locks text-interpreter down to Linux, maybe Unix-like
7
7
  #platforms, I'm thinking. A more flexible way of doing this would rock,
@@ -97,8 +97,7 @@ module Command
97
97
  rescue CommandException => ce
98
98
  output_exception("Error", ce)
99
99
  rescue Exception => e
100
- output_exception("Exception", e)
101
- pause_before_dying
100
+ self.pause_before_dying(e)
102
101
  ensure
103
102
  unless old_proc.nil?
104
103
  set_readline_completion(&old_proc)
@@ -115,7 +114,8 @@ module Command
115
114
  end
116
115
  end
117
116
 
118
- def pause_before_dying
117
+ def pause_before_dying(exception)
118
+ output_exception("Exception", exception)
119
119
  puts "Waiting for return"
120
120
  $stdin.gets
121
121
  stop
@@ -132,7 +132,7 @@ module Command
132
132
  line = new_line
133
133
  end
134
134
  line.pop if line.last.empty?
135
- return CommandSetup.new(line)
135
+ return self.current_command_set.process_terms(line, self.subject)
136
136
  end
137
137
 
138
138
  alias single_command process_input
@@ -155,29 +155,33 @@ module Command
155
155
  end
156
156
 
157
157
  def readline_complete(buffer, rl_prefix)
158
- parsed_input = split_line(buffer)
159
- prefix = parsed_input.pop
160
-
161
- #Lest this kill coverage: hide exists to fix readline's irritating
162
- #word splitting
163
- hide = prefix.sub(/#{rl_prefix}$/, "")
164
-
165
- completes = current_command_set.completion_list(parsed_input,
166
- prefix, build_subject)
167
- completes.map! do |complete|
168
- complete = complete.sub(/^#{hide}/, "")
169
- if split_line(complete).length > 1
170
- if hide.empty?
171
- '"' + complete + '"'
158
+ begin
159
+ parsed_input = split_line(buffer)
160
+ prefix = parsed_input.pop
161
+
162
+ #Lest this kill coverage: hide exists to fix readline's irritating
163
+ #word splitting
164
+ hide = prefix.sub(%r{#{rl_prefix}$}, "")
165
+
166
+ if /"#{prefix}$/ =~ buffer
167
+ hide = '"' + hide
168
+ end
169
+
170
+ completes = current_command_set.completion_list(parsed_input,
171
+ prefix, build_subject)
172
+ completes.map! do |complete|
173
+ if split_line(complete).length > 1
174
+ ('"' + complete + '"').sub(/^#{hide}/, "")
172
175
  else
173
- complete + '"'
176
+ complete.sub(/^#{hide}/, "")
174
177
  end
175
- else
176
- complete
177
178
  end
178
- end
179
179
 
180
- return completes
180
+ return completes
181
+ rescue Object => ex
182
+ #It's really irritating for an app to crap out in completion
183
+ return ["#{ex.class}: #{ex.message}", ""]
184
+ end
181
185
  end
182
186
 
183
187
  def split_line(line)
@@ -209,7 +213,7 @@ module Command
209
213
 
210
214
  prompt.sub!(*(@command_set.prompt))
211
215
  @sub_modes.each do |mode|
212
- prompt.sub!(*(mode.prompt))
216
+ prompt.sub!(*(mode[0].prompt))
213
217
  end
214
218
  prompt.sub!(*(@behavior[:prompt]))
215
219
  end
@@ -391,484 +391,10 @@ module Command
391
391
  return list
392
392
  end
393
393
  end
394
-
395
- #The end of the Results train. Formatter objects are supposed to output to the user events that they
396
- #receive from their presenters. To simplify this process, a number of common IO functions are delegated
397
- #to an IO object - usually Command::raw_stdout.
398
- #
399
- #This class in particular is pretty quiet - probably not helpful for everyday use.
400
- #Of course, for some purposes, singleton methods might be very useful
401
- class Formatter
402
- module Styler
403
- Foregrounds = {
404
- 'black' => 30,
405
- 'red' => 31,
406
- 'green' => 32,
407
- 'yellow' => 33,
408
- 'blue' => 34,
409
- 'magenta' => 35,
410
- 'cyan' => 36,
411
- 'white' => 37
412
- }
413
-
414
- Backgrounds = {}
415
-
416
- Foregrounds.each() do |name, value|
417
- Backgrounds[name] = value + 10
418
- end
419
-
420
- Extras = {
421
- 'clear' => 0,
422
- 'bold' => 1,
423
- 'underline' => 4,
424
- 'reversed' => 7
425
- }
426
-
427
- def style(text, options)
428
- options ||= {}
429
- if options.key? :format_advice
430
- options = options.merge(options[:format_advice])
431
- end
432
- aliased = {
433
- :foreground => options[:color],
434
- :extra => options[:text_style]
435
- }
436
- options = aliased.merge(options)
437
- markup = code_for(Foregrounds, options[:foreground]) +
438
- code_for(Backgrounds, options[:background]) +
439
- code_for(Extras, options[:extra])
440
- return text if markup.empty?
441
- return markup + text + code_for(Extras, "clear")
442
- end
443
-
444
- def code_for(kind, name)
445
- if kind.has_key?(name.to_s)
446
- "\e[#{kind[name.to_s]}m"
447
- else
448
- ""
449
- end
450
- end
451
- end
452
- extend Forwardable
453
-
454
- class FormatAdvisor
455
- def initialize(formatter)
456
- @advisee = formatter
457
- end
458
-
459
- def list(&block)
460
- @advisee.advice[:list] << proc(&block)
461
- end
462
-
463
- def item(&block)
464
- @advisee.advice[:item] << proc(&block)
465
- end
466
-
467
- def output(&block)
468
- @advisee.advice[:output] << proc(&block)
469
- end
470
- end
471
-
472
- def notify(msg, item)
473
- if msg == :start
474
- start
475
- return
476
- end
477
- if msg == :done
478
- finish
479
- return
480
- end
481
-
482
- apply_advice(item)
483
-
484
- if List === item
485
- case msg
486
- when :saw_begin
487
- saw_begin_list(item)
488
- when :saw_end
489
- saw_end_list(item)
490
- when :arrive
491
- closed_begin_list(item)
492
- when :leave
493
- closed_end_list(item)
494
- end
495
- else
496
- case msg
497
- when :arrive
498
- closed_item(item)
499
- when :saw
500
- saw_item(item)
501
- end
502
- end
503
- end
504
-
505
- def initialize(out = nil, err = nil)
506
- @out_to = out || ::Command::raw_stdout
507
- @err_to = err || ::Command::raw_stderr
508
- @advisor = FormatAdvisor.new(self)
509
- @advice = {:list => [], :item => [], :output => []}
510
- end
511
-
512
- def apply_advice(item)
513
- type = List === item ? :list : :item
514
-
515
- item.options[:format_advice] =
516
- @advice[type].inject(default_advice(type)) do |advice, advisor|
517
- result = advisor[item]
518
- break if result == :DONE
519
- if Hash === result
520
- advice.merge(result)
521
- else
522
- advice
523
- end
524
- end
525
- end
526
-
527
- attr_reader :advice
528
-
529
- def receive_advice(&block)
530
- @advisor.instance_eval(&block)
531
- end
532
-
533
- def default_advice(type)
534
- {}
535
- end
536
-
537
- def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
538
-
539
- def self.inherited(sub)
540
- sub.extend Forwardable
541
- sub.class_eval do
542
- def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
543
- end
544
- end
545
-
546
- #Presenter callback: output is beginning
547
- def start; end
548
-
549
- #Presenter callback: a list has just started
550
- def saw_begin_list(list); end
551
-
552
- #Presenter callback: an item has just been added
553
- def saw_item(item); end
554
-
555
- #Presenter callback: a list has just ended
556
- def saw_end_list(list); end
557
-
558
- #Presenter callback: a list opened, tree order
559
- def closed_begin_list(list); end
560
-
561
- #Presenter callback: an item added, tree order
562
- def closed_item(item); end
563
-
564
- #Presenter callback: an list closed, tree order
565
- def closed_end_list(list); end
566
-
567
- #Presenter callback: output is done
568
- def finish; end
569
- end
570
-
571
- #The simplest useful Formatter: it outputs the value of every item in tree order. Think of
572
- #it as what would happen if you just let puts and p go directly to the screen, without the
573
- #annoying consequences of threading, etc.
574
- class TextFormatter < Formatter
575
- def closed_item(value)
576
- puts value
577
- end
578
- end
579
-
580
- class StrategyFormatter < Formatter
581
- class FormatStrategy
582
- extend Forwardable
583
- include Formatter::Styler
584
-
585
- def initialize(name, formatter)
586
- @name = name
587
- @formatter = formatter
588
- setup
589
- end
590
-
591
- def setup; end
592
-
593
- def_delegators :@formatter, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
594
-
595
- attr_reader :name
596
-
597
- def switch_to(name)
598
- unless name == self.name
599
- return true
600
- end
601
- return false
602
- end
603
-
604
- def finish
605
- @formatter.pop_strategy(self.name)
606
- end
607
-
608
- #Presenter callback: a list has just started
609
- def saw_begin_list(list); end
610
-
611
- #Presenter callback: an item has just been added
612
- def saw_item(item); end
613
-
614
- #Presenter callback: a list has just ended
615
- def saw_end_list(list); end
616
-
617
- #Presenter callback: a list opened, tree order
618
- def closed_begin_list(list);
619
- end
620
-
621
- #Presenter callback: an item added, tree order
622
- def closed_item(item); end
623
-
624
- #Presenter callback: an list closed, tree order
625
- def closed_end_list(list);
626
- if list.options[:strategy_start] == self
627
- finish
628
- end
629
- end
630
-
631
- private
632
- def out
633
- @formatter.out_to
634
- end
635
-
636
- def err
637
- @formatter.err_to
638
- end
639
- end
640
-
641
- @strategies = {:default => FormatStrategy}
642
-
643
- class << self
644
- def strategy(name, base_klass = FormatStrategy, &def_block)
645
- @strategies[name.to_sym] = Class.new(base_klass, &def_block)
646
- end
647
-
648
- def inherited(sub)
649
- self.instance_variables.each do |var|
650
- value = self.instance_variable_get(var)
651
- if value.nil?
652
- sub.instance_variable_set(var, nil)
653
- else
654
- sub.instance_variable_set(var, value.dup)
655
- end
656
- end
657
- end
658
-
659
- def strategy_set(formatter)
660
- set = {}
661
- @strategies.each_pair do |name, klass|
662
- set[name] = klass.new(name, formatter)
663
- end
664
- return set
665
- end
666
- end
667
-
668
- def initialize(out = nil, err = nil)
669
- super(out, err)
670
- @strategies = self.class.strategy_set(self)
671
- @strategy_stack = [@strategies[:default]]
672
- end
673
-
674
- attr_reader :out_to, :err_to
675
-
676
- def_delegators :current_strategy, :saw_begin_list, :saw_item,
677
- :saw_end_list
678
-
679
- def current_strategy
680
- @strategy_stack.last
681
- end
682
-
683
- def push_strategy(name)
684
- if @strategies.has_key?(name)
685
- @strategy_stack.push(@strategies[name])
686
- end
687
- end
688
-
689
- def pop_strategy(name)
690
- if current_strategy.name == name
691
- @strategy_stack.pop
692
- end
693
- end
694
-
695
- def closed_begin_list(list)
696
- going_to = current_strategy.name
697
- unless list.options[:format_advice].nil? or
698
- (next_strategy = list.options[:format_advice][:type]).nil? or
699
- @strategies[next_strategy].nil? or
700
- not current_strategy.switch_to(next_strategy)
701
- going_to = next_strategy
702
- end
703
- push_strategy(going_to)
704
- current_strategy.closed_begin_list(list)
705
- end
706
-
707
- def closed_end_list(list)
708
- current_strategy.closed_end_list(list)
709
- current_strategy.finish
710
- end
711
-
712
- def closed_item(item)
713
- unless item.options[:format_advice].nil? or
714
- (once = item.options[:format_advice][:type]).nil? or
715
- @strategies[once].nil? or
716
- not current_strategy.switch_to(once)
717
- @strategies[once].closed_item(item)
718
- else
719
- current_strategy.closed_item(item)
720
- end
721
- end
722
-
723
- strategy :default do
724
- def closed_item(value)
725
- puts value
726
- end
727
- end
728
-
729
- strategy :progress do
730
- def switch_to(name); false; end
731
- def closed_begin_list(list)
732
- puts unless list.depth == 0
733
- justify = 0 || list[:format_advice][:justify]
734
- print style(list.to_s.ljust(justify), list.options)
735
- end
736
-
737
- def closed_item(item)
738
- print style(".", item.options)
739
- flush
740
- end
741
-
742
- def closed_end_list(list)
743
- puts
744
- super
745
- end
746
- end
747
-
748
- strategy :indent do
749
- def setup
750
- @indent_level = 0
751
- end
752
-
753
- def indent
754
- return " " * @indent_level
755
- end
756
-
757
- def closed_begin_list(list)
758
- super
759
- puts indent + style(list.to_s, list.options)
760
- @indent_level += 1
761
- @indent_level
762
- end
763
-
764
- def closed_item(item)
765
- item.to_s.split(/\s*\n\s*/).each do |line|
766
- puts indent + style(line, item.options)
767
- end
768
- super
769
- end
770
-
771
- def closed_end_list(list)
772
- @indent_level -= 1
773
- @indent_level
774
- super
775
- end
776
- end
777
-
778
- strategy :invisible do
779
- def closed_item(value); end
780
- end
781
-
782
- strategy :skip do
783
- def closed_begin_list(list)
784
- finish
785
- end
786
- end
787
-
788
- strategy :chatty do
789
- def switch_to(name); false; end
790
-
791
- def saw_begin_list(list)
792
- err.print style("B", list.options)
793
- end
794
-
795
- def saw_item(list)
796
- err.print style(".", list.options)
797
- end
798
-
799
- def saw_end_list(list)
800
- err.print style("E", list.options)
801
- end
802
-
803
- def closed_begin_list(list);
804
- clean_options = list.options.dup
805
- clean_options.delete(:strategy_start)
806
- puts "> #{list.to_s} (depth=#{list.depth} #{clean_options.inspect})"
807
- end
808
- def closed_item(list)
809
- puts " " + list.to_s +
810
- unless(list.options.empty?)
811
- " " + list.options.inspect
812
- else
813
- ""
814
- end
815
- end
816
- def closed_end_list(list); puts "< " + list.to_s; end
817
- end
818
- end
819
-
820
- #A trivial and obvious Formatter: produces well-formed XML fragments based on events. It even
821
- #indents. Might be handy for more complicated output processing, since you could feed the document
822
- #to a XSLT processor.
823
- class XMLFormatter < Formatter
824
- def initialize(out = nil, err = nil, indent=" ", newline="\n")
825
- super(out, err)
826
- @indent = indent
827
- @newline = newline
828
- @indent_level=0
829
- end
830
-
831
- def line(string)
832
- print "#{@indent * @indent_level}#{string}#@newline"
833
- end
834
-
835
- def closed_begin_list(name)
836
- line "<#{name}#{xmlize_options(name)}>"
837
- @indent_level += 1
838
- end
839
-
840
- def closed_item(value)
841
- line "<item value=\"#{value}\"#{xmlize_options(value)} />"
842
- end
843
-
844
- def closed_end_list(name)
845
- @indent_level -= 1
846
- if @indent_level < 0
847
- @indent_level = 0
848
- return
849
- end
850
- line "</#{name}>"
851
- end
852
-
853
- private
854
-
855
- def flatten_value(value)
856
- case value
857
- when Hash
858
- return value.map do |name, value|
859
- "#{name}: #{value}"
860
- end.join("; ")
861
- else
862
- return value.to_s
863
- end
864
- end
865
-
866
- def xmlize_options(item)
867
- item.options.inject("") do |string, (name, value)|
868
- string + " #{name}=\"#{flatten_value(value)}\""
869
- end
870
- end
871
- end
872
394
  end
873
395
  end
874
396
 
397
+ #TODO migrate these requires to only where they're needed
398
+ require 'command-set/formatter/base'
399
+ require 'command-set/formatter/strategy'
400
+ require 'command-set/formatter/xml'