germinate 1.2.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.
Files changed (105) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +26 -0
  3. data/README.rdoc +152 -0
  4. data/Rakefile +43 -0
  5. data/TODO +140 -0
  6. data/bin/germ +260 -0
  7. data/cucumber.yml +2 -0
  8. data/examples/basic.rb +123 -0
  9. data/examples/short.rb +19 -0
  10. data/features/author-formats-article.feature +111 -0
  11. data/features/author-lists-info.pending_feature +48 -0
  12. data/features/author-publishes-article-source.feature +5 -0
  13. data/features/author-publishes-article.feature +57 -0
  14. data/features/author-republishes-article.feature +5 -0
  15. data/features/author-selects-hunks.feature +26 -0
  16. data/features/author-sets-variables.feature +88 -0
  17. data/features/author-updates-article-source.feature +5 -0
  18. data/features/author-views-stuff.pending_feature +52 -0
  19. data/features/bin/quoter +6 -0
  20. data/features/bin/sorter +4 -0
  21. data/features/example_articles/bracketing.rb +27 -0
  22. data/features/example_articles/escaping.txt +13 -0
  23. data/features/example_articles/excerpt_output.rb +16 -0
  24. data/features/example_articles/hello.rb +9 -0
  25. data/features/example_articles/pipelines.txt +25 -0
  26. data/features/example_articles/regexen.rb +24 -0
  27. data/features/example_articles/sample_offsets.rb +18 -0
  28. data/features/example_articles/specials.rb +19 -0
  29. data/features/example_articles/stderr.rb +10 -0
  30. data/features/example_articles/wrapping.rb +8 -0
  31. data/features/example_output/bracketing.out +23 -0
  32. data/features/example_output/code_samples.txt +186 -0
  33. data/features/example_output/escaping.out +5 -0
  34. data/features/example_output/excerpt_output.out +6 -0
  35. data/features/example_output/hello.txt +1 -0
  36. data/features/example_output/pipelines.out +28 -0
  37. data/features/example_output/regexen.txt +22 -0
  38. data/features/example_output/sample_offsets.txt +15 -0
  39. data/features/example_output/specials.txt +40 -0
  40. data/features/example_output/stderr.out +3 -0
  41. data/features/example_output/wrapping.txt +3 -0
  42. data/features/step_definitions/germinate.rb +42 -0
  43. data/features/support/env.rb +20 -0
  44. data/germinate.gemspec +55 -0
  45. data/lib/germinate.rb +54 -0
  46. data/lib/germinate/application.rb +113 -0
  47. data/lib/germinate/article_editor.rb +20 -0
  48. data/lib/germinate/formatter.rb +119 -0
  49. data/lib/germinate/hunk.rb +183 -0
  50. data/lib/germinate/implicit_insertion.rb +9 -0
  51. data/lib/germinate/insertion.rb +29 -0
  52. data/lib/germinate/librarian.rb +293 -0
  53. data/lib/germinate/origin.rb +5 -0
  54. data/lib/germinate/pipeline.rb +13 -0
  55. data/lib/germinate/publisher.rb +57 -0
  56. data/lib/germinate/reader.rb +266 -0
  57. data/lib/germinate/selector.rb +136 -0
  58. data/lib/germinate/shared_style_attributes.rb +54 -0
  59. data/lib/germinate/shell_process.rb +94 -0
  60. data/lib/germinate/shell_publisher.rb +19 -0
  61. data/lib/germinate/simple_publisher.rb +7 -0
  62. data/lib/germinate/source_file.rb +41 -0
  63. data/lib/germinate/text_transforms.rb +119 -0
  64. data/lib/germinate/transform_process.rb +25 -0
  65. data/lib/germinate/variable.rb +23 -0
  66. data/sample.rb +14 -0
  67. data/spec/germinate/application_spec.rb +31 -0
  68. data/spec/germinate/article_editor_spec.rb +97 -0
  69. data/spec/germinate/code_hunk_spec.rb +73 -0
  70. data/spec/germinate/file_hunk_spec.rb +28 -0
  71. data/spec/germinate/formatter_spec.rb +160 -0
  72. data/spec/germinate/hunk_spec.rb +84 -0
  73. data/spec/germinate/implicit_insertion_spec.rb +33 -0
  74. data/spec/germinate/insertion_spec.rb +19 -0
  75. data/spec/germinate/librarian_spec.rb +555 -0
  76. data/spec/germinate/pipeline_spec.rb +34 -0
  77. data/spec/germinate/process_spec.rb +105 -0
  78. data/spec/germinate/publisher_spec.rb +130 -0
  79. data/spec/germinate/reader_spec.rb +385 -0
  80. data/spec/germinate/selector_spec.rb +121 -0
  81. data/spec/germinate/shell_publisher_spec.rb +61 -0
  82. data/spec/germinate/source_file_spec.rb +99 -0
  83. data/spec/germinate/text_hunk_spec.rb +98 -0
  84. data/spec/germinate/text_transforms_spec.rb +242 -0
  85. data/spec/germinate/transform_process_spec.rb +50 -0
  86. data/spec/germinate/variable_spec.rb +14 -0
  87. data/spec/germinate_spec.rb +8 -0
  88. data/spec/spec.opts +1 -0
  89. data/spec/spec_helper.rb +16 -0
  90. data/tasks/ann.rake +80 -0
  91. data/tasks/bones.rake +20 -0
  92. data/tasks/cucumber.rake +5 -0
  93. data/tasks/gem.rake +201 -0
  94. data/tasks/git.rake +40 -0
  95. data/tasks/notes.rake +27 -0
  96. data/tasks/post_load.rake +34 -0
  97. data/tasks/rdoc.rake +51 -0
  98. data/tasks/rubyforge.rake +55 -0
  99. data/tasks/setup.rb +292 -0
  100. data/tasks/spec.rake +54 -0
  101. data/tasks/svn.rake +47 -0
  102. data/tasks/test.rake +40 -0
  103. data/tasks/zentest.rake +36 -0
  104. data/test/test_germinate.rb +0 -0
  105. metadata +228 -0
