command-set 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'