pith 0.2.3 → 0.3.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/bin/pith +5 -6
- data/features/relative_linking.feature +12 -12
- data/lib/pith/console_logger.rb +15 -8
- data/lib/pith/input.rb +83 -68
- data/lib/pith/output.rb +107 -0
- data/lib/pith/pathname_ext.rb +6 -1
- data/lib/pith/project.rb +73 -48
- data/lib/pith/render_context.rb +35 -32
- data/lib/pith/server.rb +30 -10
- data/lib/pith/version.rb +1 -1
- data/spec/pith/input_spec.rb +57 -20
- data/spec/pith/pathname_ext_spec.rb +29 -0
- data/spec/pith/project_spec.rb +58 -56
- data/spec/pith/server_spec.rb +101 -0
- data/spec/spec_helper.rb +1 -0
- metadata +34 -30
- data/features/reloading.feature +0 -48
- data/spec/pith/metadata_spec.rb~ +0 -46
- data/spec/pith/plugins/publication_spec.rb~ +0 -39
- data/spec/pith/project_spec.rb~ +0 -137
- data/spec/spec_helper.rb~ +0 -18
data/lib/pith/pathname_ext.rb
CHANGED
data/lib/pith/project.rb
CHANGED
@@ -2,13 +2,14 @@ require "logger"
|
|
2
2
|
require "pith/input"
|
3
3
|
require "pith/pathname_ext"
|
4
4
|
require "pith/reference_error"
|
5
|
+
require "set"
|
5
6
|
require "tilt"
|
6
7
|
|
7
8
|
module Pith
|
8
9
|
|
9
10
|
class Project
|
10
11
|
|
11
|
-
DEFAULT_IGNORE_PATTERNS = ["_*", ".git", ".svn", "*~"].freeze
|
12
|
+
DEFAULT_IGNORE_PATTERNS = ["_*", ".git", ".gitignore", ".svn", ".sass-cache", "*~", "*.sw[op]"].to_set.freeze
|
12
13
|
|
13
14
|
def initialize(attributes = {})
|
14
15
|
@ignore_patterns = DEFAULT_IGNORE_PATTERNS.dup
|
@@ -28,63 +29,89 @@ module Pith
|
|
28
29
|
|
29
30
|
def output_dir=(dir)
|
30
31
|
@output_dir = Pathname(dir)
|
32
|
+
FileUtils.rm_rf(@output_dir)
|
33
|
+
@output_dir.mkpath
|
31
34
|
end
|
32
35
|
|
33
36
|
attr_accessor :assume_content_negotiation
|
34
37
|
attr_accessor :assume_directory_index
|
35
38
|
|
39
|
+
def ignore(pattern)
|
40
|
+
ignore_patterns << pattern
|
41
|
+
end
|
42
|
+
|
36
43
|
# Public: get inputs
|
37
44
|
#
|
38
45
|
# Returns Pith::Input objects representing the files in the input_dir.
|
39
46
|
#
|
40
|
-
# The list of inputs is cached after first load;
|
41
|
-
# call #refresh to discard the cached data.
|
42
|
-
#
|
43
47
|
def inputs
|
44
|
-
@
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
@input_map.values
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: get outputs
|
52
|
+
#
|
53
|
+
# Returns Pith::Output objects representing the files in the output_dir.
|
54
|
+
#
|
55
|
+
def outputs
|
56
|
+
inputs.map(&:output).compact
|
48
57
|
end
|
49
58
|
|
50
59
|
# Public: find an input.
|
51
60
|
#
|
52
|
-
# path - an path relative to
|
61
|
+
# path - an path relative to input_dir
|
53
62
|
#
|
54
|
-
# Returns the first input whose
|
63
|
+
# Returns the first input whose path matches.
|
55
64
|
# Returns nil if no match is found.
|
56
65
|
#
|
57
66
|
def input(path)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
67
|
+
@input_map[Pathname(path)]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: find an output.
|
71
|
+
#
|
72
|
+
# path - an path relative to output_dir
|
73
|
+
#
|
74
|
+
# Returns the first output whose path matches.
|
75
|
+
# Returns nil if no match is found.
|
76
|
+
#
|
77
|
+
def output(path)
|
78
|
+
@output_map[Pathname(path)]
|
63
79
|
end
|
64
80
|
|
65
81
|
# Public: build the project, generating output files.
|
66
82
|
#
|
67
83
|
def build
|
68
|
-
|
69
|
-
load_config
|
70
|
-
remove_old_outputs
|
84
|
+
sync
|
71
85
|
output_dir.mkpath
|
72
|
-
|
86
|
+
outputs.each(&:build)
|
73
87
|
output_dir.touch
|
74
88
|
end
|
75
89
|
|
76
|
-
# Public:
|
90
|
+
# Public: re-sync with the file-system.
|
77
91
|
#
|
78
|
-
def
|
79
|
-
@
|
80
|
-
@
|
92
|
+
def sync
|
93
|
+
@input_map ||= {}
|
94
|
+
@output_map ||= {}
|
95
|
+
@config_inputs = nil
|
96
|
+
load_config
|
97
|
+
validate_known_inputs
|
98
|
+
find_new_inputs
|
99
|
+
end
|
100
|
+
|
101
|
+
def sync_every(period)
|
102
|
+
@next_sync ||= 0
|
103
|
+
now = Time.now.to_i
|
104
|
+
if now >= @next_sync
|
105
|
+
sync
|
106
|
+
@next_sync = now + period
|
107
|
+
end
|
81
108
|
end
|
82
109
|
|
83
110
|
# Public: check for errors.
|
84
111
|
#
|
85
112
|
# Returns true if any errors were encountered during the last build.
|
86
113
|
def has_errors?
|
87
|
-
|
114
|
+
inputs.map(&:output).compact.any?(&:error)
|
88
115
|
end
|
89
116
|
|
90
117
|
def last_built_at
|
@@ -105,10 +132,10 @@ module Pith
|
|
105
132
|
@helper_module ||= Module.new
|
106
133
|
end
|
107
134
|
|
108
|
-
def
|
109
|
-
@
|
110
|
-
|
111
|
-
end
|
135
|
+
def config_inputs
|
136
|
+
@config_inputs ||= inputs.select do |input|
|
137
|
+
input.path.to_s[0,6] == "_pith/"
|
138
|
+
end
|
112
139
|
end
|
113
140
|
|
114
141
|
private
|
@@ -121,35 +148,33 @@ module Pith
|
|
121
148
|
end
|
122
149
|
end
|
123
150
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
logger.info("removing #{output_path}")
|
130
|
-
FileUtils.rm(output_file)
|
131
|
-
end
|
151
|
+
def load_input(path)
|
152
|
+
i = Input.new(self, path)
|
153
|
+
@input_map[path] = i
|
154
|
+
if o = i.output
|
155
|
+
@output_map[o.path] = o
|
132
156
|
end
|
157
|
+
i
|
133
158
|
end
|
134
159
|
|
135
|
-
def
|
136
|
-
inputs.
|
137
|
-
|
160
|
+
def validate_known_inputs
|
161
|
+
invalid_inputs = inputs.select { |i| !i.sync }
|
162
|
+
invalid_inputs.each do |i|
|
163
|
+
@input_map.delete(i.path)
|
164
|
+
if o = i.output
|
165
|
+
@output_map.delete(o.path)
|
166
|
+
end
|
138
167
|
end
|
139
168
|
end
|
140
169
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
170
|
+
def find_new_inputs
|
171
|
+
input_dir.all_files.map do |input_file|
|
172
|
+
next if input_file.in?(output_dir)
|
173
|
+
path = input_file.relative_path_from(input_dir)
|
174
|
+
input(path) || load_input(path)
|
144
175
|
end
|
145
176
|
end
|
146
177
|
|
147
|
-
def find_or_create_input(path)
|
148
|
-
file = input_dir + path
|
149
|
-
cache_key = [path, file.mtime]
|
150
|
-
input_cache[cache_key]
|
151
|
-
end
|
152
|
-
|
153
178
|
end
|
154
179
|
|
155
180
|
end
|
data/lib/pith/render_context.rb
CHANGED
@@ -5,20 +5,23 @@ require "set"
|
|
5
5
|
require "tilt"
|
6
6
|
|
7
7
|
module Pith
|
8
|
-
|
8
|
+
|
9
9
|
class RenderContext
|
10
|
-
|
10
|
+
|
11
11
|
include Tilt::CompileSite
|
12
|
-
|
13
|
-
def initialize(
|
14
|
-
@
|
12
|
+
|
13
|
+
def initialize(output)
|
14
|
+
@output = output
|
15
|
+
@page = @output.input
|
16
|
+
@project = @page.project
|
15
17
|
@input_stack = []
|
16
|
-
@dependencies = project.config_files.dup
|
17
18
|
self.extend(project.helper_module)
|
18
19
|
end
|
19
20
|
|
21
|
+
attr_reader :output
|
22
|
+
attr_reader :page
|
20
23
|
attr_reader :project
|
21
|
-
|
24
|
+
|
22
25
|
def page
|
23
26
|
@input_stack.first
|
24
27
|
end
|
@@ -26,7 +29,7 @@ module Pith
|
|
26
29
|
def current_input
|
27
30
|
@input_stack.last
|
28
31
|
end
|
29
|
-
|
32
|
+
|
30
33
|
def render(input, locals = {}, &block)
|
31
34
|
with_input(input) do
|
32
35
|
result = input.render(self, locals, &block)
|
@@ -36,8 +39,6 @@ module Pith
|
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
|
-
attr_reader :dependencies
|
40
|
-
|
41
42
|
def include(template_ref, locals = {}, &block)
|
42
43
|
content_block = if block_given?
|
43
44
|
content = capture_haml(&block)
|
@@ -45,13 +46,13 @@ module Pith
|
|
45
46
|
end
|
46
47
|
render_ref(template_ref, locals, &content_block)
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
alias :inside :include
|
50
|
-
|
51
|
+
|
51
52
|
def content_for
|
52
53
|
@content_for_hash ||= Hash.new { "" }
|
53
54
|
end
|
54
|
-
|
55
|
+
|
55
56
|
def relative_url_to(target_path)
|
56
57
|
url = target_path.relative_path_from(page.path.parent).to_s
|
57
58
|
url = url.sub(/index\.html$/, "") if project.assume_directory_index
|
@@ -59,46 +60,48 @@ module Pith
|
|
59
60
|
url = "./" if url.empty?
|
60
61
|
Pathname(url)
|
61
62
|
end
|
62
|
-
|
63
|
+
|
63
64
|
def href(target_ref)
|
64
65
|
relative_url_to(resolve_reference(target_ref))
|
65
66
|
end
|
66
67
|
|
67
68
|
def link(target_ref, label = nil)
|
68
69
|
target_path = resolve_reference(target_ref)
|
69
|
-
label ||= begin
|
70
|
+
label ||= begin
|
70
71
|
target_input = input(target_path)
|
71
|
-
record_dependency_on(target_input
|
72
|
+
output.record_dependency_on(target_input)
|
72
73
|
target_input.title
|
73
74
|
rescue ReferenceError
|
74
|
-
"???"
|
75
|
+
"???"
|
75
76
|
end
|
76
77
|
url = relative_url_to(target_path)
|
77
78
|
%{<a href="#{url}">#{label}</a>}
|
78
79
|
end
|
79
80
|
|
80
|
-
def record_dependency_on(file)
|
81
|
-
@dependencies << file
|
82
|
-
end
|
83
|
-
|
84
81
|
private
|
85
|
-
|
82
|
+
|
86
83
|
def resolve_reference(ref)
|
87
|
-
if ref.
|
88
|
-
ref.
|
84
|
+
if ref.kind_of?(Pith::Input)
|
85
|
+
raise(ReferenceError, %{No output for "#{ref.path}"}) if ref.output.nil?
|
86
|
+
ref.output.path
|
89
87
|
else
|
90
88
|
current_input.resolve_path(ref)
|
91
89
|
end
|
92
90
|
end
|
93
|
-
|
91
|
+
|
94
92
|
def input(path)
|
95
|
-
|
96
|
-
|
97
|
-
|
93
|
+
project.input(path) ||
|
94
|
+
input_with_output_path(path) ||
|
95
|
+
raise(ReferenceError, %{Can't find "#{path}"})
|
96
|
+
end
|
97
|
+
|
98
|
+
def input_with_output_path(path)
|
99
|
+
o = project.output(path)
|
100
|
+
o ? o.input : nil
|
98
101
|
end
|
99
|
-
|
102
|
+
|
100
103
|
def with_input(input)
|
101
|
-
record_dependency_on(input
|
104
|
+
output.record_dependency_on(input)
|
102
105
|
@input_stack.push(input)
|
103
106
|
begin
|
104
107
|
yield
|
@@ -106,12 +109,12 @@ module Pith
|
|
106
109
|
@input_stack.pop
|
107
110
|
end
|
108
111
|
end
|
109
|
-
|
112
|
+
|
110
113
|
def render_ref(template_ref, locals = {}, &block)
|
111
114
|
template_input = input(resolve_reference(template_ref))
|
112
115
|
render(template_input, locals, &block)
|
113
116
|
end
|
114
117
|
|
115
118
|
end
|
116
|
-
|
119
|
+
|
117
120
|
end
|
data/lib/pith/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "rack"
|
2
|
-
require "
|
2
|
+
require "pathname"
|
3
|
+
require "thread"
|
3
4
|
|
4
5
|
module Pith
|
5
6
|
|
@@ -10,29 +11,48 @@ module Pith
|
|
10
11
|
use Rack::CommonLogger
|
11
12
|
use Rack::ShowExceptions
|
12
13
|
use Rack::Lint
|
13
|
-
use
|
14
|
-
use Pith::Server::DefaultToHtml, project.output_dir
|
14
|
+
use Pith::Server::OutputFinder, project
|
15
15
|
run Rack::Directory.new(project.output_dir)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
extend self
|
20
20
|
|
21
|
-
class
|
21
|
+
class OutputFinder
|
22
22
|
|
23
|
-
def initialize(app,
|
23
|
+
def initialize(app, project)
|
24
24
|
@app = app
|
25
|
-
@
|
25
|
+
@project = project
|
26
26
|
end
|
27
27
|
|
28
28
|
def call(env)
|
29
29
|
|
30
|
+
@project.sync_every(1)
|
31
|
+
|
30
32
|
path_info = ::Rack::Utils.unescape(env["PATH_INFO"])
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
ends_with_slash = (path_info[-1] == '/')
|
34
|
+
|
35
|
+
outputs = @project.outputs.sort_by { |output| output.path }
|
36
|
+
outputs.each do |output|
|
37
|
+
|
38
|
+
output_path = "/" + output.path.to_s
|
39
|
+
|
40
|
+
if !ends_with_slash && output_path =~ %r{^#{path_info}/}
|
41
|
+
return [
|
42
|
+
302,
|
43
|
+
{ "Location" => path_info + "/" },
|
44
|
+
[]
|
45
|
+
]
|
35
46
|
end
|
47
|
+
|
48
|
+
["", ".html", "index.html"].map do |ext|
|
49
|
+
if output_path == (path_info + ext)
|
50
|
+
output.build
|
51
|
+
env["PATH_INFO"] += ext
|
52
|
+
return @app.call(env)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
36
56
|
end
|
37
57
|
|
38
58
|
@app.call(env)
|
data/lib/pith/version.rb
CHANGED
data/spec/pith/input_spec.rb
CHANGED
@@ -5,53 +5,90 @@ require "pith/project"
|
|
5
5
|
describe Pith::Input do
|
6
6
|
|
7
7
|
before do
|
8
|
-
$input_dir.mkpath
|
9
8
|
@project = Pith::Project.new(:input_dir => $input_dir)
|
10
9
|
end
|
11
10
|
|
12
11
|
def make_input(path)
|
12
|
+
path = Pathname(path)
|
13
13
|
input_file = $input_dir + path
|
14
14
|
input_file.parent.mkpath
|
15
15
|
input_file.open("w") do |io|
|
16
16
|
yield io if block_given?
|
17
17
|
end
|
18
|
-
@project
|
18
|
+
Pith::Input.new(@project, path)
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
context "for a template" do
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
@input.title.should == "Some page"
|
23
|
+
subject do
|
24
|
+
make_input("dir/some_page.html.md.erb")
|
26
25
|
end
|
27
26
|
|
28
|
-
it
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
it { should be_template }
|
28
|
+
|
29
|
+
describe "#title" do
|
30
|
+
|
31
|
+
it "is based on last component of filename" do
|
32
|
+
subject.title.should == "Some page"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can be over-ridden in metadata" do
|
36
|
+
input = make_input("dir/some_page.html.haml") do |i|
|
37
|
+
i.puts "---"
|
38
|
+
i.puts "title: Blah blah"
|
39
|
+
i.puts "..."
|
40
|
+
end
|
41
|
+
input.title.should == "Blah blah"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#output" do
|
47
|
+
|
48
|
+
it "returns an Output" do
|
49
|
+
subject.output.should_not be_nil
|
33
50
|
end
|
34
|
-
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#output_path" do
|
55
|
+
|
56
|
+
it "excludes the template-type extensions" do
|
57
|
+
subject.output_path.should == Pathname("dir/some_page.html")
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#pipeline" do
|
63
|
+
|
64
|
+
it "is a list of Tilt processors" do
|
65
|
+
subject.pipeline.should == [Tilt["erb"], Tilt["md"]]
|
66
|
+
end
|
67
|
+
|
35
68
|
end
|
36
69
|
|
37
70
|
end
|
38
71
|
|
39
|
-
|
72
|
+
context "for a resource" do
|
40
73
|
|
41
|
-
|
42
|
-
|
43
|
-
@input.output_path.should == Pathname("dir/some_page.html")
|
74
|
+
subject do
|
75
|
+
make_input("dir/some_image.gif")
|
44
76
|
end
|
45
77
|
|
78
|
+
it { should_not be_template }
|
79
|
+
|
80
|
+
its(:pipeline) { should be_empty }
|
81
|
+
|
46
82
|
end
|
47
83
|
|
48
|
-
|
84
|
+
context "for an ignored file" do
|
49
85
|
|
50
|
-
|
51
|
-
|
52
|
-
@input.pipeline.should == [Tilt["haml"]]
|
86
|
+
subject do
|
87
|
+
make_input("_blah/blah.de")
|
53
88
|
end
|
54
89
|
|
90
|
+
its(:output) { should be_nil }
|
91
|
+
|
55
92
|
end
|
56
93
|
|
57
94
|
end
|