devver-germinate 1.1.0 → 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 (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")