@@ -0,0 +1,119 @@
1
+ require 'alter_ego'
2
+
3
+ # Obsolete
4
+ class Germinate::Formatter
5
+ include AlterEgo
6
+
7
+ attr_accessor :comment_prefix
8
+
9
+ def initialize(output=$stdio)
10
+ @output = output
11
+ end
12
+
13
+ state :initial, :default => true do
14
+ transition :to => :code, :on => :start!
15
+ end
16
+
17
+ state :code do
18
+ handle :add_line!, :add_code_line!
19
+
20
+ transition :to => :paragraph, :on => :paragraph!
21
+ transition :to => :finished, :on => :finish!
22
+ end
23
+
24
+ state :paragraph do
25
+ handle :add_line!, :add_paragraph_line!
26
+
27
+ transition :to => :linebreak, :on => :linebreak!
28
+ transition :to => :code, :on => :code!
29
+ transition :to => :finished, :on => :finish!
30
+
31
+ on_exit do
32
+ flush_paragraph!
33
+ end
34
+ end
35
+
36
+ state :linebreak do
37
+ handle :add_line!, :add_linebreak_line!
38
+
39
+ transition :to => :paragraph, :on => :paragraph! do
40
+ emit!("\n")
41
+ end
42
+
43
+ transition :to => :code, :on => :code!
44
+ transition :to => :finished, :on => :finish!
45
+
46
+ end
47
+
48
+ state :finished do
49
+
50
+ end
51
+
52
+ protected
53
+
54
+ def add_code_line!(line)
55
+ case line
56
+ when /\s*(\S+)?\s*:TEXT:/ then
57
+ self.comment_prefix = $1
58
+ paragraph!
59
+ end
60
+ end
61
+
62
+ def add_paragraph_line!(line)
63
+ case line
64
+ when /:CUT:/
65
+ code!
66
+ when text_pattern
67
+ paragraph_buffer << $1.chomp
68
+ when whitespace_pattern
69
+ linebreak!
70
+ end
71
+ end
72
+
73
+ def add_linebreak_line!(line)
74
+ case line
75
+ when /:CUT:/
76
+ code!
77
+ when text_pattern
78
+ paragraph_buffer << $1.chomp
79
+ paragraph!
80
+ else
81
+ # NOOP
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :output
88
+
89
+ def text_pattern
90
+ if comment_prefix
91
+ /^\s*#{comment_prefix}+\s*(\S+.*)$/
92
+ else
93
+ /^\s*(\S+.*)\s*$/
94
+ end
95
+ end
96
+
97
+ def whitespace_pattern
98
+ if comment_prefix
99
+ /^\s*#{comment_prefix}*\s*$/
100
+ else
101
+ /^\s*$/
102
+ end
103
+ end
104
+
105
+ def paragraph_buffer
106
+ @paragraph_buffer ||= []
107
+ end
108
+
109
+ def flush_paragraph!
110
+ emit!(paragraph_buffer.join(" "))
111
+ paragraph_buffer.clear
112
+ end
113
+
114
+ def emit!(text)
115
+ unless text.empty?
116
+ output.puts(text.chomp)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,183 @@
1
+ require 'pathname'
2
+ require 'ick'
3
+ require 'fattr'
4
+ require File.expand_path("shared_style_attributes", File.dirname(__FILE__))
5
+
6
+ # A Hunk represents a chunk of content. There are different types of Hunk, like
7
+ # Code or Text, which may be formatted differently by the Formatter. At its
8
+ # most basic a Hunk is just a list of Strings, each one representing a single
9
+ # line.
10
+ class Germinate::Hunk < ::Array
11
+ include Germinate::SharedStyleAttributes
12
+ Ick::Returning.belongs_to(self)
13
+
14
+ def initialize(contents=[], template = {})
15
+ super(contents)
16
+ copy_shared_style_attributes_from(template)
17
+ end
18
+
19
+ def to_s
20
+ "#{self.class.name}[#{origin}](#{self.size} lines)"
21
+ end
22
+
23
+ # return a copy with leading and trailing whitespace lines removed
24
+ def strip
25
+ Germinate::TextTransforms.strip_blanks.call(self)
26
+ end
27
+
28
+ def resolve_insertions
29
+ dup.map!{ |line_or_insertion|
30
+ if line_or_insertion.respond_to?(:resolve)
31
+ line_or_insertion.resolve
32
+ else
33
+ line_or_insertion
34
+ end
35
+ }
36
+ end
37
+
38
+ def format_with(formatter)
39
+ raise "Unresolved hunk cannot be formatted!" unless resolved?
40
+ if nested_hunks?
41
+ group_hunks.each do |hunk|
42
+ hunk.format_with(formatter)
43
+ end
44
+ else
45
+ yield formatter
46
+ end
47
+ end
48
+
49
+ def inspect
50
+ attrs = Germinate::SharedStyleAttributes.fattrs.inject({}) {|attrs, key|
51
+ attrs[key] = send(key)
52
+ attrs
53
+ }
54
+ "#{self.class}:#{super}:#{attrs.inspect}:#{object_id}"
55
+ end
56
+
57
+ def [](*args)
58
+ returning(super) do |slice|
59
+ if slice.kind_of?(Germinate::Hunk)
60
+ slice.copy_shared_style_attributes_from(self)
61
+ end
62
+ end
63
+ end
64
+
65
+ def slice(*args)
66
+ returning(super) do |slice|
67
+ if slice.kind_of?(Germinate::Hunk)
68
+ slice.copy_shared_style_attributes_from(self)
69
+ end
70
+ end
71
+ end
72
+
73
+ def index_matching(pattern, start_index=0)
74
+ (start_index...(size)).each { |i|
75
+ return i if pattern === self[i]
76
+ }
77
+ nil
78
+ end
79
+
80
+ def whole_file?
81
+ false
82
+ end
83
+
84
+ private
85
+
86
+ def resolved?
87
+ !unresolved_hunks?
88
+ end
89
+
90
+ def unresolved_hunks?
91
+ any?{|line| line.respond_to?(:resolve)}
92
+ end
93
+
94
+ def nested?
95
+ unresolved_hunks? || nested_hunks?
96
+ end
97
+
98
+ def nested_hunks?
99
+ any?{|line| line.respond_to?(:format_with)}
100
+ end
101
+
102
+ def group_hunks
103
+ return self unless nested?
104
+ groups = inject([empty_dup]) { |hunks, line_or_hunk|
105
+ if line_or_hunk.respond_to?(:format_with)
106
+ hunks << line_or_hunk
107
+ hunks << empty_dup
108
+ else
109
+ hunks.last << line_or_hunk
110
+ end
111
+ hunks
112
+ }
113
+ groups.delete_if{|g| g.empty?}
114
+ groups
115
+ end
116
+
117
+ # An empty duplicate retains metadata but has no lines
118
+ def empty_dup
119
+ returning(dup) do |duplicate|
120
+ duplicate.clear
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ # Represents a hunk of article text
127
+ class Germinate::TextHunk < Germinate::Hunk
128
+ def initialize(contents = [], template = {})
129
+ self.join_lines = true
130
+ self.strip_blanks = true
131
+ self.rstrip_lines = true
132
+ self.uncomment = true
133
+ self.expand_insertions = true
134
+ self.flatten_nested = true
135
+ super
136
+ end
137
+
138
+ def format_with(formatter)
139
+ super(formatter) do |formatter|
140
+ formatter.format_text!(self, comment_prefix)
141
+ end
142
+ end
143
+ end
144
+
145
+ # Represents a hunk of source code
146
+ class Germinate::CodeHunk < Germinate::Hunk
147
+ def initialize(contents = [], template = {})
148
+ self.strip_blanks = true
149
+ self.bracket = true
150
+ super
151
+ end
152
+
153
+ def code_open_bracket=(new_value)
154
+ super
155
+ end
156
+
157
+ def code_close_bracket=(new_value)
158
+ super
159
+ end
160
+
161
+
162
+ def format_with(formatter)
163
+ super(formatter) do |formatter|
164
+ formatter.format_code!(self, comment_prefix)
165
+ end
166
+ end
167
+ end
168
+
169
+ class Germinate::NullHunk < Germinate::Hunk
170
+ end
171
+
172
+ # Represents a the entire text of a file on disk
173
+ class Germinate::FileHunk < Germinate::CodeHunk
174
+ def initialize(lines, template)
175
+ super(lines, template)
176
+ raise ArgumentError, "Path required" if source_path.nil?
177
+ end
178
+
179
+ def whole_file?
180
+ true
181
+ end
182
+ end
183
+
@@ -0,0 +1,9 @@
1
+ require File.expand_path("insertion", File.dirname(__FILE__))
2
+
3
+ class Germinate::ImplicitInsertion < Germinate::Insertion
4
+ def resolve
5
+ super
6
+ rescue IndexError
7
+ Germinate::NullHunk.new
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ require 'fattr'
2
+ require 'ick'
3
+ require File.expand_path("shared_style_attributes", File.dirname(__FILE__))
4
+
5
+ class Germinate::Insertion
6
+ include Germinate::SharedStyleAttributes
7
+ Ick::Returning.belongs_to(self)
8
+
9
+ attr_reader :library
10
+ attr_reader :selector
11
+
12
+ fattr(:log) { Germinate.logger }
13
+
14
+ def initialize(selector, library, template={})
15
+ copy_shared_style_attributes_from(template)
16
+ @selector = selector
17
+ @library = library
18
+ end
19
+
20
+ def to_s
21
+ "Insertion[#{selector}]"
22
+ end
23
+
24
+ def resolve
25
+ returning(library[selector, self, self]) do |hunk|
26
+ log.debug "Resolved #{self} to #{hunk}"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,293 @@
1
+ require 'orderedhash'
2
+ require 'fattr'
3
+ require 'ick'
4
+ require File.expand_path("shared_style_attributes", File.dirname(__FILE__))
5
+
6
+ # The Librarian is responsible for organizing all the chunks of content derived
7
+ # from reading a source file and making them available for later re-assembly and
8
+ # formatting.
9
+ class Germinate::Librarian
10
+ include Germinate::SharedStyleAttributes
11
+ Ick::Returning.belongs_to(self)
12
+
13
+ class VariableStore < OrderedHash
14
+ def initialize(librarian)
15
+ super()
16
+ @librarian = librarian
17
+ end
18
+
19
+ def []=(key, value)
20
+ if key?(key)
21
+ variable = fetch(key)
22
+ variable.replace(value.to_s)
23
+ variable.update_source_line!(@librarian.comment_prefix)
24
+ else
25
+ variable =
26
+ case value
27
+ when Germinate::Variable
28
+ value
29
+ else
30
+ line_number = @librarian.lines.length + 1
31
+ line = ""
32
+ Germinate::Variable.new(
33
+ key, value, line, @librarian.source_path, line_number)
34
+ end
35
+ variable.update_source_line!(@librarian.comment_prefix)
36
+ store(key, variable)
37
+ @librarian.log.debug "Appending #{variable.line.inspect} to lines"
38
+ @librarian.lines << variable.line
39
+ end
40
+ @librarian.updated = true
41
+ end
42
+
43
+ end
44
+
45
+ attr_reader :lines
46
+ attr_reader :text_lines
47
+ attr_reader :code_lines
48
+ attr_reader :front_matter_lines
49
+
50
+ fattr :source_path => nil
51
+
52
+ fattr(:log) { Germinate.logger }
53
+ fattr(:variables) { VariableStore.new(self) }
54
+ fattr(:updated) { false }
55
+ fattr(:source_file) { Germinate::SourceFile.new(source_path) }
56
+
57
+ def initialize
58
+ @lines = []
59
+ @text_lines = []
60
+ @code_lines = []
61
+ @front_matter_lines = []
62
+ @sections = OrderedHash.new do |hash, key|
63
+ hash[key] = Germinate::TextHunk.new([], shared_style_attributes)
64
+ end
65
+ @samples = OrderedHash.new do |hash, key|
66
+ hash[key] = Germinate::CodeHunk.new([], shared_style_attributes)
67
+ end
68
+ @processes = {'_transform' => Germinate::TransformProcess.new}
69
+ @publishers = OrderedHash.new
70
+ end
71
+
72
+ def add_front_matter!(line)
73
+ add_line!(line)
74
+ @front_matter_lines << line
75
+ end
76
+
77
+ def add_control!(line)
78
+ add_line!(line)
79
+ end
80
+
81
+ def add_text!(section, line)
82
+ add_line!(line)
83
+ @text_lines << line
84
+ @sections[section] << line
85
+ end
86
+
87
+ def add_code!(sample, line)
88
+ add_line!(line)
89
+ @code_lines << line
90
+ @samples[sample] << line
91
+ end
92
+
93
+ def add_insertion!(section, selector, attributes)
94
+ insertion = Germinate::Insertion.new(selector, self, attributes)
95
+ @sections[section] << insertion
96
+ @text_lines << insertion
97
+ end
98
+
99
+ def set_code_attributes!(sample, attributes)
100
+ attributes.each_pair do |key, value|
101
+ @samples[sample].send(key, value) unless value.nil?
102
+ end
103
+ end
104
+
105
+ def add_process!(process_name, command)
106
+ @processes[process_name] =
107
+ Germinate::ShellProcess.new(process_name, command, variables)
108
+ end
109
+
110
+ def add_publisher!(name, identifier, options)
111
+ @publishers[name] = Germinate::Publisher.make(name, identifier, self, options)
112
+ end
113
+
114
+ def store_changes!
115
+ source_file.write!(lines)
116
+ end
117
+
118
+ def set_variable!(line, line_number, name, value)
119
+ variables.store(name,Germinate::Variable.new(name, value, line, source_path, line_number))
120
+ end
121
+
122
+ def comment_prefix_known?
123
+ !comment_prefix.nil?
124
+ end
125
+
126
+ def section(section_name)
127
+ unless has_section?(section_name)
128
+ raise IndexError,
129
+ "No text section named '#{section_name}'. "\
130
+ "Known sections: #{@sections.keys.join(', ')}"
131
+ end
132
+ Array(@sections[section_name])
133
+ end
134
+
135
+ def has_section?(section_name)
136
+ @sections.key?(section_name)
137
+ end
138
+
139
+ def sample(sample_name)
140
+ unless has_sample?(sample_name)
141
+ raise IndexError,
142
+ "No code sample named '#{sample_name}'. "\
143
+ "Known samples: #{@samples.keys.join(', ')}"
144
+ end
145
+ Array(@samples[sample_name])
146
+ end
147
+
148
+ # Fetch a process by name
149
+ def process(process_name)
150
+ @processes.fetch(process_name)
151
+ rescue IndexError => error
152
+ raise error.exception("Unknown process #{process_name.inspect}")
153
+ end
154
+
155
+ def process_names
156
+ @processes.keys
157
+ end
158
+
159
+ def publisher_names
160
+ @publishers.keys
161
+ end
162
+
163
+ # fetch a publisher by name
164
+ def publisher(publisher_name)
165
+ @publishers.fetch(publisher_name)
166
+ rescue IndexError => error
167
+ raise error.exception("Unknown publisher #{publisher_name.inspect}")
168
+ end
169
+
170
+ def has_sample?(sample_name)
171
+ @samples.key?(sample_name)
172
+ end
173
+
174
+ # TODO Too big, refactor.
175
+ def [](selector, origin="<Unknown>", template={})
176
+ log.debug "Selecting #{selector}, from #{origin}"
177
+ selector = case selector
178
+ when Germinate::Selector then selector
179
+ else Germinate::Selector.new(selector, "SECTION0", origin)
180
+ end
181
+ sample =
182
+ case selector.selector_type
183
+ when :code then
184
+ sample(selector.key)
185
+ when :special then
186
+ case selector.key
187
+ when "SOURCE"
188
+ source_hunk =
189
+ if selector.whole?
190
+ Germinate::FileHunk.new(lines, self)
191
+ else
192
+ Germinate::CodeHunk.new(lines, self)
193
+ end
194
+ source_hunk.disable_all_transforms!
195
+ source_hunk
196
+ when "CODE" then Germinate::CodeHunk.new(code_lines, self)
197
+ when "TEXT" then Germinate::TextHunk.new(text_lines, self)
198
+ else raise "Unknown special section '$#{selector.key}'"
199
+ end
200
+ else
201
+ raise Exception,
202
+ "Unknown selector type #{selector.selector_type.inspect}"
203
+ end
204
+
205
+ sample.copy_shared_style_attributes_from(template)
206
+ sample.origin.source_path ||= source_path
207
+ sample.origin.selector ||= selector
208
+
209
+ sample = if selector.excerpt_output?
210
+ excerpt(execute_pipeline(sample, selector.pipeline), selector)
211
+ else
212
+ execute_pipeline(excerpt(sample, selector), selector.pipeline)
213
+ end
214
+ sample
215
+ end
216
+
217
+ def section_names
218
+ @sections.keys
219
+ end
220
+
221
+ def sample_names
222
+ @samples.keys
223
+ end
224
+
225
+ # Given a list of process names or a '|'-delimited string, return a Pipeline
226
+ # object representing a super-process of all the named processes chained
227
+ # together.
228
+ def make_pipeline(process_names_or_string)
229
+ names =
230
+ if process_names_or_string.kind_of?(String)
231
+ process_names_or_string.split("|")
232
+ else
233
+ process_names_or_string
234
+ end
235
+ processes = names.map{|n| process(n)}
236
+ Germinate::Pipeline.new(processes)
237
+ end
238
+
239
+ private
240
+
241
+ def excerpt(sample, selector)
242
+ # TODO make excerpting just another TextTransform
243
+ start_offset = start_offset(sample, selector)
244
+ end_offset = end_offset(sample, selector, start_offset)
245
+ case selector.delimiter
246
+ when '..' then sample[start_offset..end_offset]
247
+ when '...' then sample[start_offset...end_offset]
248
+ when ',' then sample[start_offset, selector.length]
249
+ when nil then sample.dup.replace([sample[start_offset]])
250
+ else raise "Don't understand delimiter #{selector.delimiter.inspect}"
251
+ end
252
+ end
253
+
254
+ def add_line!(line)
255
+ line.chomp!
256
+ line << "\n"
257
+ @lines << line
258
+ end
259
+
260
+ def start_offset(hunk, selector)
261
+ offset = selector.start_offset_for_slice
262
+ case offset
263
+ when Integer then offset
264
+ when Regexp then
265
+ returning(hunk.index_matching(offset)) do |index|
266
+ if index.nil?
267
+ raise "Cannot find line matching #{offset.inspect} in #{selector}"
268
+ end
269
+ end
270
+ else
271
+ raise "Don't know how to use #{offset.inspect} as an offset"
272
+ end
273
+ end
274
+
275
+ def end_offset(hunk, selector, start_offset)
276
+ offset = selector.end_offset_for_slice
277
+ case offset
278
+ when Integer, nil then offset
279
+ when Regexp then
280
+ returning(hunk.index_matching(offset, start_offset)) do |index|
281
+ if index.nil?
282
+ raise "Cannot find line matching #{offset.inspect} in #{selector}"
283
+ end
284
+ end
285
+ else
286
+ raise "Don't know how to use #{offset.inspect} as an offset"
287
+ end
288
+ end
289
+
290
+ def execute_pipeline(hunk, names)
291
+ make_pipeline(names).call(hunk)
292
+ end
293
+ end