devver-germinate 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/History.txt +12 -0
  2. data/README.rdoc +26 -4
  3. data/TODO +80 -8
  4. data/bin/germ +162 -38
  5. data/examples/basic.rb +14 -9
  6. data/examples/short.rb +2 -0
  7. data/features/author-formats-article.feature +3 -3
  8. data/features/{author-lists-info.feature → author-lists-info.pending_feature} +3 -0
  9. data/features/author-publishes-article.feature +52 -0
  10. data/features/author-selects-hunks.feature +1 -1
  11. data/features/author-sets-variables.feature +88 -0
  12. data/features/{author-views-stuff.feature → author-views-stuff.pending_feature} +4 -0
  13. data/features/example_articles/escaping.txt +1 -0
  14. data/features/example_articles/specials.rb +3 -3
  15. data/features/example_output/specials.txt +9 -5
  16. data/features/step_definitions/germinate.rb +9 -0
  17. data/germinate.gemspec +3 -3
  18. data/lib/germinate.rb +1 -1
  19. data/lib/germinate/application.rb +82 -31
  20. data/lib/germinate/hunk.rb +20 -0
  21. data/lib/germinate/insertion.rb +10 -2
  22. data/lib/germinate/librarian.rb +129 -31
  23. data/lib/germinate/origin.rb +5 -0
  24. data/lib/germinate/pipeline.rb +2 -0
  25. data/lib/germinate/publisher.rb +57 -0
  26. data/lib/germinate/reader.rb +51 -8
  27. data/lib/germinate/selector.rb +18 -6
  28. data/lib/germinate/shared_style_attributes.rb +18 -1
  29. data/lib/germinate/{process.rb → shell_process.rb} +27 -8
  30. data/lib/germinate/shell_publisher.rb +19 -0
  31. data/lib/germinate/simple_publisher.rb +7 -0
  32. data/lib/germinate/source_file.rb +41 -0
  33. data/lib/germinate/text_transforms.rb +38 -9
  34. data/lib/germinate/transform_process.rb +25 -0
  35. data/lib/germinate/variable.rb +23 -0
  36. data/sample.rb +14 -0
  37. data/spec/germinate/application_spec.rb +18 -1
  38. data/spec/germinate/article_editor_spec.rb +3 -3
  39. data/spec/germinate/code_hunk_spec.rb +28 -0
  40. data/spec/germinate/file_hunk_spec.rb +1 -0
  41. data/spec/germinate/hunk_spec.rb +1 -0
  42. data/spec/germinate/insertion_spec.rb +2 -1
  43. data/spec/germinate/librarian_spec.rb +280 -85
  44. data/spec/germinate/pipeline_spec.rb +10 -0
  45. data/spec/germinate/process_spec.rb +31 -6
  46. data/spec/germinate/publisher_spec.rb +130 -0
  47. data/spec/germinate/reader_spec.rb +58 -2
  48. data/spec/germinate/selector_spec.rb +34 -14
  49. data/spec/germinate/shell_publisher_spec.rb +61 -0
  50. data/spec/germinate/source_file_spec.rb +99 -0
  51. data/spec/germinate/text_hunk_spec.rb +45 -0
  52. data/spec/germinate/text_transforms_spec.rb +90 -2
  53. data/spec/germinate/transform_process_spec.rb +50 -0
  54. data/spec/germinate/variable_spec.rb +14 -0
  55. metadata +19 -7
  56. data/lib/germinate/article_formatter.rb +0 -75
  57. data/spec/germinate/article_formatter_spec.rb +0 -153
@@ -8,14 +8,16 @@ class Germinate::Selector
8
8
  attr_reader :delimiter
9
9
  attr_reader :pipeline
10
10
  attr_reader :default_key
11
+ attr_reader :origin
11
12
 
