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.
- 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")
|