devver-germinate 1.0.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.
- data/.gitignore +2 -0
- data/History.txt +4 -0
- data/README.rdoc +132 -0
- data/Rakefile +43 -0
- data/bin/germ +133 -0
- data/cucumber.yml +2 -0
- data/examples/basic.rb +118 -0
- data/examples/short.rb +17 -0
- data/features/author-formats-article.feature +108 -0
- data/features/author-lists-info.feature +45 -0
- data/features/author-publishes-article-source.feature +5 -0
- data/features/author-publishes-article.feature +5 -0
- data/features/author-republishes-article.feature +5 -0
- data/features/author-selects-hunks.feature +26 -0
- data/features/author-updates-article-source.feature +5 -0
- data/features/author-views-stuff.feature +48 -0
- data/features/bin/quoter +6 -0
- data/features/bin/sorter +4 -0
- data/features/example_articles/code_samples.rb +89 -0
- data/features/example_articles/escaping.txt +12 -0
- data/features/example_articles/hello.rb +9 -0
- data/features/example_articles/pipelines.txt +25 -0
- data/features/example_articles/regexen.rb +24 -0
- data/features/example_articles/sample_offsets.rb +18 -0
- data/features/example_articles/specials.rb +19 -0
- data/features/example_articles/wrapping.rb +8 -0
- data/features/example_output/code_samples.txt +186 -0
- data/features/example_output/escaping.out +5 -0
- data/features/example_output/hello.txt +1 -0
- data/features/example_output/pipelines.out +28 -0
- data/features/example_output/regexen.txt +22 -0
- data/features/example_output/sample_offsets.txt +15 -0
- data/features/example_output/specials.txt +36 -0
- data/features/example_output/wrapping.txt +3 -0
- data/features/step_definitions/germinate.rb +30 -0
- data/features/support/env.rb +18 -0
- data/germinate.gemspec +55 -0
- data/lib/germinate.rb +54 -0
- data/lib/germinate/application.rb +62 -0
- data/lib/germinate/article_editor.rb +20 -0
- data/lib/germinate/article_formatter.rb +75 -0
- data/lib/germinate/formatter.rb +119 -0
- data/lib/germinate/hunk.rb +149 -0
- data/lib/germinate/implicit_insertion.rb +9 -0
- data/lib/germinate/insertion.rb +15 -0
- data/lib/germinate/librarian.rb +179 -0
- data/lib/germinate/pipeline.rb +11 -0
- data/lib/germinate/process.rb +67 -0
- data/lib/germinate/reader.rb +212 -0
- data/lib/germinate/selector.rb +95 -0
- data/lib/germinate/shared_style_attributes.rb +23 -0
- data/lib/germinate/text_transforms.rb +90 -0
- data/spec/germinate/application_spec.rb +14 -0
- data/spec/germinate/article_editor_spec.rb +97 -0
- data/spec/germinate/article_formatter_spec.rb +153 -0
- data/spec/germinate/code_hunk_spec.rb +45 -0
- data/spec/germinate/formatter_spec.rb +160 -0
- data/spec/germinate/hunk_spec.rb +77 -0
- data/spec/germinate/implicit_insertion_spec.rb +33 -0
- data/spec/germinate/insertion_spec.rb +18 -0
- data/spec/germinate/librarian_spec.rb +336 -0
- data/spec/germinate/pipeline_spec.rb +24 -0
- data/spec/germinate/process_spec.rb +64 -0
- data/spec/germinate/reader_spec.rb +306 -0
- data/spec/germinate/selector_spec.rb +65 -0
- data/spec/germinate/text_hunk_spec.rb +53 -0
- data/spec/germinate/text_transforms_spec.rb +154 -0
- data/spec/germinate_spec.rb +8 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/cucumber.rake +5 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test_germinate.rb +0 -0
- metadata +209 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
class Germinate::Selector
|
2
|
+
attr_reader :string
|
3
|
+
attr_reader :selector_type
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :start_offset
|
6
|
+
attr_reader :end_offset
|
7
|
+
attr_reader :length
|
8
|
+
attr_reader :delimiter
|
9
|
+
attr_reader :pipeline
|
10
|
+
attr_reader :default_key
|
11
|
+
|
12
|
+
PATTERN = /([@$])?(\w+)?(:([^\s\|]+))?(\|([\w|]+))?/
|
13
|
+
EXCERPT_PATTERN = %r{((-?\d+)|(/[^/]*/))(((\.\.\.?)|(,))((-?\d+)|(/[^/]*/)))?}
|
14
|
+
|
15
|
+
def initialize(string, default_key)
|
16
|
+
@string = string
|
17
|
+
@default_key = default_key
|
18
|
+
match_data = case string
|
19
|
+
when "", nil then {}
|
20
|
+
else PATTERN.match(string)
|
21
|
+
end
|
22
|
+
|
23
|
+
@selector_type =
|
24
|
+
case match_data[1]
|
25
|
+
when "$" then :special
|
26
|
+
when "@", nil then :code
|
27
|
+
else raise "Unknown selector type '#{match_data[1]}'"
|
28
|
+
end
|
29
|
+
@key = match_data[2] || default_key
|
30
|
+
if match_data[3]
|
31
|
+
parse_excerpt(match_data[3])
|
32
|
+
else
|
33
|
+
@delimiter = '..'
|
34
|
+
@start_offset = 1
|
35
|
+
@end_offset = -1
|
36
|
+
@length = nil
|
37
|
+
end
|
38
|
+
@pipeline = String(match_data[6]).split("|")
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_offset_for_slice
|
42
|
+
offset_for_slice(start_offset)
|
43
|
+
end
|
44
|
+
|
45
|
+
def end_offset_for_slice
|
46
|
+
offset_for_slice(end_offset)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def parse_excerpt(excerpt)
|
52
|
+
match_data = EXCERPT_PATTERN.match(excerpt)
|
53
|
+
integer_start_offset = match_data[2]
|
54
|
+
regexp_start_offset = match_data[3]
|
55
|
+
@delimiter = match_data[5]
|
56
|
+
integer_end_offset = match_data[9]
|
57
|
+
regexp_end_offset = match_data[10]
|
58
|
+
|
59
|
+
if integer_start_offset
|
60
|
+
@start_offset = integer_start_offset.to_i
|
61
|
+
elsif regexp_start_offset
|
62
|
+
@start_offset = Regexp.new(regexp_start_offset[1..-2])
|
63
|
+
else
|
64
|
+
raise "Could not parse start offset '#{match_data[1]}'"
|
65
|
+
end
|
66
|
+
|
67
|
+
case @delimiter
|
68
|
+
when '..', '...'
|
69
|
+
if integer_end_offset
|
70
|
+
@end_offset = integer_end_offset.to_i
|
71
|
+
elsif regexp_end_offset
|
72
|
+
@end_offset = Regexp.new(regexp_end_offset[1..-2])
|
73
|
+
end
|
74
|
+
when nil
|
75
|
+
@end_offset = @start_offset
|
76
|
+
when ','
|
77
|
+
@length = integer_end_offset.to_i
|
78
|
+
else raise "Should not get here"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def integer_offsets?
|
84
|
+
Integer === @start_offset && Integer === @end_offset
|
85
|
+
end
|
86
|
+
|
87
|
+
def offset_for_slice(offset)
|
88
|
+
return offset if offset.nil? || offset.kind_of?(Regexp)
|
89
|
+
if offset < 1
|
90
|
+
offset
|
91
|
+
else
|
92
|
+
offset - 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'fattr'
|
2
|
+
|
3
|
+
module Germinate::SharedStyleAttributes
|
4
|
+
fattr :comment_prefix
|
5
|
+
fattr :code_open_bracket => nil
|
6
|
+
fattr :code_close_bracket => nil
|
7
|
+
fattr :pipeline => []
|
8
|
+
|
9
|
+
def shared_style_attributes
|
10
|
+
Germinate::SharedStyleAttributes.fattrs.inject({}) {
|
11
|
+
|attributes, key|
|
12
|
+
|
13
|
+
attributes[key] = send(key)
|
14
|
+
attributes
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def copy_shared_style_attrubutes_from(other)
|
19
|
+
other.shared_style_attributes.each_pair do |key, value|
|
20
|
+
self.send(key, value) unless other.send(key).nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# A library of text transforms. Each public module method returns a callable
|
2
|
+
# object which can be used to transform an array of lines.
|
3
|
+
module Germinate::TextTransforms
|
4
|
+
|
5
|
+
IDENTITY_TRANSFORM = lambda {|hunk| hunk}
|
6
|
+
def self.join_lines
|
7
|
+
lambda { |hunk|
|
8
|
+
paragraphs = hunk.inject([[]]) { |result, line|
|
9
|
+
case line
|
10
|
+
when /\S/
|
11
|
+
result.last << line.strip
|
12
|
+
else
|
13
|
+
result << [line]
|
14
|
+
result << []
|
15
|
+
end
|
16
|
+
result
|
17
|
+
}
|
18
|
+
paragraphs.delete_if{|p| p.empty?}
|
19
|
+
hunk.dup.replace(paragraphs.map {|paragraph|
|
20
|
+
paragraph.join(" ")
|
21
|
+
})
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.strip_blanks
|
26
|
+
lambda { |hunk|
|
27
|
+
result = hunk.dup
|
28
|
+
result.shift until result.first =~ /\S/ || result.empty?
|
29
|
+
result.pop until result.last =~ /\S/ || result.empty?
|
30
|
+
result
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.erase_comments(comment_prefix="")
|
35
|
+
lambda { |hunk|
|
36
|
+
hunk.dup.map! do |line|
|
37
|
+
if comment_prefix
|
38
|
+
if match_data = /^\s*(#{comment_prefix})+\s*/.match(line)
|
39
|
+
offset = match_data.begin(0)
|
40
|
+
length = match_data[0].length
|
41
|
+
line_copy = line.dup
|
42
|
+
line_copy[offset, length] = (" " * length)
|
43
|
+
line_copy
|
44
|
+
else
|
45
|
+
line
|
46
|
+
end
|
47
|
+
else
|
48
|
+
line
|
49
|
+
end
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.uncomment(comment_prefix="")
|
55
|
+
lambda { |hunk|
|
56
|
+
hunk.dup.map! do |line|
|
57
|
+
if comment_prefix
|
58
|
+
line.sub(/^#{comment_prefix}/,"")
|
59
|
+
else
|
60
|
+
line
|
61
|
+
end
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.rstrip_lines
|
67
|
+
lambda { |hunk|
|
68
|
+
hunk.dup.map!{|line| line.to_s.rstrip}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.bracket(open_bracket=nil, close_bracket=nil)
|
73
|
+
lambda { |hunk|
|
74
|
+
result = hunk.dup
|
75
|
+
result.clear
|
76
|
+
result << (open_bracket || hunk.code_open_bracket)
|
77
|
+
result.push(*Array(hunk))
|
78
|
+
result << (close_bracket || hunk.code_close_bracket)
|
79
|
+
result.compact!
|
80
|
+
result
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.pipeline(pipeline=nil)
|
85
|
+
lambda do |hunk|
|
86
|
+
pipeline ||= hunk.pipeline
|
87
|
+
pipeline.call(hunk)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.expand_path(
|
2
|
+
File.join(File.dirname(__FILE__), %w[.. .. lib germinate]))
|
3
|
+
|
4
|
+
module Germinate
|
5
|
+
describe ArticleEditor do
|
6
|
+
before :each do
|
7
|
+
@librarian = Librarian.new
|
8
|
+
@it = ArticleEditor.new(@librarian)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "with an empty library" do
|
12
|
+
it "should yield no hunks" do
|
13
|
+
collect_hunks.should be_empty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with a text section and matching code section" do
|
18
|
+
before :each do
|
19
|
+
@librarian.add_text!("SECTION1", "this is the text")
|
20
|
+
@librarian.add_text!("SECTION1", "text line 2")
|
21
|
+
@librarian.add_code!("SECTION1", "this is the code")
|
22
|
+
@librarian.add_code!("SECTION1", "code line 2")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should yield text and code in order" do
|
26
|
+
collect_hunks.should == [
|
27
|
+
["this is the text", "text line 2"],
|
28
|
+
["this is the code", "code line 2"]
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should yield text section as TextHunk" do
|
33
|
+
collect_hunks[0].should be_a_kind_of(TextHunk)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should yield code section as CodeHunk" do
|
37
|
+
collect_hunks[1].should be_a_kind_of(CodeHunk)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should set no comment prefix on hunks" do
|
41
|
+
collect_hunks[0].comment_prefix.should be_nil
|
42
|
+
collect_hunks[1].comment_prefix.should be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with a text section and mis-matched code section" do
|
47
|
+
before :each do
|
48
|
+
@librarian.add_text!("SECTION1", "this is the text")
|
49
|
+
@librarian.add_text!("SECTION1", "text line 2")
|
50
|
+
@librarian.add_code!("SECTION2", "this is the code")
|
51
|
+
@librarian.add_code!("SECTION2", "code line 2")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should yield just the text" do
|
55
|
+
collect_hunks.should == [
|
56
|
+
["this is the text", "text line 2"]
|
57
|
+
]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with a known comment prefix" do
|
62
|
+
before :each do
|
63
|
+
@librarian.comment_prefix = ";;"
|
64
|
+
@librarian.add_text!("SECTION1", "this is the text")
|
65
|
+
@librarian.add_code!("SECTION1", "this is the code")
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when iterating through hunks" do
|
71
|
+
before :each do
|
72
|
+
@resolved = stub("Resolved Section")
|
73
|
+
@section = stub("Unresolved Section",
|
74
|
+
:resolve_insertions => @resolved)
|
75
|
+
@librarian = stub("Librarian",
|
76
|
+
:section_names => ["section1"],
|
77
|
+
:section => @section,
|
78
|
+
:sample => @section,
|
79
|
+
:has_sample? => true)
|
80
|
+
@it = ArticleEditor.new(@librarian)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should resolve hunks before yielding them" do
|
84
|
+
collect_hunks[0].should equal(@resolved)
|
85
|
+
collect_hunks[1].should equal(@resolved)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def collect_hunks
|
90
|
+
hunks = []
|
91
|
+
@it.each_hunk do |hunk|
|
92
|
+
hunks << hunk
|
93
|
+
end
|
94
|
+
hunks
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
require File.expand_path(
|
4
|
+
File.join(File.dirname(__FILE__), %w[.. .. lib germinate]))
|
5
|
+
|
6
|
+
module Germinate
|
7
|
+
describe ArticleFormatter do
|
8
|
+
before :each do
|
9
|
+
@output = StringIO.new
|
10
|
+
@hunk1 = stub("Hunk1")
|
11
|
+
@hunk2 = stub("Hunk2")
|
12
|
+
@it = ArticleFormatter.new(@output)
|
13
|
+
end
|
14
|
+
|
15
|
+
def output
|
16
|
+
@output.rewind
|
17
|
+
@output.string
|
18
|
+
end
|
19
|
+
|
20
|
+
context "given some hunks to format" do
|
21
|
+
before :each do
|
22
|
+
@hunks = [@hunk1, @hunk2]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should visit each hunk in turn" do
|
26
|
+
@hunk1.should_receive(:format_with).with(@it).ordered
|
27
|
+
@hunk2.should_receive(:format_with).with(@it).ordered
|
28
|
+
@hunks.each do |hunk|
|
29
|
+
@it.format!(hunk)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
context "given some text lines to format but no comment prefix" do
|
36
|
+
before :each do
|
37
|
+
@it.join_lines = false
|
38
|
+
@it.format_text!(Hunk.new([" # foo", "bar\n\n"]))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should just normalise newlines" do
|
42
|
+
output.should == " # foo\nbar\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "given some text lines to format and a comment prefix" do
|
47
|
+
before :each do
|
48
|
+
@it.comment_prefix = "# "
|
49
|
+
@it.uncomment = true
|
50
|
+
@it.join_lines = false
|
51
|
+
@it.format_text!(Hunk.new(["# foo", "bar\n\n"]), "# ")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should erase comments" do
|
55
|
+
output.should == " foo\nbar\n"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "given leading and trailing blank lines around text" do
|
60
|
+
before :each do
|
61
|
+
@it.format_text!(Hunk.new(["", "foo", " \n "]), "#")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should erase comments" do
|
65
|
+
output.should == "foo\n"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "given uncommenting is enabled and a comment prefix is set" do
|
70
|
+
it "should supply the comment prefix to the uncomment transform" do
|
71
|
+
@hunk = stub("Hunk").as_null_object
|
72
|
+
@hunk.stub!(:strip).and_return(@hunk)
|
73
|
+
@prefix = "//"
|
74
|
+
@transform = stub("Transform", :call => @transformed)
|
75
|
+
TextTransforms.stub!(:uncomment).and_return(@transform)
|
76
|
+
|
77
|
+
TextTransforms.should_receive(:uncomment).with(@prefix).
|
78
|
+
and_return(lambda{|h| h})
|
79
|
+
|
80
|
+
@it.comment_prefix = @prefix
|
81
|
+
@it.uncomment = true
|
82
|
+
@it.format_text!(@hunk)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
CODE_TRANSFORMS = %w[rstrip_lines strip_blanks]
|
87
|
+
TEXT_TRANSFORMS = %w[join_lines strip_blanks uncomment rstrip_lines]
|
88
|
+
TEXT_TRANSFORMS.each do |transform_name|
|
89
|
+
context "when only #{transform_name} is enabled" do
|
90
|
+
before :each do
|
91
|
+
(TEXT_TRANSFORMS - [transform_name]).each do |disabled_transform|
|
92
|
+
@it.send("#{disabled_transform}=", false)
|
93
|
+
TextTransforms.stub!(disabled_transform) do
|
94
|
+
fail "Transform #{disabled_transform} should not be enabled"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@it.send("#{transform_name}=", true)
|
98
|
+
|
99
|
+
@transformed = stub("Transformed Hunk").as_null_object
|
100
|
+
@hunk = stub("Hunk").as_null_object
|
101
|
+
@hunk.stub!(:strip).and_return(@hunk)
|
102
|
+
@transform = stub("Transform", :call => @transformed)
|
103
|
+
TextTransforms.stub!(transform_name).and_return(@transform)
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should perform transform on text hunks" do
|
108
|
+
@transform.should_receive(:call).with(@hunk)
|
109
|
+
@it.format_text!(@hunk)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should return the result of the transform" do
|
113
|
+
@it.format_text!(@hunk).should == @transformed
|
114
|
+
end
|
115
|
+
|
116
|
+
unless CODE_TRANSFORMS.include?(transform_name)
|
117
|
+
it "should not perform transform on code hunks" do
|
118
|
+
@transform.should_not_receive(:call).with(@hunk)
|
119
|
+
@it.format_code!(@hunk)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "given some code lines to format" do
|
126
|
+
before :each do
|
127
|
+
@it.format_code!(Hunk.new([" \n ", " # foo", "bar\n\n", ""]))
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should normalise newlines and strip blanks" do
|
131
|
+
output.should == " # foo\nbar\n"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "given a code hunk with brackets specified" do
|
136
|
+
before :each do
|
137
|
+
@hunk = Hunk.new(["line 1"],
|
138
|
+
:code_open_bracket => "<<<",
|
139
|
+
:code_close_bracket => ">>>")
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should wrap the code in brackets" do
|
143
|
+
@transform = stub("Bracketer")
|
144
|
+
TextTransforms.should_receive(:bracket).
|
145
|
+
with().and_return(@transform)
|
146
|
+
@transform.should_receive(:call).with(["line 1"]).and_return([])
|
147
|
+
@it.format_code!(@hunk)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|