ver 2009.10.14

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.
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