devver-germinate 1.0.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/.gitignore +2 -0
- data/History.txt +4 -0
- data/README.rdoc +132 -0
- data/Rakefile +43 -0
- data/bin/germ +133 -0
- data/cucumber.yml +2 -0
- data/examples/basic.rb +118 -0
- data/examples/short.rb +17 -0
- data/features/author-formats-article.feature +108 -0
- data/features/author-lists-info.feature +45 -0
- data/features/author-publishes-article-source.feature +5 -0
- data/features/author-publishes-article.feature +5 -0
- data/features/author-republishes-article.feature +5 -0
- data/features/author-selects-hunks.feature +26 -0
- data/features/author-updates-article-source.feature +5 -0
- data/features/author-views-stuff.feature +48 -0
- data/features/bin/quoter +6 -0
- data/features/bin/sorter +4 -0
- data/features/example_articles/code_samples.rb +89 -0
- data/features/example_articles/escaping.txt +12 -0
- data/features/example_articles/hello.rb +9 -0
- data/features/example_articles/pipelines.txt +25 -0
- data/features/example_articles/regexen.rb +24 -0
- data/features/example_articles/sample_offsets.rb +18 -0
- data/features/example_articles/specials.rb +19 -0
- data/features/example_articles/wrapping.rb +8 -0
- data/features/example_output/code_samples.txt +186 -0
- data/features/example_output/escaping.out +5 -0
- data/features/example_output/hello.txt +1 -0
- data/features/example_output/pipelines.out +28 -0
- data/features/example_output/regexen.txt +22 -0
- data/features/example_output/sample_offsets.txt +15 -0
- data/features/example_output/specials.txt +36 -0
- data/features/example_output/wrapping.txt +3 -0
- data/features/step_definitions/germinate.rb +30 -0
- data/features/support/env.rb +18 -0
- data/germinate.gemspec +55 -0
- data/lib/germinate.rb +54 -0
- data/lib/germinate/application.rb +62 -0
- data/lib/germinate/article_editor.rb +20 -0
- data/lib/germinate/article_formatter.rb +75 -0
- data/lib/germinate/formatter.rb +119 -0
- data/lib/germinate/hunk.rb +149 -0
- data/lib/germinate/implicit_insertion.rb +9 -0
- data/lib/germinate/insertion.rb +15 -0
- data/lib/germinate/librarian.rb +179 -0
- data/lib/germinate/pipeline.rb +11 -0
- data/lib/germinate/process.rb +67 -0
- data/lib/germinate/reader.rb +212 -0
- data/lib/germinate/selector.rb +95 -0
- data/lib/germinate/shared_style_attributes.rb +23 -0
- data/lib/germinate/text_transforms.rb +90 -0
- data/spec/germinate/application_spec.rb +14 -0
- data/spec/germinate/article_editor_spec.rb +97 -0
- data/spec/germinate/article_formatter_spec.rb +153 -0
- data/spec/germinate/code_hunk_spec.rb +45 -0
- data/spec/germinate/formatter_spec.rb +160 -0
- data/spec/germinate/hunk_spec.rb +77 -0
- data/spec/germinate/implicit_insertion_spec.rb +33 -0
- data/spec/germinate/insertion_spec.rb +18 -0
- data/spec/germinate/librarian_spec.rb +336 -0
- data/spec/germinate/pipeline_spec.rb +24 -0
- data/spec/germinate/process_spec.rb +64 -0
- data/spec/germinate/reader_spec.rb +306 -0
- data/spec/germinate/selector_spec.rb +65 -0
- data/spec/germinate/text_hunk_spec.rb +53 -0
- data/spec/germinate/text_transforms_spec.rb +154 -0
- data/spec/germinate_spec.rb +8 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/cucumber.rake +5 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test_germinate.rb +0 -0
- metadata +209 -0
data/lib/germinate.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fattr'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Germinate
|
6
|
+
|
7
|
+
# :stopdoc:
|
8
|
+
VERSION = '1.0.0'
|
9
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
10
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
11
|
+
# :startdoc:
|
12
|
+
|
13
|
+
# Returns the version string for the library.
|
14
|
+
#
|
15
|
+
def self.version
|
16
|
+
VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the library path for the module. If any arguments are given,
|
20
|
+
# they will be joined to the end of the libray path using
|
21
|
+
# <tt>File.join</tt>.
|
22
|
+
#
|
23
|
+
def self.libpath( *args )
|
24
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the lpath for the module. If any arguments are given,
|
28
|
+
# they will be joined to the end of the path using
|
29
|
+
# <tt>File.join</tt>.
|
30
|
+
#
|
31
|
+
def self.path( *args )
|
32
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Utility method used to require all files ending in .rb that lie in the
|
36
|
+
# directory below this file that has the same name as the filename passed
|
37
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
38
|
+
# the _filename_ does not have to be equivalent to the directory.
|
39
|
+
#
|
40
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
41
|
+
dir ||= ::File.basename(fname, '.*')
|
42
|
+
search_me = ::File.expand_path(
|
43
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
44
|
+
|
45
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
46
|
+
end
|
47
|
+
|
48
|
+
Fattr(:logger) { Logger.new($stderr) }
|
49
|
+
|
50
|
+
end # module Germinate
|
51
|
+
|
52
|
+
Germinate.require_all_libs_relative_to(__FILE__)
|
53
|
+
|
54
|
+
# EOF
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# The Application ties all the other componts together. It has public methods
|
2
|
+
# roughly corresponding commands that the 'germ' command-line tool supports.
|
3
|
+
class Germinate::Application
|
4
|
+
attr_writer :formatter
|
5
|
+
|
6
|
+
def format(source, output=$stdout, errors=$stderr)
|
7
|
+
librarian = load_librarian(source)
|
8
|
+
editor = Germinate::ArticleEditor.new(librarian)
|
9
|
+
formatter = Germinate::ArticleFormatter.new(output)
|
10
|
+
|
11
|
+
Germinate::SharedStyleAttributes.fattrs.each do
|
12
|
+
|style_attribute|
|
13
|
+
formatter.send(style_attribute, librarian.send(style_attribute))
|
14
|
+
end
|
15
|
+
formatter.start!
|
16
|
+
editor.each_hunk do |hunk|
|
17
|
+
formatter.format!(hunk)
|
18
|
+
end
|
19
|
+
formatter.finish!
|
20
|
+
end
|
21
|
+
|
22
|
+
def list(source, things_to_list, output=$stdout)
|
23
|
+
librarian = load_librarian(source)
|
24
|
+
if things_to_list.include?(:sections)
|
25
|
+
output.puts(librarian.section_names.join("\n"))
|
26
|
+
end
|
27
|
+
if things_to_list.include?(:samples)
|
28
|
+
output.puts(librarian.sample_names.join("\n"))
|
29
|
+
end
|
30
|
+
if things_to_list.include?(:processes)
|
31
|
+
output.puts(librarian.process_names.join("\n"))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def show(source, selection, output=$stdout)
|
36
|
+
librarian = load_librarian(source)
|
37
|
+
selection.fetch(:section, []).each do |section|
|
38
|
+
output.puts(*librarian.section(section))
|
39
|
+
end
|
40
|
+
selection.fetch(:sample, []).each do |sample|
|
41
|
+
output.puts(*librarian.sample(sample))
|
42
|
+
end
|
43
|
+
selection.fetch(:process, []).each do |process|
|
44
|
+
output.puts(*librarian.process(process).command)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def select(source, selector, output=$stdout)
|
49
|
+
librarian = load_librarian(source)
|
50
|
+
output.puts(*librarian[selector])
|
51
|
+
end
|
52
|
+
private
|
53
|
+
|
54
|
+
def load_librarian(source)
|
55
|
+
librarian = Germinate::Librarian.new
|
56
|
+
reader = Germinate::Reader.new(librarian)
|
57
|
+
source.each_line do |line|
|
58
|
+
reader << line
|
59
|
+
end
|
60
|
+
librarian
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# An Editor is responsible for selecting hunks of text from a Librarian and
|
2
|
+
# assembling them into a list for formatting.
|
3
|
+
class Germinate::ArticleEditor
|
4
|
+
def initialize(librarian)
|
5
|
+
@librarian = librarian
|
6
|
+
end
|
7
|
+
|
8
|
+
def each_hunk(&block)
|
9
|
+
librarian.section_names.each do |section_name|
|
10
|
+
yield librarian.section(section_name).resolve_insertions
|
11
|
+
if librarian.has_sample?(section_name)
|
12
|
+
yield librarian.sample(section_name).resolve_insertions
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :librarian
|
20
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'ick'
|
2
|
+
require 'fattr'
|
3
|
+
require File.expand_path("shared_style_attributes", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
# A Formatter is responsible for taking content hunks received from an Editor
|
6
|
+
# and formatting them for display or publishing.
|
7
|
+
class Germinate::ArticleFormatter
|
8
|
+
Ick::Returning.belongs_to self
|
9
|
+
include Germinate::SharedStyleAttributes
|
10
|
+
|
11
|
+
fattr :join_lines => true
|
12
|
+
fattr :strip_blanks => true
|
13
|
+
fattr :rstrip_newlines => true
|
14
|
+
fattr :uncomment => true
|
15
|
+
fattr :rstrip_lines => true
|
16
|
+
|
17
|
+
def initialize(output_stream=$stdout)
|
18
|
+
@output_stream = output_stream
|
19
|
+
@first_output = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def start!
|
23
|
+
end
|
24
|
+
|
25
|
+
def finish!
|
26
|
+
end
|
27
|
+
|
28
|
+
def format!(hunk)
|
29
|
+
@output_stream.puts unless first_output?
|
30
|
+
hunk.format_with(self)
|
31
|
+
@first_output = false if first_output?
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_text!(hunk, comment_prefix=nil)
|
35
|
+
text_transforms.inject(hunk) do |hunk, transform|
|
36
|
+
transform.call(hunk)
|
37
|
+
end.each do |line|
|
38
|
+
@output_stream.puts(line)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def format_code!(hunk, comment_prefix=nil)
|
43
|
+
code_transforms.inject(hunk) do |hunk, transform|
|
44
|
+
transform.call(hunk)
|
45
|
+
end.each do |line|
|
46
|
+
@output_stream.puts(line)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def first_output?
|
53
|
+
@first_output
|
54
|
+
end
|
55
|
+
|
56
|
+
def text_transforms
|
57
|
+
returning([]) do |transforms|
|
58
|
+
transforms << Germinate::TextTransforms.strip_blanks if strip_blanks?
|
59
|
+
if uncomment?
|
60
|
+
transforms << Germinate::TextTransforms.uncomment(comment_prefix)
|
61
|
+
end
|
62
|
+
transforms << Germinate::TextTransforms.join_lines if join_lines?
|
63
|
+
transforms << Germinate::TextTransforms.rstrip_lines if rstrip_lines?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def code_transforms
|
68
|
+
returning([]) do |transforms|
|
69
|
+
transforms << Germinate::TextTransforms.strip_blanks if strip_blanks?
|
70
|
+
transforms << Germinate::TextTransforms.rstrip_lines if rstrip_lines?
|
71
|
+
transforms <<
|
72
|
+
Germinate::TextTransforms.bracket
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'alter_ego'
|
2
|
+
|
3
|
+
# Obsolete
|
4
|
+
class Germinate::Formatter
|
5
|
+
include AlterEgo
|
6
|
+
|
7
|
+
attr_accessor :comment_prefix
|
8
|
+
|
9
|
+
def initialize(output=$stdio)
|
10
|
+
@output = output
|
11
|
+
end
|
12
|
+
|
13
|
+
state :initial, :default => true do
|
14
|
+
transition :to => :code, :on => :start!
|
15
|
+
end
|
16
|
+
|
17
|
+
state :code do
|
18
|
+
handle :add_line!, :add_code_line!
|
19
|
+
|
20
|
+
transition :to => :paragraph, :on => :paragraph!
|
21
|
+
transition :to => :finished, :on => :finish!
|
22
|
+
end
|
23
|
+
|
24
|
+
state :paragraph do
|
25
|
+
handle :add_line!, :add_paragraph_line!
|
26
|
+
|
27
|
+
transition :to => :linebreak, :on => :linebreak!
|
28
|
+
transition :to => :code, :on => :code!
|
29
|
+
transition :to => :finished, :on => :finish!
|
30
|
+
|
31
|
+
on_exit do
|
32
|
+
flush_paragraph!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
state :linebreak do
|
37
|
+
handle :add_line!, :add_linebreak_line!
|
38
|
+
|
39
|
+
transition :to => :paragraph, :on => :paragraph! do
|
40
|
+
emit!("\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
transition :to => :code, :on => :code!
|
44
|
+
transition :to => :finished, :on => :finish!
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
state :finished do
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def add_code_line!(line)
|
55
|
+
case line
|
56
|
+
when /\s*(\S+)?\s*:TEXT:/ then
|
57
|
+
self.comment_prefix = $1
|
58
|
+
paragraph!
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_paragraph_line!(line)
|
63
|
+
case line
|
64
|
+
when /:CUT:/
|
65
|
+
code!
|
66
|
+
when text_pattern
|
67
|
+
paragraph_buffer << $1.chomp
|
68
|
+
when whitespace_pattern
|
69
|
+
linebreak!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_linebreak_line!(line)
|
74
|
+
case line
|
75
|
+
when /:CUT:/
|
76
|
+
code!
|
77
|
+
when text_pattern
|
78
|
+
paragraph_buffer << $1.chomp
|
79
|
+
paragraph!
|
80
|
+
else
|
81
|
+
# NOOP
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
attr_reader :output
|
88
|
+
|
89
|
+
def text_pattern
|
90
|
+
if comment_prefix
|
91
|
+
/^\s*#{comment_prefix}+\s*(\S+.*)$/
|
92
|
+
else
|
93
|
+
/^\s*(\S+.*)\s*$/
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def whitespace_pattern
|
98
|
+
if comment_prefix
|
99
|
+
/^\s*#{comment_prefix}*\s*$/
|
100
|
+
else
|
101
|
+
/^\s*$/
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def paragraph_buffer
|
106
|
+
@paragraph_buffer ||= []
|
107
|
+
end
|
108
|
+
|
109
|
+
def flush_paragraph!
|
110
|
+
emit!(paragraph_buffer.join(" "))
|
111
|
+
paragraph_buffer.clear
|
112
|
+
end
|
113
|
+
|
114
|
+
def emit!(text)
|
115
|
+
unless text.empty?
|
116
|
+
output.puts(text.chomp)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'ick'
|
2
|
+
require File.expand_path("shared_style_attributes", File.dirname(__FILE__))
|
3
|
+
|
4
|
+
# A Hunk represents a chunk of content. There are different types of Hunk, like
|
5
|
+
# Code or Text, which may be formatted differently by the Formatter. At its
|
6
|
+
# most basic a Hunk is just a list of Strings, each one representing a single
|
7
|
+
# line.
|
8
|
+
class Germinate::Hunk < ::Array
|
9
|
+
include Germinate::SharedStyleAttributes
|
10
|
+
Ick::Returning.belongs_to(self)
|
11
|
+
|
12
|
+
def initialize(contents=[], template = {})
|
13
|
+
super(contents)
|
14
|
+
if Germinate::SharedStyleAttributes === template
|
15
|
+
copy_shared_style_attrubutes_from(template)
|
16
|
+
else
|
17
|
+
template.each_pair do |key, value|
|
18
|
+
send(key, value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# return a copy with leading and trailing whitespace lines removed
|
24
|
+
def strip
|
25
|
+
Germinate::TextTransforms.strip_blanks.call(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve_insertions
|
29
|
+
dup.map!{ |line_or_insertion|
|
30
|
+
if line_or_insertion.respond_to?(:resolve)
|
31
|
+
line_or_insertion.resolve
|
32
|
+
else
|
33
|
+
line_or_insertion
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_with(formatter)
|
39
|
+
raise "Unresolved hunk cannot be formatted!" unless resolved?
|
40
|
+
if nested_hunks?
|
41
|
+
group_hunks.each do |hunk|
|
42
|
+
hunk.format_with(formatter)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
yield formatter
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
attrs = Germinate::SharedStyleAttributes.fattrs.inject({}) {|attrs, key|
|
51
|
+
attrs[key] = send(key)
|
52
|
+
attrs
|
53
|
+
}
|
54
|
+
"#{self.class}:#{super}:#{attrs.inspect}:#{object_id}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def [](*args)
|
58
|
+
returning(super) do |slice|
|
59
|
+
if slice.kind_of?(Germinate::Hunk)
|
60
|
+
slice.copy_shared_style_attrubutes_from(self)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def slice(*args)
|
66
|
+
returning(super) do |slice|
|
67
|
+
if slice.kind_of?(Germinate::Hunk)
|
68
|
+
slice.copy_shared_style_attrubutes_from(self)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def index_matching(pattern, start_index=0)
|
74
|
+
(start_index...(size)).each { |i|
|
75
|
+
return i if pattern === self[i]
|
76
|
+
}
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def resolved?
|
83
|
+
!unresolved_hunks?
|
84
|
+
end
|
85
|
+
|
86
|
+
def unresolved_hunks?
|
87
|
+
any?{|line| line.respond_to?(:resolve)}
|
88
|
+
end
|
89
|
+
|
90
|
+
def nested?
|
91
|
+
unresolved_hunks? || nested_hunks?
|
92
|
+
end
|
93
|
+
|
94
|
+
def nested_hunks?
|
95
|
+
any?{|line| line.respond_to?(:format_with)}
|
96
|
+
end
|
97
|
+
|
98
|
+
def group_hunks
|
99
|
+
return self unless nested?
|
100
|
+
groups = inject([empty_dup]) { |hunks, line_or_hunk|
|
101
|
+
if line_or_hunk.respond_to?(:format_with)
|
102
|
+
hunks << line_or_hunk
|
103
|
+
hunks << empty_dup
|
104
|
+
else
|
105
|
+
hunks.last << line_or_hunk
|
106
|
+
end
|
107
|
+
hunks
|
108
|
+
}
|
109
|
+
groups.delete_if{|g| g.empty?}
|
110
|
+
groups
|
111
|
+
end
|
112
|
+
|
113
|
+
# An empty duplicate retains metadata but has no lines
|
114
|
+
def empty_dup
|
115
|
+
returning(dup) do |duplicate|
|
116
|
+
duplicate.clear
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class Germinate::TextHunk < Germinate::Hunk
|
123
|
+
def format_with(formatter)
|
124
|
+
super(formatter) do |formatter|
|
125
|
+
formatter.format_text!(self, comment_prefix)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Germinate::CodeHunk < Germinate::Hunk
|
131
|
+
def code_open_bracket=(new_value)
|
132
|
+
super
|
133
|
+
end
|
134
|
+
|
135
|
+
def code_close_bracket=(new_value)
|
136
|
+
super
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
def format_with(formatter)
|
141
|
+
super(formatter) do |formatter|
|
142
|
+
formatter.format_code!(self, comment_prefix)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Germinate::NullHunk < Germinate::Hunk
|
148
|
+
end
|
149
|
+
|