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.
- data/History.txt +12 -0
- data/README.rdoc +26 -4
- data/TODO +80 -8
- data/bin/germ +162 -38
- data/examples/basic.rb +14 -9
- data/examples/short.rb +2 -0
- data/features/author-formats-article.feature +3 -3
- data/features/{author-lists-info.feature → author-lists-info.pending_feature} +3 -0
- data/features/author-publishes-article.feature +52 -0
- data/features/author-selects-hunks.feature +1 -1
- data/features/author-sets-variables.feature +88 -0
- data/features/{author-views-stuff.feature → author-views-stuff.pending_feature} +4 -0
- data/features/example_articles/escaping.txt +1 -0
- data/features/example_articles/specials.rb +3 -3
- data/features/example_output/specials.txt +9 -5
- data/features/step_definitions/germinate.rb +9 -0
- data/germinate.gemspec +3 -3
- data/lib/germinate.rb +1 -1
- data/lib/germinate/application.rb +82 -31
- data/lib/germinate/hunk.rb +20 -0
- data/lib/germinate/insertion.rb +10 -2
- data/lib/germinate/librarian.rb +129 -31
- data/lib/germinate/origin.rb +5 -0
- data/lib/germinate/pipeline.rb +2 -0
- data/lib/germinate/publisher.rb +57 -0
- data/lib/germinate/reader.rb +51 -8
- data/lib/germinate/selector.rb +18 -6
- data/lib/germinate/shared_style_attributes.rb +18 -1
- data/lib/germinate/{process.rb → shell_process.rb} +27 -8
- data/lib/germinate/shell_publisher.rb +19 -0
- data/lib/germinate/simple_publisher.rb +7 -0
- data/lib/germinate/source_file.rb +41 -0
- data/lib/germinate/text_transforms.rb +38 -9
- data/lib/germinate/transform_process.rb +25 -0
- data/lib/germinate/variable.rb +23 -0
- data/sample.rb +14 -0
- data/spec/germinate/application_spec.rb +18 -1
- data/spec/germinate/article_editor_spec.rb +3 -3
- data/spec/germinate/code_hunk_spec.rb +28 -0
- data/spec/germinate/file_hunk_spec.rb +1 -0
- data/spec/germinate/hunk_spec.rb +1 -0
- data/spec/germinate/insertion_spec.rb +2 -1
- data/spec/germinate/librarian_spec.rb +280 -85
- data/spec/germinate/pipeline_spec.rb +10 -0
- data/spec/germinate/process_spec.rb +31 -6
- data/spec/germinate/publisher_spec.rb +130 -0
- data/spec/germinate/reader_spec.rb +58 -2
- data/spec/germinate/selector_spec.rb +34 -14
- data/spec/germinate/shell_publisher_spec.rb +61 -0
- data/spec/germinate/source_file_spec.rb +99 -0
- data/spec/germinate/text_hunk_spec.rb +45 -0
- data/spec/germinate/text_transforms_spec.rb +90 -2
- data/spec/germinate/transform_process_spec.rb +50 -0
- data/spec/germinate/variable_spec.rb +14 -0
- metadata +19 -7
- data/lib/germinate/article_formatter.rb +0 -75
- data/spec/germinate/article_formatter_spec.rb +0 -153
data/lib/germinate/selector.rb
CHANGED
@@ -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+)?(:([
|
13
|
-
EXCERPT_OUTPUT_PATTERN = /^([@$])?(\w+)?(\|([\w|]+))?(:([
|
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 "@"
|
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]).
|
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
|
6
|
+
# A ShellProcess represents an external command which can be used to process a Hunk
|
7
7
|
# of text.
|
8
|
-
class Germinate::
|
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
|
16
|
-
@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
|
69
|
-
|
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,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
|
-
|
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
|
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
|
29
|
-
result.pop
|
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
|
data/sample.rb
ADDED
@@ -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")
|