pith 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/README.markdown +29 -0
  2. data/Rakefile +16 -0
  3. data/bin/pith +60 -0
  4. data/cucumber.yml +7 -0
  5. data/features/content_for.feature +29 -0
  6. data/features/content_for.feature~ +29 -0
  7. data/features/haml.feature +24 -0
  8. data/features/haml.feature~ +24 -0
  9. data/features/helpers.feature +23 -0
  10. data/features/ignorance.feature +26 -0
  11. data/features/ignorance.feature~ +26 -0
  12. data/features/include.feature +84 -0
  13. data/features/include.feature~ +84 -0
  14. data/features/incremental_rebuild.feature +24 -0
  15. data/features/layouts.feature +43 -0
  16. data/features/layouts.feature~ +43 -0
  17. data/features/linking.feature +79 -0
  18. data/features/linking.feature~ +79 -0
  19. data/features/metadata.feature +20 -0
  20. data/features/metadata.feature~ +20 -0
  21. data/features/step_definitions/build_steps.rb +46 -0
  22. data/features/step_definitions/build_steps.rb~ +29 -0
  23. data/features/support/env.rb +75 -0
  24. data/features/support/env.rb~ +43 -0
  25. data/features/support/rspec_matchers.rb +5 -0
  26. data/features/textile.feature +29 -0
  27. data/features/textile.feature~ +29 -0
  28. data/features/verbatim.feature +32 -0
  29. data/features/verbatim.feature~ +32 -0
  30. data/lib/pith.rb +1 -0
  31. data/lib/pith.rb~ +2 -0
  32. data/lib/pith/console_logger.rb +20 -0
  33. data/lib/pith/console_logger.rb~ +20 -0
  34. data/lib/pith/input.rb +189 -0
  35. data/lib/pith/input.rb~ +95 -0
  36. data/lib/pith/metadata.rb +26 -0
  37. data/lib/pith/metadata.rb~ +26 -0
  38. data/lib/pith/project.rb +68 -0
  39. data/lib/pith/project.rb~ +50 -0
  40. data/lib/pith/render_context.rb +77 -0
  41. data/lib/pith/render_context.rb~ +66 -0
  42. data/lib/pith/server.rb +42 -0
  43. data/lib/pith/server.rb~ +45 -0
  44. data/lib/pith/version.rb +3 -0
  45. data/lib/pith/version.rb~ +3 -0
  46. data/sample/_layouts/standard.haml +7 -0
  47. data/sample/index.html.haml +8 -0
  48. data/sample/stylesheets/app.css.sass +44 -0
  49. data/spec/pith/metadata_spec.rb +46 -0
  50. data/spec/pith/metadata_spec.rb~ +46 -0
  51. data/spec/pith/project_spec.rb +61 -0
  52. data/spec/spec_helper.rb +27 -0
  53. data/spec/spec_helper.rb~ +0 -0
  54. metadata +166 -0
