ver 2009.10.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/CHANGELOG +1404 -0
  2. data/MANIFEST +187 -0
  3. data/Rakefile +30 -0
  4. data/TODO +169 -0
  5. data/bin/ver +19 -0
  6. data/config/detect.rb +109 -0
  7. data/config/keymap/vim.rb +239 -0
  8. data/config/rc.rb +0 -0
  9. data/config/scratch +3 -0
  10. data/config/syntax/ANTLR.json +275 -0
  11. data/config/syntax/ASP VB.net.json +228 -0
  12. data/config/syntax/ASP.json +135 -0
  13. data/config/syntax/ActionScript.json +168 -0
  14. data/config/syntax/Ada.json +108 -0
  15. data/config/syntax/Apache.json +342 -0
  16. data/config/syntax/AppleScript.json +721 -0
  17. data/config/syntax/Bibtex.json +253 -0
  18. data/config/syntax/Blog (HTML).json +47 -0
  19. data/config/syntax/Blog (Markdown).json +50 -0
  20. data/config/syntax/Blog (Text).json +45 -0
  21. data/config/syntax/Blog (Textile).json +45 -0
  22. data/config/syntax/Bulletin Board.json +428 -0
  23. data/config/syntax/C++.json +323 -0
  24. data/config/syntax/C.json +694 -0
  25. data/config/syntax/CSS.json +346 -0
  26. data/config/syntax/DOT.json +79 -0
  27. data/config/syntax/Diff.json +136 -0
  28. data/config/syntax/Dylan.json +104 -0
  29. data/config/syntax/Eiffel.json +128 -0
  30. data/config/syntax/Erlang.json +1636 -0
  31. data/config/syntax/F-Script.json +137 -0
  32. data/config/syntax/FXScript.json +260 -0
  33. data/config/syntax/Gri.json +141 -0
  34. data/config/syntax/HTML (Mason).json +207 -0
  35. data/config/syntax/HTML (Rails).json +52 -0
  36. data/config/syntax/HTML (Tcl).json +42 -0
  37. data/config/syntax/HTML for ASP.net.json +736 -0
  38. data/config/syntax/HTML-ASP.json +45 -0
  39. data/config/syntax/HTML.json +614 -0
  40. data/config/syntax/Haskell.json +407 -0
  41. data/config/syntax/Inform.json +80 -0
  42. data/config/syntax/Ini.json +91 -0
  43. data/config/syntax/Io.json +142 -0
  44. data/config/syntax/Java.json +745 -0
  45. data/config/syntax/JavaProperties.json +42 -0
  46. data/config/syntax/JavaScript.json +446 -0
  47. data/config/syntax/LaTeX Beamer.json +65 -0
  48. data/config/syntax/LaTeX Log.json +88 -0
  49. data/config/syntax/LaTeX.json +962 -0
  50. data/config/syntax/Lighttpd.json +93 -0
  51. data/config/syntax/Lisp.json +101 -0
  52. data/config/syntax/Literate Haskell.json +55 -0
  53. data/config/syntax/Logtalk.json +289 -0
  54. data/config/syntax/Lua.json +146 -0
  55. data/config/syntax/M.json +744 -0
  56. data/config/syntax/MEL.json +161 -0
  57. data/config/syntax/MIPS.json +114 -0
  58. data/config/syntax/Mail.json +224 -0
  59. data/config/syntax/Makefile.json +66 -0
  60. data/config/syntax/Markdown.json +644 -0
  61. data/config/syntax/Modula-3.json +80 -0
  62. data/config/syntax/Movable Type.json +348 -0
  63. data/config/syntax/OCaml.json +1391 -0
  64. data/config/syntax/Objective-C++.json +21 -0
  65. data/config/syntax/OpenGL.json +24 -0
  66. data/config/syntax/PHP.json +2184 -0
  67. data/config/syntax/Pascal.json +128 -0
  68. data/config/syntax/Perl.json +2091 -0
  69. data/config/syntax/Plain text.json +49 -0
  70. data/config/syntax/Processing.json +188 -0
  71. data/config/syntax/Quake3 Config.json +54 -0
  72. data/config/syntax/R.json +157 -0
  73. data/config/syntax/Rez.json +137 -0
  74. data/config/syntax/Ruby on Rails.json +170 -0
  75. data/config/syntax/Ruby.json +1753 -0
  76. data/config/syntax/SQL (Rails).json +31 -0
  77. data/config/syntax/SQL.json +435 -0
  78. data/config/syntax/SWIG.json +96 -0
  79. data/config/syntax/Scheme.json +359 -0
  80. data/config/syntax/Shell-Unix-Generic.json +1198 -0
  81. data/config/syntax/Slate.json +265 -0
  82. data/config/syntax/Smarty.json +110 -0
  83. data/config/syntax/Standard ML.json +322 -0
  84. data/config/syntax/Subversion commit message.json +62 -0
  85. data/config/syntax/Tcl.json +278 -0
  86. data/config/syntax/TeX Math.json +83 -0
  87. data/config/syntax/TeX.json +157 -0
  88. data/config/syntax/Textile.json +273 -0
  89. data/config/syntax/Twiki.json +436 -0
  90. data/config/syntax/Vectorscript.json +97 -0
  91. data/config/syntax/XML strict.json +148 -0
  92. data/config/syntax/XML.json +301 -0
  93. data/config/syntax/XSL.json +96 -0
  94. data/config/syntax/YAML.json +293 -0
  95. data/config/syntax/iCalendar.json +51 -0
  96. data/config/syntax/reStructuredText.json +403 -0
  97. data/config/theme/Active4D.json +260 -0
  98. data/config/theme/All Hallow's Eve.json +171 -0
  99. data/config/theme/Amy.json +359 -0
  100. data/config/theme/BBEdit.json +269 -0
  101. data/config/theme/Bespin.json +322 -0
  102. data/config/theme/Blackboard.json +215 -0
  103. data/config/theme/BoysAndGirls01.json +156 -0
  104. data/config/theme/Brilliance Black.json +1695 -0
  105. data/config/theme/Brilliance Dull.json +1451 -0
  106. data/config/theme/Classic Modified.json +288 -0
  107. data/config/theme/Cobalt.json +345 -0
  108. data/config/theme/Cool Glow.json +215 -0
  109. data/config/theme/Dawn.json +258 -0
  110. data/config/theme/Eiffel.json +270 -0
  111. data/config/theme/Espresso Libre.json +247 -0
  112. data/config/theme/Fluidvision.json +272 -0
  113. data/config/theme/IDLE.json +159 -0
  114. data/config/theme/LAZY.json +178 -0
  115. data/config/theme/Mac Classic.json +277 -0
  116. data/config/theme/MagicWB (Amiga).json +231 -0
  117. data/config/theme/Merbivore Soft.json +181 -0
  118. data/config/theme/Merbivore.json +181 -0
  119. data/config/theme/Monokai.json +177 -0
  120. data/config/theme/Notepad2.json +166 -0
  121. data/config/theme/Pastels on Dark.json +437 -0
  122. data/config/theme/RubyBlue.json +226 -0
  123. data/config/theme/Sin City 2.json +361 -0
  124. data/config/theme/Slate.json +270 -0
  125. data/config/theme/Slush & Poppies.json +232 -0
  126. data/config/theme/SpaceCadet.json +143 -0
  127. data/config/theme/Sunburst.json +415 -0
  128. data/config/theme/Twilight BG FG.json +633 -0
  129. data/config/theme/Twilight.json +321 -0
  130. data/config/theme/Whys Poignant.json +119 -0
  131. data/config/theme/Zenburnesque.json +237 -0
  132. data/config/theme/barf.json +155 -0
  133. data/config/theme/fake.json +418 -0
  134. data/config/theme/happydeluxe.json +114 -0
  135. data/config/theme/iLife 05.json +393 -0
  136. data/config/theme/iPlastic.json +177 -0
  137. data/config/theme/mintBlue Dark.json +414 -0
  138. data/config/theme/mintBlue.json +415 -0
  139. data/config/theme/monoindustrial.json +276 -0
  140. data/config/theme/starlight.json +67 -0
  141. data/config/tutorial +74 -0
  142. data/config/welcome +115 -0
  143. data/help/index.verh +14 -0
  144. data/lib/ver.rb +156 -0
  145. data/lib/ver/entry.rb +97 -0
  146. data/lib/ver/keymap.rb +96 -0
  147. data/lib/ver/layout.rb +107 -0
  148. data/lib/ver/methods.rb +19 -0
  149. data/lib/ver/methods/completion.rb +116 -0
  150. data/lib/ver/methods/control.rb +340 -0
  151. data/lib/ver/methods/insert.rb +6 -0
  152. data/lib/ver/methods/move.rb +65 -0
  153. data/lib/ver/methods/search.rb +33 -0
  154. data/lib/ver/methods/select.rb +145 -0
  155. data/lib/ver/methods/views.rb +21 -0
  156. data/lib/ver/mode.rb +160 -0
  157. data/lib/ver/options.rb +207 -0
  158. data/lib/ver/plist.rb +106 -0
  159. data/lib/ver/status.rb +67 -0
  160. data/lib/ver/syntax.rb +68 -0
  161. data/lib/ver/syntax/detector.rb +53 -0
  162. data/lib/ver/syntax/processor.rb +48 -0
  163. data/lib/ver/text.rb +374 -0
  164. data/lib/ver/textpow.rb +357 -0
  165. data/lib/ver/theme.rb +162 -0
  166. data/lib/ver/vendor/fuzzy_file_finder.rb +340 -0
  167. data/lib/ver/view.rb +163 -0
  168. data/lib/ver/view/entry.rb +28 -0
  169. data/lib/ver/view/list.rb +137 -0
  170. data/lib/ver/view/list/buffer.rb +27 -0
  171. data/lib/ver/view/list/fuzzy_file_finder.rb +44 -0
  172. data/lib/ver/view/list/syntax.rb +13 -0
  173. data/lib/ver/view/list/theme.rb +13 -0
  174. data/spec/keymap.rb +224 -0
  175. data/tasks/bacon.rake +49 -0
  176. data/tasks/changelog.rake +18 -0
  177. data/tasks/gem.rake +22 -0
  178. data/tasks/gem_installer.rake +76 -0
  179. data/tasks/grancher.rake +12 -0
  180. data/tasks/install_dependencies.rake +6 -0
  181. data/tasks/manifest.rake +4 -0
  182. data/tasks/plist2json.rake +35 -0
  183. data/tasks/rcov.rake +18 -0
  184. data/tasks/release.rake +12 -0
  185. data/tasks/reversion.rake +8 -0
  186. data/tasks/syntax_list.rake +31 -0
  187. data/ver.gemspec +29 -0
  188. metadata +241 -0
