devver-germinate 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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