@@ -0,0 +1,43 @@
1
+ require "pathname"
2
+
3
+ class DirHash
4
+
5
+ def initialize(dir)
6
+ @dir = Pathname(dir)
7
+ end
8
+
9
+ def [](file_name)
10
+ file_path = @dir + file_name
11
+ file_path.read if file_path.exist?
12
+ end
13
+
14
+ def []=(file_name, content)
15
+ file_path = @dir + file_name
16
+ file_path.parent.mkpath
17
+ file_path.open("w") do |io|
18
+ io << content
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ $project_dir = Pathname(__FILE__).expand_path.parent.parent.parent
25
+ $tmp_dir = $project_dir + "tmp"
26
+
27
+ $input_dir = $tmp_dir + "input"
28
+
29
+ $output_dir = $tmp_dir + "output"
30
+ $output_dir.mkpath
31
+
32
+ $: << ($project_dir + "lib").to_s
33
+ require "pith"
34
+
35
+ Before do
36
+ [$input_dir, $output_dir].each do |dir|
37
+ dir.rmtree if dir.exist?
38
+ dir.mkpath
39
+ end
40
+ @project = Pith::Project.new(:input_dir => $input_dir, :output_dir => $output_dir)
41
+ @inputs = DirHash.new($input_dir)
42
+ @outputs = DirHash.new($output_dir)
43
+ end
@@ -0,0 +1,5 @@
1
+ Spec::Matchers.define :contain do |expected|
2
+ match do |actual|
3
+ actual.any? { |x| expected === x }
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ Feature: support for Textile markup
2
+
3
+ I want to use Textile fragments in pages
4
+ So that I can express content succintly
5
+
6
+ Scenario: Haml page with embedded Textile
7
+
8
+ Given input file "page.html.haml" contains
9
+ """
10
+ :textile
11
+
12
+ A paragraph.
13
+
14
+ * one
15
+ * two
16
+ * three
17
+ """
18
+
19
+ When I build the site
20
+
21
+ Then output file "page.html" should contain
22
+ """
23
+ <p>A paragraph.</p>
24
+ <ul>
25
+ <li>one</li>
26
+ <li>two</li>
27
+ <li>three</li>
28
+ </ul>
29
+ """
@@ -0,0 +1,29 @@
1
+ Feature: support for Textile markup
2
+
3
+ I want to use Textile fragments in pages
4
+ So that I can express content succintly
5
+
6
+ Scenario: Haml page with embedded Textile
7
+
8
+ Given input file "page.html.haml" contains
9
+ """
10
+ :textile
11
+
12
+ A paragraph.
13
+
14
+ * one
15
+ * two
16
+ * three
17
+ """
18
+
19
+ When I build the site
20
+
21
+ Then output file "page.html" should contain
22
+ """
23
+ <p>A paragraph.</p>
24
+ <ul>
25
+ <li>one</li>
26
+ <li>two</li>
27
+ <li>three</li>
28
+ </ul>
29
+ """
@@ -0,0 +1,32 @@
1
+ Feature: unrecognised input files are copied into output intact
2
+
3
+ I want any unrecognised file to be copied into the site verbatim
4
+ So that I can mix dynamic and static content
5
+
6
+ Scenario: text file
7
+
8
+ Given input file "verbatim.txt" contains
9
+ """
10
+ Blah de blah
11
+ """
12
+
13
+ When I build the site
14
+
15
+ Then output file "verbatim.txt" should contain
16
+ """
17
+ Blah de blah
18
+ """
19
+
20
+ Scenario: file in subdirectory
21
+
22
+ Given input file "blah/verbatim.txt" contains
23
+ """
24
+ Blah de blah
25
+ """
26
+
27
+ When I build the site
28
+
29
+ Then output file "blah/verbatim.txt" should contain
30
+ """
31
+ Blah de blah
32
+ """
@@ -0,0 +1,32 @@
1
+ Feature: unrecognised input files are copied into output intact
2
+
3
+ I want any unrecognised file to be copied into the site verbatim
4
+ So that I can mix dynamic and static content
5
+
6
+ Scenario: text file
7
+
8
+ Given input file "verbatim.txt" contains
9
+ """
10
+ Blah de blah
11
+ """
12
+
13
+ When I build the site
14
+
15
+ Then output file "verbatim.txt" should contain
16
+ """
17
+ Blah de blah
18
+ """
19
+
20
+ Scenario: file in subdirectory
21
+
22
+ Given input file "blah/verbatim.txt" contains
23
+ """
24
+ Blah de blah
25
+ """
26
+
27
+ When I build the site
28
+
29
+ Then output file "blah/verbatim.txt" should contain
30
+ """
31
+ Blah de blah
32
+ """
@@ -0,0 +1 @@
1
+ require 'pith/project'
@@ -0,0 +1,2 @@
1
+ require 'pith/console_logger'
2
+ require 'pith/project'
@@ -0,0 +1,20 @@
1
+ module Pith
2
+
3
+ class ConsoleLogger
4
+
5
+ def initialize(io = STDOUT)
6
+ @io = io
7
+ end
8
+
9
+ def info(message, &block)
10
+ message ||= block.call
11
+ @io.puts(message)
12
+ end
13
+
14
+ def write(message)
15
+ @io.puts(message)
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,20 @@
1
+ module Pith
2
+
3
+ class ConsoleLogger
4
+
5
+ def initialize(io = STDOUT)
6
+ @io = io
7
+ end
8
+
9
+ def info(message, &block)
10
+ message ||= block.call
11
+ @io.puts(message)
12
+ end
13
+
14
+ def write(message)
15
+ @io.puts(message)
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,189 @@
1
+ require "pith/render_context"
2
+ require "pith/metadata"
3
+ require "fileutils"
4
+
5
+ module Pith
6
+ module Input
7
+
8
+ class << self
9
+
10
+ def new(project, path)
11
+ if path.to_s =~ /^(.*)\.(.*)$/ && RenderContext.can_render?($2)
12
+ Template.new(project, path)
13
+ else
14
+ Verbatim.new(project, path)
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ class Abstract
21
+
22
+ def initialize(project, path)
23
+ @project = project
24
+ @path = path
25
+ end
26
+
27
+ attr_reader :project, :path
28
+
29
+ # Public: Get the file-system location of this input.
30
+ #
31
+ # Returns a fully-qualified Pathname.
32
+ #
33
+ def full_path
34
+ project.input_dir + path
35
+ end
36
+
37
+ # Public: Get YAML metadata declared in the header of of a template.
38
+ #
39
+ # The first line of the template must end with "---". Any preceding characters
40
+ # are considered to be a comment prefix, and are stripped from the following
41
+ # lines. The metadata block ends when the comment block ends, or a line ending
42
+ # with "..." is encountered.
43
+ #
44
+ # Examples
45
+ #
46
+ # Given input starting with:
47
+ #
48
+ # -# ---
49
+ # -# published: 2008-09-15
50
+ # -# ...
51
+ #
52
+ # input.meta
53
+ # #=> { "published" => "2008-09-15" }
54
+ #
55
+ # Returns a Hash.
56
+ #
57
+ def meta
58
+ if @metadata.nil?
59
+ full_path.open do |io|
60
+ @metadata = Pith::Metadata.extract_from(io).freeze
61
+ end
62
+ end
63
+ @metadata
64
+ end
65
+
66
+ # Public: Generate a corresponding output file.
67
+ #
68
+ # Returns the output Pathname.
69
+ # Returns nil if no output was generated.
70
+ #
71
+ def build
72
+ generate_output unless ignorable? || uptodate?
73
+ end
74
+
75
+ # Public: Resolve a reference relative to this input.
76
+ #
77
+ # href - a String referencing another asset
78
+ #
79
+ # An href starting with "/" is resolved relative to the project root;
80
+ # anything else is resolved relative to this input.
81
+ #
82
+ # Returns a fully-qualified Pathname of the asset.
83
+ #
84
+ def relative_path(href)
85
+ href = href.to_str
86
+ if href[0,1] == "/"
87
+ Pathname(href[1..-1])
88
+ else
89
+ path.parent + href
90
+ end
91
+ end
92
+
93
+ # Resolve a reference relative to this input.
94
+ #
95
+ # href - a String referencing another asset
96
+ #
97
+ # Returns the referenced Input.
98
+ #
99
+ def relative_input(href)
100
+ project.input(relative_path(href))
101
+ end
102
+
103
+ # Consider whether this input can be ignored.
104
+ #
105
+ # Returns true if it can.
106
+ #
107
+ def ignorable?
108
+ path.to_s.split("/").any? { |component| component.to_s[0,1] == "_" }
109
+ end
110
+
111
+ protected
112
+
113
+ def logger
114
+ project.logger
115
+ end
116
+
117
+ def trace(strategy, output_path = nil)
118
+ output_path ||= "X"
119
+ logger.info("%-36s%-14s%s" % [path, "--(#{strategy})-->", output_path])
120
+ end
121
+
122
+ end
123
+
124
+ class Template < Abstract
125
+
126
+ def initialize(project, path)
127
+ super(project, path)
128
+ path.to_s =~ /^(.*)\.(.*)$/
129
+ @output_path = $1
130
+ @type = $2
131
+ end
132
+
133
+ attr_reader :output_path, :type
134
+
135
+ def full_output_path
136
+ project.output_dir + output_path
137
+ end
138
+
139
+ def uptodate?
140
+ return false if all_input_paths.nil?
141
+ FileUtils.uptodate?(full_output_path, all_input_paths)
142
+ end
143
+
144
+ # Render this input using Tilt
145
+ #
146
+ def generate_output
147
+ trace(type, output_path)
148
+ full_output_path.parent.mkpath
149
+ render_context = RenderContext.new(project)
150
+ full_output_path.open("w") do |out|
151
+ out.puts(render_context.render(self))
152
+ end
153
+ remember_dependencies(render_context.rendered_inputs)
154
+ end
155
+
156
+ private
157
+
158
+ def remember_dependencies(rendered_inputs)
159
+ @all_input_paths = rendered_inputs.map { |input| input.full_path }
160
+ end
161
+
162
+ def all_input_paths
163
+ @all_input_paths
164
+ end
165
+
166
+ end
167
+
168
+ class Verbatim < Abstract
169
+
170
+ def full_output_path
171
+ project.output_dir + path
172
+ end
173
+
174
+ def uptodate?
175
+ FileUtils.uptodate?(full_output_path, [full_path])
176
+ end
177
+
178
+ # Copy this input verbatim into the output directory
179
+ #
180
+ def generate_output
181
+ trace("copy", path)
182
+ full_output_path.parent.mkpath
183
+ FileUtils.copy(full_path, full_output_path)
184
+ end
185
+
186
+ end
187
+
188
+ end
189
+ end
@@ -0,0 +1,95 @@
1
+ require "tilt"
2
+ require "pith/output_context"
3
+ require "pith/metadata"
4
+
5
+ module Pith
6
+
7
+ class Input
8
+
9
+ def initialize(project, path)
10
+ @project = project
11
+ @path = path
12
+ end
13
+
14
+ attr_reader :project, :path
15
+
16
+ def build
17
+ ignore || evaluate_as_tilt_template || copy_verbatim
18
+ end
19
+
20
+ def relative_input(name)
21
+ resolved_path = relative_path(name)
22
+ input = project.inputs.find do |input|
23
+ input.path == resolved_path
24
+ end
25
+ raise %{can't locate "#{resolved_path}"} if input.nil?
26
+ input
27
+ end
28
+
29
+ def relative_path(name)
30
+ name = name.to_str
31
+ if name[0,1] == "/"
32
+ Pathname(name[1..-1])
33
+ else
34
+ path.parent + name
35
+ end
36
+ end
37
+
38
+ def render(context, locals = {}, &block)
39
+ Tilt.new(full_path).render(context, locals, &block)
40
+ end
41
+
42
+ def meta
43
+ if @metadata.nil?
44
+ full_path.open do |io|
45
+ @metadata = Pith::Metadata.extract_from(io).freeze
46
+ end
47
+ end
48
+ @metadata
49
+ end
50
+
51
+ private
52
+
53
+ def logger
54
+ project.logger
55
+ end
56
+
57
+ def trace(strategy, output_path = nil)
58
+ output_path ||= "X"
59
+ logger.info("%-36s%-14s%s" % [path, "--(#{strategy})-->", output_path])
60
+ end
61
+
62
+ def full_path
63
+ project.input_dir + path
64
+ end
65
+
66
+ def ignore
67
+ path.to_s.split("/").any? { |component| component.to_s[0,1] == "_" }
68
+ end
69
+ alias :ignorable? :ignore
70
+
71
+ def evaluate_as_tilt_template
72
+ if path.to_s =~ /^(.*)\.(.*)$/ && Tilt.registered?($2)
73
+ output_path = Pathname($1); ext = $2
74
+ trace(ext, output_path)
75
+ output_file = project.output_dir + output_path
76
+ output_file.parent.mkpath
77
+ output_file.open("w") do |out|
78
+ output = render(RenderContext.new(self))
79
+ out.puts(output)
80
+ end
81
+ output_file
82
+ end
83
+ end
84
+
85
+ def copy_verbatim
86
+ trace("copy", path)
87
+ output_path = project.output_dir + path
88
+ output_path.parent.mkpath
89
+ FileUtils.copy(full_path, output_path)
90
+ output_path
91
+ end
92
+
93
+ end
94
+
95
+ end