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.
Files changed (86) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +4 -0
  3. data/README.rdoc +132 -0
  4. data/Rakefile +43 -0
  5. data/bin/germ +133 -0
  6. data/cucumber.yml +2 -0
  7. data/examples/basic.rb +118 -0
  8. data/examples/short.rb +17 -0
  9. data/features/author-formats-article.feature +108 -0
  10. data/features/author-lists-info.feature +45 -0
  11. data/features/author-publishes-article-source.feature +5 -0
  12. data/features/author-publishes-article.feature +5 -0
  13. data/features/author-republishes-article.feature +5 -0
  14. data/features/author-selects-hunks.feature +26 -0
  15. data/features/author-updates-article-source.feature +5 -0
  16. data/features/author-views-stuff.feature +48 -0
  17. data/features/bin/quoter +6 -0
  18. data/features/bin/sorter +4 -0
  19. data/features/example_articles/code_samples.rb +89 -0
  20. data/features/example_articles/escaping.txt +12 -0
  21. data/features/example_articles/hello.rb +9 -0
  22. data/features/example_articles/pipelines.txt +25 -0
  23. data/features/example_articles/regexen.rb +24 -0
  24. data/features/example_articles/sample_offsets.rb +18 -0
  25. data/features/example_articles/specials.rb +19 -0
  26. data/features/example_articles/wrapping.rb +8 -0
  27. data/features/example_output/code_samples.txt +186 -0
  28. data/features/example_output/escaping.out +5 -0
  29. data/features/example_output/hello.txt +1 -0
  30. data/features/example_output/pipelines.out +28 -0
  31. data/features/example_output/regexen.txt +22 -0
  32. data/features/example_output/sample_offsets.txt +15 -0
  33. data/features/example_output/specials.txt +36 -0
  34. data/features/example_output/wrapping.txt +3 -0
  35. data/features/step_definitions/germinate.rb +30 -0
  36. data/features/support/env.rb +18 -0
  37. data/germinate.gemspec +55 -0
  38. data/lib/germinate.rb +54 -0
  39. data/lib/germinate/application.rb +62 -0
  40. data/lib/germinate/article_editor.rb +20 -0
  41. data/lib/germinate/article_formatter.rb +75 -0
  42. data/lib/germinate/formatter.rb +119 -0
  43. data/lib/germinate/hunk.rb +149 -0
  44. data/lib/germinate/implicit_insertion.rb +9 -0
  45. data/lib/germinate/insertion.rb +15 -0
  46. data/lib/germinate/librarian.rb +179 -0
  47. data/lib/germinate/pipeline.rb +11 -0
  48. data/lib/germinate/process.rb +67 -0
  49. data/lib/germinate/reader.rb +212 -0
  50. data/lib/germinate/selector.rb +95 -0
  51. data/lib/germinate/shared_style_attributes.rb +23 -0
  52. data/lib/germinate/text_transforms.rb +90 -0
  53. data/spec/germinate/application_spec.rb +14 -0
  54. data/spec/germinate/article_editor_spec.rb +97 -0
  55. data/spec/germinate/article_formatter_spec.rb +153 -0
  56. data/spec/germinate/code_hunk_spec.rb +45 -0
  57. data/spec/germinate/formatter_spec.rb +160 -0
  58. data/spec/germinate/hunk_spec.rb +77 -0
  59. data/spec/germinate/implicit_insertion_spec.rb +33 -0
  60. data/spec/germinate/insertion_spec.rb +18 -0
  61. data/spec/germinate/librarian_spec.rb +336 -0
  62. data/spec/germinate/pipeline_spec.rb +24 -0
  63. data/spec/germinate/process_spec.rb +64 -0
  64. data/spec/germinate/reader_spec.rb +306 -0
  65. data/spec/germinate/selector_spec.rb +65 -0
  66. data/spec/germinate/text_hunk_spec.rb +53 -0
  67. data/spec/germinate/text_transforms_spec.rb +154 -0
  68. data/spec/germinate_spec.rb +8 -0
  69. data/spec/spec.opts +1 -0
  70. data/spec/spec_helper.rb +16 -0
  71. data/tasks/ann.rake +80 -0
  72. data/tasks/bones.rake +20 -0
  73. data/tasks/cucumber.rake +5 -0
  74. data/tasks/gem.rake +201 -0
  75. data/tasks/git.rake +40 -0
  76. data/tasks/notes.rake +27 -0
  77. data/tasks/post_load.rake +34 -0
  78. data/tasks/rdoc.rake +51 -0
  79. data/tasks/rubyforge.rake +55 -0
  80. data/tasks/setup.rb +292 -0
  81. data/tasks/spec.rake +54 -0
  82. data/tasks/svn.rake +47 -0
  83. data/tasks/test.rake +40 -0
  84. data/tasks/zentest.rake +36 -0
  85. data/test/test_germinate.rb +0 -0
  86. 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,14 @@
1
+ require File.expand_path(
2
+ File.join(File.dirname(__FILE__), %w[.. .. lib germinate]))
3
+
4
+ module Germinate
5
+
6
+ describe Application do
7
+ before :each do
8
+ @it = Application.new
9
+ end
10
+
11
+
12
+ end
13
+
14
+ 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