@@ -0,0 +1,6 @@
1
+ module VER
2
+ module Methods
3
+ module Insert
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,65 @@
1
+ module VER
2
+ module Methods
3
+ module Move
4
+ def go_char_left(count = 1)
5
+ mark_set :insert, "insert - #{count} char"
6
+ end
7
+
8
+ def go_char_right(count = 1)
9
+ mark_set :insert, "insert + #{count} char"
10
+ end
11
+
12
+ def go_line_up(count = 1)
13
+ mark_set :insert, "insert - #{count} line"
14
+ end
15
+
16
+ def go_line_down(count = 1)
17
+ mark_set :insert, "insert + #{count} line"
18
+ end
19
+
20
+ def go_word_left
21
+ mark_set :insert, 'insert - 1 char'
22
+ mark_set :insert, 'insert wordstart'
23
+ end
24
+
25
+ def go_word_right
26
+ mark_set :insert, 'insert + 1 char'
27
+ mark_set :insert, 'insert wordend'
28
+ end
29
+
30
+ def go_beginning_of_line
31
+ mark_set :insert, 'insert linestart'
32
+ end
33
+
34
+ def go_end_of_line
35
+ mark_set :insert, 'insert lineend'
36
+ end
37
+
38
+ def go_line(number = 0)
39
+ mark_set :insert, "#{number}.0"
40
+ end
41
+
42
+ def go_end_of_file
43
+ mark_set :insert, :end
44
+ end
45
+
46
+ def go_page_up
47
+ height = Tk.root.winfo_height
48
+ linespace = cget(:font).metrics(:linespace)
49
+ diff = height / linespace
50
+
51
+ mark_set :insert, "insert - #{diff} line"
52
+ see :insert
53
+ end
54
+
55
+ def go_page_down
56
+ height = Tk.root.winfo_height
57
+ linespace = cget(:font).metrics(:linespace)
58
+ diff = height / linespace
59
+
60
+ mark_set :insert, "insert + #{diff} line"
61
+ see :insert
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,33 @@
1
+ module VER
2
+ module Methods
3
+ module Search
4
+ def status_search
5
+ status_ask 'Search term: ' do |term|
6
+ tag_all_matching(:search, Regexp.new(term))
7
+ end
8
+ end
9
+
10
+ def search_next
11
+ from, to = tag_nextrange('search', 'insert + 1 chars', 'end')
12
+ mark_set(:insert, from) if from
13
+ end
14
+
15
+ def search_prev
16
+ from, to = tag_prevrange('search', 'insert - 1 chars', '0.0')
17
+ mark_set(:insert, from) if from
18
+ end
19
+
20
+ def search_next_word_under_cursor
21
+ word = get('insert wordstart', 'insert wordend')
22
+ tag_all_matching(:search, word)
23
+ search_next
24
+ end
25
+
26
+ def search_prev_word_under_cursor
27
+ word = get('insert wordstart', 'insert wordend')
28
+ tag_all_matching(:search, word)
29
+ search_prev
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,145 @@
1
+ module VER
2
+ module Methods
3
+ module Select
4
+ def start_selection_mode(name)
5
+ self.mode = name
6
+ @selection_start = index(:insert).split('.').map(&:to_i)
7
+ refresh_selection
8
+ end
9
+
10
+ def switch_selection_mode(name)
11
+ self.mode = name
12
+ refresh_selection
13
+ end
14
+
15
+ %w[char line block].each do |suffix|
16
+ name = "select_#{suffix}"
17
+ define_method "start_#{name}_mode" do
18
+ start_selection_mode name
19
+ end
20
+
21
+ define_method "switch_#{name}_mode" do
22
+ switch_selection_mode name
23
+ end
24
+ end
25
+
26
+ def delete_selection
27
+ queue = tag_ranges(:sel).flatten
28
+ delete(*queue)
29
+ mark_set(:insert, queue.first)
30
+
31
+ clear_selection
32
+ start_control_mode
33
+ end
34
+
35
+ def indent_selection
36
+ each_selected_line do |y, fx, tx|
37
+ tx = fx + 2
38
+ next if get("#{y}.#{fx}", "#{y}.#{tx}").empty?
39
+ insert("#{y}.#{fx}", ' ')
40
+ end
41
+
42
+ edit_separator
43
+ refresh_selection
44
+ end
45
+
46
+ def unindent_selection
47
+ queue = []
48
+
49
+ each_selected_line do |y, fx, tx|
50
+ tx = fx + 2
51
+ left, right = "#{y}.#{fx}", "#{y}.#{tx}"
52
+ next unless get(left, right) == ' '
53
+ queue << left << right
54
+ end
55
+
56
+ delete(*queue)
57
+ edit_separator
58
+ refresh_selection
59
+ end
60
+
61
+ def selection_evaluate
62
+ tag_ranges(:sel).each do |from, to|
63
+ code = get(from, to)
64
+
65
+ begin
66
+ result = eval(code)
67
+ insert("#{to} lineend", "\n%p" % [result])
68
+ rescue => exception
69
+ insert("#{to} lineend", "\n%p" % [exception])
70
+ end
71
+ end
72
+ end
73
+
74
+ def copy_selection
75
+ chunks = tag_ranges(:sel).map{|sel| get(*sel) }
76
+ copy(chunks.size == 1 ? chunks.first : chunks)
77
+ clear_selection
78
+ start_control_mode
79
+ end
80
+
81
+ def pipe_selection
82
+ status_ask 'Pipe command: ' do |cmd|
83
+ pipe_selection_execute(cmd)
84
+ clear_selection
85
+ start_control_mode
86
+ end
87
+ end
88
+
89
+ def comment_selection
90
+ each_selected_line do |y, fx, tx|
91
+ insert("#{y}.0 linestart", '# ')
92
+ end
93
+
94
+ edit_separator
95
+ refresh_selection
96
+ end
97
+
98
+ def uncomment_selection
99
+ each_selected_line do |y, fx, tx|
100
+ delete("#{y}.0 linestart", "#{y}.0 linestart + 2 chars")
101
+ end
102
+
103
+ edit_separator
104
+ refresh_selection
105
+ end
106
+
107
+ private
108
+
109
+ def each_selection
110
+ tag_ranges(:sel).each do |sel|
111
+ (fy, fx), (ty, tx) = sel.map{|pos| pos.split('.').map(&:to_i) }
112
+ yield fy, fx, ty, tx
113
+ end
114
+ end
115
+
116
+ def each_selected_line
117
+ each_selection do |fy, fx, ty, tx|
118
+ fy.upto(ty) do |y|
119
+ yield y, fx, tx
120
+ end
121
+ end
122
+ end
123
+
124
+ def pipe_selection_execute(*cmd)
125
+ require 'open3'
126
+
127
+ Open3.popen3(*cmd) do |si, sose, thread|
128
+ queue = []
129
+ tag_ranges(:sel).each do |from, to|
130
+ si.write(get(from, to))
131
+ queue << from << to
132
+ end
133
+
134
+ si.close
135
+ output = sose.read
136
+
137
+ return if queue.empty?
138
+
139
+ delete(*queue)
140
+ insert(queue.first, output)
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,21 @@
1
+ module VER
2
+ module Methods
3
+ module Views
4
+ def view_create
5
+ view.create
6
+ end
7
+
8
+ def view_close
9
+ view.close
10
+ end
11
+
12
+ def view_focus_next
13
+ view.focus_next
14
+ end
15
+
16
+ def view_focus_prev
17
+ view.focus_prev
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/ver/mode.rb ADDED
@@ -0,0 +1,160 @@
1
+ module VER
2
+ class Mode
3
+ MERGER = proc{|key, v1, v2|
4
+ if v1.respond_to?(:merge) && v2.respond_to?(:merge)
5
+ v1.merge(v2, &MERGER)
6
+ else
7
+ v2
8
+ end
9
+ }
10
+
11
+ attr_accessor :callback, :name, :arguments
12
+
13
+ def initialize(name, callback)
14
+ @name, @callback = name, callback
15
+ @stack = []
16
+ @map = {}
17
+ @ancestors = []
18
+ @missing = nil
19
+ @arguments = true
20
+ end
21
+
22
+ def inherits(*others)
23
+ others.flatten.each do |other|
24
+ ancestor = find_ancestor(other.to_sym)
25
+ @ancestors.delete ancestor
26
+ @ancestors.unshift ancestor
27
+ end
28
+ end
29
+
30
+ def find_ancestor(name)
31
+ callback.modes[name.to_sym]
32
+ end
33
+
34
+ def ancestors(*done, &block)
35
+ yield self
36
+
37
+ @ancestors.each do |ancestor|
38
+ next if done.include?(ancestor)
39
+ yield ancestor
40
+ ancestor.ancestors(done + [self], &block)
41
+ end
42
+ end
43
+
44
+ def missing(sym)
45
+ @missing = sym
46
+ end
47
+
48
+ def map(sym, *keychains)
49
+ keychains.each do |keychain|
50
+ bind(keychain.flatten, sym)
51
+ end
52
+ end
53
+ alias to map
54
+
55
+ def bind(keychain, action_name = nil, &block)
56
+ keychain = keychain.dup
57
+ total = hash = {}
58
+
59
+ while key = keychain.shift
60
+ register key
61
+
62
+ if keychain.empty?
63
+ hash[key] = block || action_name
64
+ else
65
+ hash = hash[key] = {}
66
+ end
67
+ end
68
+
69
+ @map.replace @map.merge(total, &MERGER)
70
+ end
71
+
72
+ def register(key)
73
+ callback.register(key)
74
+ end
75
+
76
+ def enter_keys(*keys)
77
+ keys.flatten.each{|key| enter_key(key) }
78
+ end
79
+
80
+ def enter_key(key)
81
+ @stack << key
82
+
83
+ ancestors do |ancestor|
84
+ result = ancestor.attempt_execute(@stack)
85
+
86
+ case result
87
+ when nil # nothing matched yet, but possible in future
88
+ return nil
89
+ when false # nothing possible
90
+ # try next one
91
+ when true # executed
92
+ @stack.clear
93
+ return true
94
+ else
95
+ raise "%p is not a valid result" % [result]
96
+ end
97
+ end
98
+
99
+ # no ancestors or all failed
100
+ @stack.clear
101
+ enter_missing(key)
102
+ rescue => ex
103
+ puts ex, *ex.backtrace
104
+ @stack.clear
105
+ end
106
+
107
+ def enter_missing(key)
108
+ execute(@missing, key) if @missing
109
+ end
110
+
111
+ def attempt_execute(original_stack)
112
+ if arguments
113
+ stack, arg = Mode.split_stack(original_stack)
114
+ else
115
+ stack, arg = original_stack, nil
116
+ end
117
+
118
+ if stack.empty?
119
+ arg ? nil : false
120
+ else
121
+ executable = stack.inject(@map){|keys, key| keys.fetch(key) }
122
+
123
+ execute(executable, *arg)
124
+ end
125
+ rescue KeyError
126
+ false
127
+ end
128
+
129
+ def execute(executable, *arg)
130
+ case executable
131
+ when Hash
132
+ return nil
133
+ when Symbol
134
+ callback.send(executable, *arg)
135
+ when Array
136
+ callback.send(*executable, *arg)
137
+ when Proc
138
+ executable.call(*arg)
139
+ else
140
+ return false
141
+ end
142
+
143
+ true
144
+ end
145
+
146
+ def self.split_stack(stack)
147
+ return stack, nil if stack[0] == '0'
148
+
149
+ pivot = stack.index{|c| c !~ /\d/ }
150
+
151
+ if pivot == 0
152
+ return stack, nil
153
+ elsif pivot
154
+ return stack[pivot..-1], stack[0..pivot].join.to_i
155
+ else
156
+ return [], stack.join.to_i
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,207 @@
1
+ module VER
2
+ # Provides a minimal DSL to describe options with defaults and metadata.
3
+ #
4
+ # The example below should demonstrate the major features, note that key
5
+ # lookup wanders up the hierarchy until there is a match found or the parent
6
+ # of the Options class is itself, in which case nil will be returned.
7
+ #
8
+ # Usage:
9
+ #
10
+ # class Calculator
11
+ # @options = Options.new(:foo)
12
+ # def self.options; @options; end
13
+ #
14
+ # options.dsl do
15
+ # o "Which method to use", :method, :plus
16
+ # o "Default arguments", :args, [1, 2]
17
+ # sub(:minus){ o("Default arguments", :args, [4, 3]) }
18
+ # end
19
+ #
20
+ # def self.calculate(method = nil, *args)
21
+ # method ||= options[:method]
22
+ # args = args.empty? ? options[method, :args] : args
23
+ # self.send(method, *args)
24
+ # end
25
+ #
26
+ # def self.plus(n1, n2)
27
+ # n1 + n2
28
+ # end
29
+ #
30
+ # def self.minus(n1, n2)
31
+ # n1 - n2
32
+ # end
33
+ # end
34
+ #
35
+ # Calculator.calculate
36
+ # # => 3
37
+ # Calculator.options[:method] = :minus
38
+ # # => :minus
39
+ # Calculator.calculate
40
+ # # => 1
41
+ # Calculator.calculate(:plus, 4, 5)
42
+ # # => 9
43
+ #
44
+ class Options
45
+ def initialize(name, parent = self)
46
+ @name, @parent, = name, parent
47
+ @hash = {}
48
+ yield(self) if block_given?
49
+ end
50
+
51
+ # Shortcut for instance_eval
52
+ def dsl(&block)
53
+ instance_eval(&block) if block
54
+ self
55
+ end
56
+
57
+ # Create a new Options instance with +name+ and pass +block+ on to its #dsl.
58
+ # Assigns the new instance to the +name+ Symbol on current instance.
59
+ def sub(name, &block)
60
+ name = name.to_sym
61
+
62
+ case found = @hash[name]
63
+ when Options
64
+ found.dsl(&block)
65
+ else
66
+ found = @hash[name] = Options.new(name, self).dsl(&block)
67
+ end
68
+
69
+ found
70
+ end
71
+
72
+ # Store an option in the Options instance.
73
+ #
74
+ # @param [#to_s] doc describing the purpose of this option
75
+ # @param [#to_sym] key used to access
76
+ # @param [Object] value may be anything
77
+ # @param [Hash] other optional Hash containing meta-data
78
+ # :doc, :value keys will be ignored
79
+ def option(doc, key, value, other = {}, &block)
80
+ trigger = block || other[:trigger]
81
+ convert = {:doc => doc.to_s, :value => value}
82
+ convert[:trigger] = trigger if trigger
83
+ @hash[key.to_sym] = other.merge(convert)
84
+ end
85
+ alias o option
86
+
87
+ # To avoid lookup on the parent, we can set a default to the internal Hash.
88
+ # Parameters as in {Options#o}, but without the +key+.
89
+ def default(doc, value, other = {})
90
+ @hash.default = other.merge(:doc => doc, :value => value)
91
+ end
92
+
93
+ # Add a block that will be called when a new value is set.
94
+ def trigger(key, &block)
95
+ @hash[key.to_sym][:trigger] = block
96
+ end
97
+
98
+ # Try to retrieve the corresponding Hash for the passed keys, will try to
99
+ # retrieve the key from a parent if no match is found on the current
100
+ # instance. If multiple keys are passed it will try to find a matching
101
+ # child and pass the request on to it.
102
+ def get(key, *keys)
103
+ if keys.empty?
104
+ if value = @hash[key.to_sym]
105
+ value
106
+ elsif @parent != self
107
+ @parent.get(key)
108
+ else
109
+ nil
110
+ end
111
+ elsif sub_options = get(key)
112
+ sub_options.get(*keys)
113
+ end
114
+ end
115
+
116
+ # @param [Array] keys
117
+ # @param [Object] value
118
+ def set_value(keys, value)
119
+ got = get(*keys)
120
+ return got[:value] = value if got
121
+ raise(IndexError, "There is no option available for %p" % [keys])
122
+ end
123
+
124
+ # Retrieve only the :value from the value hash if found via +keys+.
125
+ def [](*keys)
126
+ if value = get(*keys)
127
+ value.is_a?(Hash) ? value[:value] : value
128
+ end
129
+ end
130
+
131
+ # Assign new :value to the value hash on the current instance.
132
+ #
133
+ # TODO: allow arbitrary assignments
134
+ def []=(key, value)
135
+ ks = key.to_sym
136
+ if @hash.has_key? ks
137
+ ns = @hash[ks]
138
+ ns[:value] = value
139
+ ns[:trigger].call(value) if ns[:trigger].respond_to?(:call)
140
+ elsif existing = get(key)
141
+ option(existing[:doc].to_s.dup, key, value)
142
+ else
143
+ raise(ArgumentError, "No key for %p exists" % [key])
144
+ end
145
+ end
146
+
147
+ def method_missing(meth, *args)
148
+ case meth.to_s
149
+ when /^(.*)=$/
150
+ self[$1] = args.first
151
+ else
152
+ self[meth]
153
+ end
154
+ end
155
+
156
+ def merge!(hash)
157
+ hash.each_pair do |key, value|
158
+ set_value(key.to_s.split('.'), value)
159
+ end
160
+ end
161
+
162
+ def to_hash
163
+ @hash
164
+ end
165
+
166
+ def each_option(&block)
167
+ @hash.each(&block)
168
+ end
169
+
170
+ def each_pair
171
+ @hash.each do |key, values|
172
+ yield(key, self[key])
173
+ end
174
+ end
175
+
176
+ def inspect
177
+ @hash.inspect
178
+ end
179
+
180
+ def pretty_print(q)
181
+ q.pp_hash @hash
182
+ end
183
+ end
184
+
185
+ # extend your class with this
186
+ module Optioned
187
+ def self.included(into)
188
+ into.extend(SingletonMethods)
189
+
190
+ snaked = into.name.split('::').last
191
+ snaked = snaked.gsub(/\B[A-Z][^A-Z]/, '_\&').downcase.gsub(' ', '_')
192
+
193
+ options = VER.options.sub(snaked)
194
+ into.instance_variable_set(:@options, options)
195
+ end
196
+
197
+ module SingletonMethods
198
+ attr_reader :options
199
+ end
200
+
201
+ private
202
+
203
+ def options
204
+ self.class.options
205
+ end
206
+ end
207
+ end