12
- PATTERN = /^([@$])?(\w+)?(:([^\s\|]+))?(\|([\w|]+))?$/
13
- EXCERPT_OUTPUT_PATTERN = /^([@$])?(\w+)?(\|([\w|]+))?(:([^\s\|]+))?$/
13
+ PATTERN = /^([@$])?(\w+)?(:([^\|]+))?(\|([\w|]+))?$/
14
+ EXCERPT_OUTPUT_PATTERN = /^([@$])?(\w+)?(\|([\w|]+))?(:([^\|]+))?$/
14
15
  EXCERPT_PATTERN = %r{((-?\d+)|(/[^/]*/))(((\.\.\.?)|(,))((-?\d+)|(/[^/]*/)))?}
15
16
 
16
- def initialize(string, default_key)
17
+ def initialize(string, default_key=:unspecified, origin="<Unknown>")
17
18
  @string = string
18
19
  @default_key = default_key
20
+ @origin = origin
19
21
  match_data = case string
20
22
  when "", nil then {}
21
23
  else
@@ -35,10 +37,11 @@ class Germinate::Selector
35
37
  @selector_type =
36
38
  case match_data[1]
37
39
  when "$" then :special
38
- when "@", nil then :code
40
+ when "@" then :code
41
+ when nil then default_key == :unspecified ? :special : :code
39
42
  else raise "Unknown selector type '#{match_data[1]}'"
40
43
  end
41
- @key = match_data[2] || default_key
44
+ @key = match_data[2] || (default_key == :unspecified ? "SOURCE" : default_key)
42
45
  if match_data[subscript_index]
43
46
  @slice = true
44
47
  parse_excerpt(match_data[subscript_index])
@@ -49,7 +52,8 @@ class Germinate::Selector
49
52
  @end_offset = -1
50
53
  @length = nil
51
54
  end
52
- @pipeline = String(match_data[pipeline_index]).split("|")
55
+ @pipeline = String(match_data[pipeline_index]).
56
+ split("|").unshift('_transform').uniq
53
57
  end
54
58
 
55
59
  def start_offset_for_slice
@@ -75,6 +79,14 @@ class Germinate::Selector
75
79
  !slice?
76
80
  end
77
81
 
82
+ def to_s
83
+ string + "(#{origin})"
84
+ end
85
+
86
+ def ==(other)
87
+ string == other
88
+ end
89
+
78
90
  private
79
91
 
80
92
  def parse_excerpt(excerpt)
@@ -1,11 +1,28 @@
1
+ require File.expand_path("text_transforms", File.dirname(__FILE__))
2
+ require File.expand_path("pipeline", File.dirname(__FILE__))
3
+ require File.expand_path("origin", File.dirname(__FILE__))
1
4
  require 'fattr'
2
5
 
3
6
  module Germinate::SharedStyleAttributes
4
7
  fattr :comment_prefix
5
8
  fattr :code_open_bracket => nil
6
9
  fattr :code_close_bracket => nil
7
- fattr :pipeline => []
10
+ fattr :pipeline => Germinate::Pipeline.new([])
8
11
  fattr :source_path => nil
12
+ fattr :origin => Germinate::Origin.new
13
+
14
+
15
+ TEXT_TRANSFORMS = Germinate::TextTransforms.singleton_methods
16
+
17
+ (TEXT_TRANSFORMS - ['pipeline']).each do |transform|
18
+ fattr(transform, false)
19
+ end
20
+
21
+ def disable_all_transforms!
22
+ TEXT_TRANSFORMS.each do |transform|
23
+ self.send("#{transform}=", false)
24
+ end
25
+ end
9
26
 
10
27
  def shared_style_attributes
