pith 0.2.0 → 0.2.1
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/bin/pith +6 -5
- data/features/error_handling.feature +5 -4
- data/features/step_definitions/build_steps.rb +4 -0
- data/lib/pith/input.rb +215 -11
- data/lib/pith/plugins/publication/input.rb +2 -14
- data/lib/pith/project.rb +26 -19
- data/lib/pith/version.rb +1 -1
- data/spec/pith/{input/template_spec.rb~ → input_spec.rb} +15 -28
- data/spec/pith/project_spec.rb +14 -29
- metadata +28 -41
- data/lib/pith/exception_ext.rb +0 -9
- data/lib/pith/input/abstract.rb +0 -78
- data/lib/pith/input/abstract.rb~ +0 -74
- data/lib/pith/input/base.rb~ +0 -74
- data/lib/pith/input/resource.rb +0 -39
- data/lib/pith/input/resource.rb~ +0 -41
- data/lib/pith/input/template.rb +0 -159
- data/lib/pith/input/template.rb~ +0 -126
- data/lib/pith/input/verbatim.rb~ +0 -31
- data/spec/pith/input/abstract_spec.rb~ +0 -31
- data/spec/pith/input/template_spec.rb +0 -70
data/bin/pith
CHANGED
@@ -16,11 +16,11 @@ class PithCommand < Clamp::Command
|
|
16
16
|
option ["-i", "--input"], "INPUT_DIR", "Input directory", :attribute_name => :input_dir, :default => "." do |dir|
|
17
17
|
Pathname(dir)
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
option ["-o", "--output"], "OUTPUT_DIR", "Output directory", :attribute_name => :output_dir, :default => "INPUT_DIR/_out" do |dir|
|
21
21
|
Pathname(dir)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
option ["-n", "--interval"], "INTERVAL", "Rebuild interval", :default => 2 do |n|
|
25
25
|
Integer(n)
|
26
26
|
end
|
@@ -45,7 +45,7 @@ class PithCommand < Clamp::Command
|
|
45
45
|
watch
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
subcommand "serve", "Serve the generated website" do
|
50
50
|
def execute
|
51
51
|
build
|
@@ -63,7 +63,7 @@ class PithCommand < Clamp::Command
|
|
63
63
|
def default_output_dir
|
64
64
|
input_dir + "_out"
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def project
|
68
68
|
unless @project
|
69
69
|
pith_dir = input_dir + "_pith"
|
@@ -79,6 +79,7 @@ class PithCommand < Clamp::Command
|
|
79
79
|
|
80
80
|
def build
|
81
81
|
project.build
|
82
|
+
exit(1) if project.has_errors?
|
82
83
|
end
|
83
84
|
|
84
85
|
def watch
|
@@ -92,7 +93,7 @@ class PithCommand < Clamp::Command
|
|
92
93
|
puts %{>>> Now taking the Pith at "http://localhost:#{port}"}
|
93
94
|
Rack::Handler.get("thin").run(server, :Port => port)
|
94
95
|
end
|
95
|
-
|
96
|
+
|
96
97
|
end
|
97
98
|
|
98
99
|
PithCommand.run
|
@@ -1,16 +1,17 @@
|
|
1
1
|
Feature: error handling
|
2
2
|
|
3
3
|
I want to be told when something goes wrong
|
4
|
-
|
4
|
+
|
5
5
|
Scenario: bad haml
|
6
6
|
|
7
7
|
Given input file "index.html.haml" contains
|
8
8
|
"""
|
9
9
|
%h2{class="this is not valid ruby"} Heading
|
10
|
-
|
10
|
+
|
11
11
|
%p Content
|
12
12
|
"""
|
13
|
-
|
13
|
+
|
14
14
|
When I build the site
|
15
|
-
|
15
|
+
|
16
16
|
Then output file "index.html" should contain /syntax error/
|
17
|
+
And the project should have errors
|
data/lib/pith/input.rb
CHANGED
@@ -1,22 +1,226 @@
|
|
1
|
-
require "
|
2
|
-
require "
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
require "pith/render_context"
|
4
|
+
require "tilt"
|
5
|
+
require "yaml"
|
3
6
|
|
4
7
|
module Pith
|
5
|
-
|
8
|
+
class Input
|
6
9
|
|
7
|
-
|
10
|
+
def initialize(project, path)
|
11
|
+
@project = project
|
12
|
+
@path = path
|
13
|
+
@meta = {}
|
14
|
+
determine_pipeline
|
15
|
+
load unless resource?
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :project, :path
|
19
|
+
|
20
|
+
attr_reader :output_path
|
21
|
+
attr_reader :dependencies
|
22
|
+
attr_reader :pipeline
|
23
|
+
attr_reader :error
|
24
|
+
|
25
|
+
# Public: Get the file-system location of this input.
|
26
|
+
#
|
27
|
+
# Returns a fully-qualified Pathname.
|
28
|
+
#
|
29
|
+
def file
|
30
|
+
project.input_dir + path
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Get the file-system location of the corresponding output file.
|
34
|
+
#
|
35
|
+
# Returns a fully-qualified Pathname.
|
36
|
+
#
|
37
|
+
def output_file
|
38
|
+
project.output_dir + output_path
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Generate an output file.
|
42
|
+
#
|
43
|
+
def build
|
44
|
+
return false if ignorable? || uptodate?
|
45
|
+
logger.info("--> #{output_path}")
|
46
|
+
generate_output
|
47
|
+
end
|
48
|
+
|
49
|
+
# Consider whether this input can be ignored.
|
50
|
+
#
|
51
|
+
# Returns true if it can.
|
52
|
+
#
|
53
|
+
def ignorable?
|
54
|
+
path.each_filename do |path_component|
|
55
|
+
project.ignore_patterns.each do |pattern|
|
56
|
+
return true if File.fnmatch(pattern, path_component)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
8
60
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
61
|
+
# Check whether output is up-to-date.
|
62
|
+
#
|
63
|
+
# Return true unless output needs to be re-generated.
|
64
|
+
#
|
65
|
+
def uptodate?
|
66
|
+
dependencies && FileUtils.uptodate?(output_file, dependencies)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Generate output for this template
|
70
|
+
#
|
71
|
+
def generate_output
|
72
|
+
output_file.parent.mkpath
|
73
|
+
return FileUtils.copy(file, output_file) if resource?
|
74
|
+
render_context = RenderContext.new(project)
|
75
|
+
output_file.open("w") do |out|
|
76
|
+
begin
|
77
|
+
@error = nil
|
78
|
+
out.puts(render_context.render(self))
|
79
|
+
rescue StandardError, SyntaxError => e
|
80
|
+
@error = e
|
81
|
+
logger.warn exception_summary(e, :max_backtrace => 5)
|
82
|
+
out.puts "<pre>"
|
83
|
+
out.puts exception_summary(e)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@dependencies = render_context.dependencies
|
87
|
+
end
|
88
|
+
|
89
|
+
# Render this input using Tilt
|
90
|
+
#
|
91
|
+
def render(context, locals = {}, &block)
|
92
|
+
return file.read if resource?
|
93
|
+
@pipeline.inject(@template_text) do |text, processor|
|
94
|
+
template = processor.new(file.to_s, @template_start_line) { text }
|
95
|
+
template.render(context, locals, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Public: Get YAML metadata declared in the header of of a template.
|
100
|
+
#
|
101
|
+
# If the first line of the template starts with "---" it is considered to be
|
102
|
+
# the start of a YAML 'document', which is loaded and returned.
|
103
|
+
#
|
104
|
+
# Examples
|
105
|
+
#
|
106
|
+
# Given input starting with:
|
107
|
+
#
|
108
|
+
# ---
|
109
|
+
# published: 2008-09-15
|
110
|
+
# ...
|
111
|
+
# OTHER STUFF
|
112
|
+
#
|
113
|
+
# input.meta
|
114
|
+
# #=> { "published" => "2008-09-15" }
|
115
|
+
#
|
116
|
+
# Returns a Hash.
|
117
|
+
#
|
118
|
+
def meta
|
119
|
+
@meta
|
120
|
+
end
|
121
|
+
|
122
|
+
# Public: Get page title.
|
123
|
+
#
|
124
|
+
# The default title is based on the input file-name, sans-extension, capitalised,
|
125
|
+
# but can be overridden by providing a "title" in the metadata block.
|
126
|
+
#
|
127
|
+
# Examples
|
128
|
+
#
|
129
|
+
# input.path.to_s
|
130
|
+
# #=> "some_page.html.haml"
|
131
|
+
# input.title
|
132
|
+
# #=> "Some page"
|
133
|
+
#
|
134
|
+
def title
|
135
|
+
meta["title"] || default_title
|
136
|
+
end
|
137
|
+
|
138
|
+
# Public: Resolve a reference relative to this input.
|
139
|
+
#
|
140
|
+
# ref - a String referencing another asset
|
141
|
+
#
|
142
|
+
# A ref starting with "/" is resolved relative to the project root;
|
143
|
+
# anything else is resolved relative to this input.
|
144
|
+
#
|
145
|
+
# Returns a fully-qualified Pathname of the asset.
|
146
|
+
#
|
147
|
+
def resolve_path(ref)
|
148
|
+
ref = ref.to_s
|
149
|
+
if ref[0,1] == "/"
|
150
|
+
Pathname(ref[1..-1])
|
151
|
+
else
|
152
|
+
path.parent + ref
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def default_title
|
159
|
+
path.basename.to_s.sub(/\..*/, '').tr('_-', ' ').capitalize
|
160
|
+
end
|
161
|
+
|
162
|
+
def determine_pipeline
|
163
|
+
@pipeline = []
|
164
|
+
remaining_path = path.to_s
|
165
|
+
while remaining_path =~ /^(.+)\.(.+)$/
|
166
|
+
if Tilt[$2]
|
167
|
+
remaining_path = $1
|
168
|
+
@pipeline << Tilt[$2]
|
14
169
|
else
|
15
|
-
|
170
|
+
break
|
171
|
+
end
|
172
|
+
end
|
173
|
+
@output_path = Pathname(remaining_path)
|
174
|
+
if resource?
|
175
|
+
@dependencies = [file]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def resource?
|
180
|
+
pipeline.empty?
|
181
|
+
end
|
182
|
+
|
183
|
+
# Read input file, extracting YAML meta-data header, and template content.
|
184
|
+
#
|
185
|
+
def load
|
186
|
+
file.open do |input|
|
187
|
+
load_meta(input)
|
188
|
+
load_template(input)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def load_meta(input)
|
193
|
+
header = input.gets
|
194
|
+
if header =~ /^---/
|
195
|
+
while line = input.gets
|
196
|
+
break if line =~ /^(---|\.\.\.)/
|
197
|
+
header << line
|
198
|
+
end
|
199
|
+
begin
|
200
|
+
@meta = YAML.load(header)
|
201
|
+
rescue ArgumentError, SyntaxError
|
202
|
+
logger.warn "#{file}:1: badly-formed YAML header"
|
16
203
|
end
|
204
|
+
else
|
205
|
+
input.rewind
|
17
206
|
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def load_template(input)
|
210
|
+
@template_start_line = input.lineno + 1
|
211
|
+
@template_text = input.read
|
212
|
+
end
|
18
213
|
|
214
|
+
def exception_summary(e, options = {})
|
215
|
+
max_backtrace = options[:max_backtrace] || 999
|
216
|
+
trimmed_backtrace = e.backtrace[0, max_backtrace]
|
217
|
+
(["#{e.class}: #{e.message}"] + trimmed_backtrace).join("\n ") + "\n"
|
218
|
+
end
|
219
|
+
|
220
|
+
def logger
|
221
|
+
project.logger
|
19
222
|
end
|
20
223
|
|
21
224
|
end
|
22
|
-
|
225
|
+
|
226
|
+
end
|
@@ -32,19 +32,7 @@ module Pith
|
|
32
32
|
end
|
33
33
|
|
34
34
|
module Pith
|
35
|
-
|
36
|
-
|
37
|
-
class Abstract
|
38
|
-
|
39
|
-
def published?
|
40
|
-
false
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
class Template
|
46
|
-
include Pith::Plugins::Publication::TemplateMethods
|
47
|
-
end
|
48
|
-
|
35
|
+
class Input
|
36
|
+
include Pith::Plugins::Publication::TemplateMethods
|
49
37
|
end
|
50
38
|
end
|
data/lib/pith/project.rb
CHANGED
@@ -5,21 +5,21 @@ require "pith/reference_error"
|
|
5
5
|
require "tilt"
|
6
6
|
|
7
7
|
module Pith
|
8
|
-
|
8
|
+
|
9
9
|
class Project
|
10
|
-
|
10
|
+
|
11
11
|
DEFAULT_IGNORE_PATTERNS = ["_*", ".git", ".svn"].freeze
|
12
|
-
|
12
|
+
|
13
13
|
def initialize(attributes = {})
|
14
14
|
@ignore_patterns = DEFAULT_IGNORE_PATTERNS.dup
|
15
15
|
attributes.each do |k,v|
|
16
16
|
send("#{k}=", v)
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
attr_reader :input_dir
|
21
21
|
attr_reader :ignore_patterns
|
22
|
-
|
22
|
+
|
23
23
|
def input_dir=(dir)
|
24
24
|
@input_dir = Pathname(dir)
|
25
25
|
end
|
@@ -32,12 +32,12 @@ module Pith
|
|
32
32
|
|
33
33
|
attr_accessor :assume_content_negotiation
|
34
34
|
attr_accessor :assume_directory_index
|
35
|
-
|
35
|
+
|
36
36
|
# Public: get inputs
|
37
37
|
#
|
38
38
|
# Returns Pith::Input objects representing the files in the input_dir.
|
39
39
|
#
|
40
|
-
# The list of inputs is cached after first load;
|
40
|
+
# The list of inputs is cached after first load;
|
41
41
|
# call #refresh to discard the cached data.
|
42
42
|
#
|
43
43
|
def inputs
|
@@ -71,7 +71,7 @@ module Pith
|
|
71
71
|
generate_outputs
|
72
72
|
output_dir.touch
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
# Public: discard cached data that is out-of-sync with the file-system.
|
76
76
|
#
|
77
77
|
def refresh
|
@@ -79,32 +79,39 @@ module Pith
|
|
79
79
|
@config_files = nil
|
80
80
|
end
|
81
81
|
|
82
|
+
# Public: check for errors.
|
83
|
+
#
|
84
|
+
# Returns true if any errors were encountered during the last build.
|
85
|
+
def has_errors?
|
86
|
+
@inputs.any?(&:error)
|
87
|
+
end
|
88
|
+
|
82
89
|
def last_built_at
|
83
90
|
output_dir.mtime
|
84
91
|
end
|
85
|
-
|
92
|
+
|
86
93
|
def logger
|
87
94
|
@logger ||= Logger.new(nil)
|
88
95
|
end
|
89
|
-
|
96
|
+
|
90
97
|
attr_writer :logger
|
91
98
|
|
92
99
|
def helpers(&block)
|
93
100
|
helper_module.module_eval(&block)
|
94
101
|
end
|
95
|
-
|
102
|
+
|
96
103
|
def helper_module
|
97
104
|
@helper_module ||= Module.new
|
98
105
|
end
|
99
106
|
|
100
107
|
def config_files
|
101
|
-
@config_files ||= begin
|
108
|
+
@config_files ||= begin
|
102
109
|
input_dir.all_files("_pith/**")
|
103
110
|
end.to_set
|
104
111
|
end
|
105
|
-
|
112
|
+
|
106
113
|
private
|
107
|
-
|
114
|
+
|
108
115
|
def load_config
|
109
116
|
config_file = input_dir + "_pith/config.rb"
|
110
117
|
project = self
|
@@ -112,7 +119,7 @@ module Pith
|
|
112
119
|
eval(config_file.read, binding, config_file.to_s, 1)
|
113
120
|
end
|
114
121
|
end
|
115
|
-
|
122
|
+
|
116
123
|
def remove_old_outputs
|
117
124
|
valid_output_paths = inputs.map { |i| i.output_path }
|
118
125
|
output_dir.all_files.each do |output_file|
|
@@ -123,9 +130,9 @@ module Pith
|
|
123
130
|
end
|
124
131
|
end
|
125
132
|
end
|
126
|
-
|
133
|
+
|
127
134
|
def generate_outputs
|
128
|
-
inputs.each do |input|
|
135
|
+
inputs.each do |input|
|
129
136
|
input.build
|
130
137
|
end
|
131
138
|
end
|
@@ -141,7 +148,7 @@ module Pith
|
|
141
148
|
cache_key = [path, file.mtime]
|
142
149
|
input_cache[cache_key]
|
143
150
|
end
|
144
|
-
|
151
|
+
|
145
152
|
end
|
146
|
-
|
153
|
+
|
147
154
|
end
|