11
28
  Germinate::SharedStyleAttributes.fattrs.inject({}) {
@@ -3,17 +3,19 @@ require 'fattr'
3
3
  require 'English'
4
4
  require 'main'
5
5
 
6
- # A Process represents an external command which can be used to process a Hunk
6
+ # A ShellProcess represents an external command which can be used to process a Hunk
7
7
  # of text.
8
- class Germinate::Process
8
+ class Germinate::ShellProcess
9
9
  attr_reader :name
10
10
  attr_reader :command
11
+ attr_reader :variables
11
12
 
12
13
  fattr(:log) { Germinate.logger }
13
14
 
14
- def initialize(name, command)
15
- @name = name
16
- @command = command
15
+ def initialize(name, command, variables={})
16
+ @name = name
17
+ @command = command
18
+ @variables = variables
17
19
  end
18
20
 
19
21
  def call(input)
@@ -65,11 +67,28 @@ class Germinate::Process
65
67
  end
66
68
 
67
69
  def log_popen(command, mode, &block)
68
- log.debug "Running command '#{command}'"
69
- IO.popen(command, mode, &block)
70
+ log.debug "Running command `#{command}`"
71
+ with_environment_variables(@variables) do
72
+ IO.popen(command, mode, &block)
73
+ end
70
74
  status = $CHILD_STATUS
71
- unless status.success?
75
+ unless status.nil? ||status.success?
72
76
  log.warn "Command '#{command}' exited with status #{status}"
73
77
  end
74
78
  end
79
+
80
+ def with_environment_variables(variables)
81
+ old_values = variables.inject({}) do |original, (name, variable)|
82
+ original[name.to_s] = ENV[name.to_s]
83
+ ENV[name.to_s] = variable.to_s
84
+ original
85
+ end
86
+ yield
87
+ ensure
88
+ (old_values || {}).each_pair do |name, value|
89
+ if value.nil? then ENV.delete(name)
90
+ else ENV[name] = value
91
+ end
92
+ end
93
+ end
75
94
  end
@@ -0,0 +1,19 @@
1
+ class Germinate::ShellPublisher < Germinate::Publisher
2
+ identifier 'shell'
3
+
4
+ def initialize(name, librarian, options)
5
+ @command = options.delete(:command) do
6
+ raise ArgumentError,
7
+ "A 'command' option must be supplied for publisher type 'shell'"
8
+ end
9
+ super
10
+ end
11
+
12
+ def publish!(output, extra_options={})
13
+ process = Germinate::ShellProcess.new(name, @command, librarian.variables)
14
+ processed = process.call(input)
15
+ processed.each do |line|
16
+ output.print(line)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ class Germinate::SimplePublisher < Germinate::Publisher
2
+ def publish!(output, extra_options={})
3
+ input.each do |line|
4
+ output.puts(line)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ require 'pathname'
2
+ require 'fattr'
3
+ require 'fileutils'
4
+
5
+ # SourcePath represents an article source file on disk.
6
+ class Germinate::SourceFile
7
+ fattr :path
8
+ fattr(:backup_path) { Pathname(path.to_s + ".germ.bak") }
9
+ fattr(:log) { Germinate.logger }
10
+
11
+ def initialize(path)
12
+ @path = Pathname(path)
13
+ end
14
+
15
+ def write!(lines)
16
+ log.debug "Writing #{lines.size} lines to #{path}"
17
+ file = File.new(path)
18
+ flock_result = file.flock(File::LOCK_EX)
19
+ if flock_result != 0
20
+ raise "Unable to lock file #{path}"
21
+ end
22
+ FileUtils.cp(path, backup_path)
23
+ unless path.read == backup_path.read
24
+ raise "Error backup up #{path} to #{backup_path}"
25
+ end
26
+ begin
27
+ path.open('w+') do |output|
28
+ lines.each do |line|
29
+ output.write(line)
30
+ end
31
+ end
32
+ rescue Exception => error
33
+ FileUtils.cp(backup_path, path)
34
+ raise
35
+ end
36
+ log.info "Changes saved to #{path}."
37
+ log.info "Previous state saved as #{backup_path}."
38
+ ensure
39
+ file.flock(File::LOCK_UN)
40
+ end
41
+ end
@@ -9,15 +9,20 @@ module Germinate::TextTransforms
9
9
  case line
10
10
  when /\S/
11
11
  result.last << line.strip
12
- else
12
+ when String
13
13
  result << [line]
14
14
  result << []
15
+ else
16
+ result << line
15
17
  end
16
18
  result
17
19
  }
18
20
  paragraphs.delete_if{|p| p.empty?}
19
21
  hunk.dup.replace(paragraphs.map {|paragraph|
20
- paragraph.join(" ")
22
+ case paragraph
23
+ when Germinate::Hunk then paragraph
24
+ else paragraph.join(" ")
25
+ end
21
26
  })
22
27
  }
23
28
  end
@@ -25,8 +30,8 @@ module Germinate::TextTransforms
25
30
  def self.strip_blanks
26
31
  lambda { |hunk|
27
32
  result = hunk.dup
28
- result.shift until result.first =~ /\S/ || result.empty?
29
- result.pop until result.last =~ /\S/ || result.empty?
33
+ result.shift while result.first =~ /^\s*$/ && !result.empty?
34
+ result.pop while result.last =~ /^\s*$/ && !result.empty?
30
35
  result
31
36
  }
32
37
  end
@@ -34,7 +39,7 @@ module Germinate::TextTransforms
34
39
  def self.erase_comments(comment_prefix="")
35
40
  lambda { |hunk|
36
41
  hunk.dup.map! do |line|
37
- if comment_prefix
42
+ if comment_prefix && String === line
38
43
  if match_data = /^\s*(#{comment_prefix})+\s*/.match(line)
39
44
  offset = match_data.begin(0)
40
45
  length = match_data[0].length
@@ -51,11 +56,12 @@ module Germinate::TextTransforms
51
56
  }
52
57
  end
53
58
 
54
- def self.uncomment(comment_prefix="")
59
+ def self.uncomment(comment_prefix=nil)
55
60
  lambda { |hunk|
61
+ comment_prefix ||= hunk.comment_prefix
56
62
  hunk.dup.map! do |line|
57
- if comment_prefix
58
- line.sub(/^#{comment_prefix}/,"")
63
+ if comment_prefix && line.respond_to?(:sub)
64
+ line.sub(/^#{Regexp.escape(comment_prefix.rstrip)}\s*/,"")
59
65
  else
60
66
  line
61
67
  end
@@ -65,7 +71,7 @@ module Germinate::TextTransforms
65
71
 
66
72
  def self.rstrip_lines
67
73
  lambda { |hunk|
68
- hunk.dup.map!{|line| line.to_s.rstrip}
74
+ hunk.dup.map!{|line| String === line ? line.to_s.rstrip : line}
69
75
  }
70
76
  end
71
77
 
@@ -87,4 +93,27 @@ module Germinate::TextTransforms
87
93
  pipeline.call(hunk)
88
94
  end
89
95
  end
96
+
97
+ def self.expand_insertions
98
+ lambda do |hunk|
99
+ hunk.resolve_insertions
100
+ end
101
+ end
102
+
103
+ def self.flatten_nested
104
+ lambda do |hunk|
105
+ result = hunk.flatten
106
+ result.copy_shared_style_attributes_from(hunk)
107
+ result
108
+ end
109
+ end
110
+
111
+ eigenclss = class << self; self; end
112
+
113
+ eigenclss.instance_eval do
114
+ private
115
+ def log
116
+ @log ||= Germinate.logger
117
+ end
118
+ end
90
119
  end
@@ -0,0 +1,25 @@
1
+ require 'fattr'
2
+ require File.expand_path("shared_style_attributes", File.dirname(__FILE__))
3
+
4
+ class Germinate::TransformProcess
5
+ fattr(:log) { Germinate.logger }
6
+
7
+ # We can't just use TextTransforms.singleton_methods because order is important
8
+ def ordered_transforms
9
+ %w[expand_insertions strip_blanks erase_comments
10
+ uncomment join_lines rstrip_lines
11
+ pipeline bracket flatten_nested]
12
+ end
13
+
14
+ def call(hunk)
15
+ ordered_transforms.inject(hunk) { |input, transform|
16
+ if hunk.send("#{transform}?")
17
+ log.debug "Performing text transform #{transform} on #{hunk}"
18
+ Germinate::TextTransforms.send(transform).call(input)
19
+ else
20
+ log.debug "Skipping text transform #{transform} on #{hunk} lines"
21
+ input
22
+ end
23
+ }
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ class Germinate::Variable < String
2
+ attr_reader :name
3
+ attr_reader :origin
4
+ attr_reader :line
5
+
6
+ def initialize(name, value, line, source_path, line_number)
7
+ super(value.to_s)
8
+ @name = name
9
+ @line = line
10
+ @origin = Germinate::Origin.new(source_path, line_number)
11
+ end
12
+
13
+ def update_source_line!(comment_prefix)
14
+ line.replace(make_control_line(comment_prefix))
15
+ end
16
+
17
+ private
18
+
19
+ def make_control_line(comment_prefix)
20
+ "#{comment_prefix}:SET: '#{name}', '#{self}'\n"
21
+ end
22
+
23
+ end
@@ -0,0 +1,14 @@
1
+ # :BRACKET_CODE: "<pre>", "</pre>"
2
+
3
+ # :SAMPLE: sample1
4
+ code sample 1
5
+
6
+ # :SAMPLE: sample2
7
+ code sample 2
8
+
9
+ # :TEXT:
10
+ # Here is example 2:
11
+ # :INSERT: @sample2
12
+ #
13
+ # And here is example 1:
14
+ # :INSERT: @sample1
@@ -5,10 +5,27 @@ module Germinate
5
5
 
6
6
  describe Application do
7
7
  before :each do
8
- @it = Application.new
8
+ @it = Application.new(nil, nil)
9
9
  end
10
10
 
11
+ context "loading plugins" do
12
+ before :each do
13
+ @files = ['a', 'b']
14
+ Gem.stub!(:find_files).and_return(@files)
15
+ Kernel.stub!(:load)
16
+ end
11
17
 
18
+ it "should look for files called germinate_plugin_v0_init.rb" do
19
+ Gem.should_receive(:find_files).with('germinate_plugin_v0_init')
20
+ @it.load_plugins!
21
+ end
22
+
23
+ it "should load the found files" do
24
+ Kernel.should_receive(:load).with('a')
25
+ Kernel.should_receive(:load).with('b')
26
+ @it.load_plugins!
27
+ end
28
+ end
12
29
  end
13
30
 
14
31
  end
@@ -24,8 +24,8 @@ module Germinate
24
24
 
25
25
  it "should yield text and code in order" do
26
26
  collect_hunks.should == [
27
- ["this is the text", "text line 2"],
28
- ["this is the code", "code line 2"]
27
+ ["this is the text\n", "text line 2\n"],
28
+ ["this is the code\n", "code line 2\n"]
29
29
  ]
30
30
  end
31
31
 
@@ -53,7 +53,7 @@ module Germinate
53
53
 
54
54
  it "should yield just the text" do
55
55
  collect_hunks.should == [
56
- ["this is the text", "text line 2"]
56
+ ["this is the text\n", "text line 2\n"]
57
57
  ]
58
58
  end
59
59
  end
@@ -7,6 +7,34 @@ module Germinate
7
7
  @it = CodeHunk.new(["foo", "bar"])
8
8
  end
9
9
 
10
+ it "should disable line joining" do
11
+ @it.should_not be_join_lines
12
+ end
13
+
14
+ it "should enable blank stripping" do
15
+ @it.should be_strip_blanks
16
+ end
17
+
18
+ it "should disable comment erasure" do
19
+ @it.should_not be_erase_comments
20
+ end
21
+
22
+ it "should disable uncommenting" do
23
+ @it.should_not be_uncomment
24
+ end
25
+
26
+ it "should disable stripping right-side whitespace" do
27
+ @it.should_not be_rstrip_lines
28
+ end
29
+
30
+ it "should enable bracketing" do
31
+ @it.should be_bracket
32
+ end
33
+
34
+ it "should enable pipeline processing" do
35
+ @it.should be_pipeline
36
+ end
37
+
10
38
  context "when visited by a formatter" do
11
39
  before :each do
12
40
  @formatter = stub("Formatter")