pith 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -30,7 +30,7 @@ Create an input directory for your site (wherever you want), and pop some files
30
30
  logo.png
31
31
  index.html.haml
32
32
 
33
- The only requirement is the existance of a sub-directory called "`_pith`". Pith checks that it's present, to prevent you accidently treating your entire home-directory as website input.
33
+ The only requirement is the existance of a subdirectory called "`_pith`". Pith checks that it's present, to prevent you accidently treating your entire home directory as website input.
34
34
 
35
35
  Next, use the `pith build` command to convert your inputs into a functioning website.
36
36
 
@@ -39,7 +39,16 @@ Next, use the `pith build` command to convert your inputs into a functioning web
39
39
  --(copy)--> images/logo.png
40
40
  --(haml)--> index.html
41
41
 
42
- Any input file with an extension recognised by [Tilt][tilt] is considered to be a template, and will be dynamically evaluated. Formats supported by Tilt include:
42
+ By default, output is generated into an subdirectory called "`_out`", inside the input directory ... but the default can be easily overridden, e.g.
43
+
44
+ $ pith -i SITE -o OUTPUT build
45
+
46
+ Templates
47
+ ---------
48
+
49
+ Files in the input directory are considered to be "templates" if the file name ends with a template format extension recognised as such by [Tilt][tilt], e.g. "`.haml`", or "`.textile`". These will be evaluated dynamically. Pith strips the format extension off the file name when generating output.
50
+
51
+ Formats supported by Tilt include:
43
52
 
44
53
  - [Markdown](http://daringfireball.net/projects/markdown/) (`markdown`, `md`)
45
54
  - [Textile](http://redcloth.org/hobix.com/textile/) (`textile`)
@@ -48,7 +57,12 @@ Any input file with an extension recognised by [Tilt][tilt] is considered to be
48
57
  - [Sass][sass] (`sass`)
49
58
  - [CoffeeScript](http://jashkenas.github.com/coffee-script/) (`coffee`)
50
59
 
51
- Anything else is just copied verbatim into the generated site.
60
+ Any non-template input files (we call them "resources") are just copied verbatim into the output directory.
61
+
62
+ Ignored files
63
+ -------------
64
+
65
+ Files or directories beginning with an underscore are ignored; that is, we don't generate corresponding output files. They can still be used as "layout" or "partial" templates though; see below.
52
66
 
53
67
  Page metadata
54
68
  -------------
@@ -69,18 +83,16 @@ Metadata provided in the header can be referenced by template content, via the "
69
83
 
70
84
  This is especially useful in "layout" templates (see below).
71
85
 
72
- Partials
73
- --------
86
+ Partials and Layouts
87
+ --------------------
74
88
 
75
89
  Templates can include other templates, e.g.
76
90
 
77
- = include("_header.haml")
78
-
79
- Note the leading underscore ("_"). Any input file (or directory) beginning with an underscore is ignored when generating outputs.
91
+ = include "_header.haml"
80
92
 
81
93
  When including, you can pass local variables, e.g.
82
94
 
83
- = include("_list.haml", :items => [1,2,3])
95
+ = include "_list.haml", :items => [1,2,3]
84
96
 
85
97
  which can be accessed in the included template:
86
98
 
@@ -88,15 +100,12 @@ which can be accessed in the included template:
88
100
  - items.each do |i|
89
101
  %li= i
90
102
 
91
- Layouts
92
- -------
93
-
94
- Layout templates are a bit like partials, except that they take a block, e.g.
103
+ In Haml templates, you can also pass a block, e.g.
95
104
 
96
- = inside "_mylayout.haml" do
105
+ = include "_mylayout.haml" do
97
106
  %p Some content
98
107
 
99
- Use "`yield`" to embed the block's content within the layout:
108
+ and access it in the template using "`yield`":
100
109
 
101
110
  !!!
102
111
  %html
@@ -106,14 +115,16 @@ Use "`yield`" to embed the block's content within the layout:
106
115
  .main
107
116
  = yield
108
117
 
109
- Layouts can also be specified using a "`layout`" entry in the page header, e.g.
118
+ This way, any template can be used as a "layout".
119
+
120
+ Layouts can also be applied by using a "`layout`" entry in the page header, e.g.
110
121
 
111
122
  ---
112
123
  layout: "/_mylayout.haml"
113
124
  ...
114
125
 
115
126
  Some content
116
-
127
+
117
128
  Relative links
118
129
  --------------
119
130
 
@@ -123,7 +134,7 @@ It's sensible to use relative URIs when linking to other pages (and resources) i
123
134
 
124
135
  %img{:src => href("/images/logo.png")}
125
136
 
126
- Any path beginning with a slash ("/") is resolved relative to the root of the site; anything else is resolve relative to the current input-file (even if that happens to be a layout or partial). Either way, "`href`" always returns a relative link.
137
+ Any path beginning with a slash ("/") is resolved relative to the root of the site; anything else is resolved relative to the current input-file (even if that happens to be a layout or partial). Either way, "`href`" always returns a relative link.
127
138
 
128
139
  There's also a "`link`" function, for even easier hyper-linking:
129
140
 
@@ -0,0 +1,14 @@
1
+ Feature: automatic cleanup
2
+
3
+ I want old outputs removed from the output directory
4
+ So that they don't hang around
5
+
6
+ Scenario: output without matching input
7
+
8
+ Given input file "blah.txt" exists
9
+
10
+ When I build the site
11
+ And I remove input file "blah.txt"
12
+ And I rebuild the site
13
+
14
+ Then output file "blah.txt" should not exist
@@ -30,6 +30,10 @@ When /^I change input file "([^\"]*)" to contain$/ do |path, content|
30
30
  @inputs[path] = content
31
31
  end
32
32
 
33
+ When /^I remove input file "([^\"]*)"$/ do |path|
34
+ @inputs[path] = nil
35
+ end
36
+
33
37
  When /^I (?:re)?build the site$/ do
34
38
  @project.logger.clear
35
39
  @project.build
@@ -14,6 +14,14 @@ Given "the site is up-to-date" do
14
14
  When "I build the site"
15
15
  end
16
16
 
17
+ Given /^the "([^\"]*)" flag is enabled$/ do |flag|
18
+ @project.send("#{flag}=", true)
19
+ end
20
+
21
+ Given /^the "([^\"]*)" flag is disabled$/ do |flag|
22
+ @project.send("#{flag}=", false)
23
+ end
24
+
17
25
  When /^I change input file "([^\"]*)" to contain "([^\"]*)"$/ do |path, content|
18
26
  @inputs[path] = content
19
27
  end
@@ -22,6 +30,10 @@ When /^I change input file "([^\"]*)" to contain$/ do |path, content|
22
30
  @inputs[path] = content
23
31
  end
24
32
 
33
+ When /^I remove input file "([^\"]*)"$/ do |path|
34
+ @inputs[path] = nil
35
+ end
36
+
25
37
  When /^I (?:re)?build the site$/ do
26
38
  @project.logger.clear
27
39
  @project.build
@@ -12,7 +12,15 @@ class DirHash
12
12
  end
13
13
 
14
14
  def []=(file, content)
15
- write(file, content)
15
+ if content.nil?
16
+ remove(file)
17
+ else
18
+ write(file, content)
19
+ end
20
+ end
21
+
22
+ def remove(file)
23
+ (@dir + file).unlink
16
24
  end
17
25
 
18
26
  def write(file, content, options = {})
@@ -12,7 +12,15 @@ class DirHash
12
12
  end
13
13
 
14
14
  def []=(file, content)
15
- write(file, content)
15
+ if content.nil?
16
+ remove(file)
17
+ else
18
+ write(file, content)
19
+ end
20
+ end
21
+
22
+ def remove(file)
23
+ (@dir + file).unlink
16
24
  end
17
25
 
18
26
  def write(file, content, options = {})
@@ -11,10 +11,6 @@ module Pith
11
11
  @io.puts(message)
12
12
  end
13
13
 
14
- def write(message)
15
- @io.puts(message)
16
- end
17
-
18
14
  end
19
15
 
20
16
  end
@@ -32,7 +32,7 @@ module Pith
32
32
  #
33
33
  def build
34
34
  return false if ignorable? || uptodate?
35
- logger.info("%-14s%s" % ["--(#{type})-->", output_path])
35
+ logger.info("%-16s%s" % ["--(#{type})-->", output_path])
36
36
  generate_output
37
37
  end
38
38
 
@@ -0,0 +1,74 @@
1
+ require "pathname"
2
+
3
+ module Pith
4
+ module Input
5
+
6
+ class Abstract
7
+
8
+ def initialize(project, path)
9
+ @project = project
10
+ @path = path
11
+ end
12
+
13
+ attr_reader :project, :path
14
+
15
+ # Public: Get the file-system location of this input.
16
+ #
17
+ # Returns a fully-qualified Pathname.
18
+ #
19
+ def file
20
+ project.input_dir + path
21
+ end
22
+
23
+ # Public: Get the file-system location of the corresponding output file.
24
+ #
25
+ # Returns a fully-qualified Pathname.
26
+ #
27
+ def output_file
28
+ project.output_dir + output_path
29
+ end
30
+
31
+ # Public: Generate an output file.
32
+ #
33
+ def build
34
+ return false if ignorable? || uptodate?
35
+ logger.info("%-16s%s" % ["--(#{type})-->", output_path])
36
+ generate_output
37
+ end
38
+
39
+ # Public: Resolve a reference relative to this input.
40
+ #
41
+ # ref - a String referencing another asset
42
+ #
43
+ # A ref starting with "/" is resolved relative to the project root;
44
+ # anything else is resolved relative to this input.
45
+ #
46
+ # Returns a fully-qualified Pathname of the asset.
47
+ #
48
+ def resolve_path(ref)
49
+ ref = ref.to_str
50
+ if ref[0,1] == "/"
51
+ Pathname(ref[1..-1])
52
+ else
53
+ path.parent + ref
54
+ end
55
+ end
56
+
57
+ # Consider whether this input can be ignored.
58
+ #
59
+ # Returns true if it can.
60
+ #
61
+ def ignorable?
62
+ path.to_s.split("/").any? { |component| component.to_s[0,1] == "_" }
63
+ end
64
+
65
+ protected
66
+
67
+ def logger
68
+ project.logger
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -4,6 +4,8 @@ require "pith/input/abstract"
4
4
  module Pith
5
5
  module Input
6
6
 
7
+ # Represents a non-template input.
8
+ #
7
9
  class Resource < Abstract
8
10
 
9
11
  def output_path
@@ -0,0 +1,41 @@
1
+ require "fileutils"
2
+ require "pith/input/abstract"
3
+
4
+ module Pith
5
+ module Input
6
+
7
+ class Resource < Abstract
8
+
9
+ def output_path
10
+ path
11
+ end
12
+
13
+ def type
14
+ "copy"
15
+ end
16
+
17
+ def uptodate?
18
+ FileUtils.uptodate?(output_file, [file])
19
+ end
20
+
21
+ # Copy this input verbatim into the output directory
22
+ #
23
+ def generate_output
24
+ output_file.parent.mkpath
25
+ FileUtils.copy(file, output_file)
26
+ end
27
+
28
+ # Render this input, for inclusion within templates
29
+ #
30
+ def render(context, locals = {})
31
+ file.read
32
+ end
33
+
34
+ def meta
35
+ {}
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
@@ -8,6 +8,8 @@ require "yaml"
8
8
  module Pith
9
9
  module Input
10
10
 
11
+ # Represents an input that should be evaluated as a template.
12
+ #
11
13
  class Template < Abstract
12
14
 
13
15
  def self.can_handle?(path)
@@ -10,14 +10,16 @@ module Pith
10
10
 
11
11
  class Template < Abstract
12
12
 
13
- class UnrecognisedType < StandardError; end
13
+ def self.can_handle?(path)
14
+ path.to_str =~ /\.([^.]+)$/ && Tilt.registered?($1)
15
+ end
14
16
 
15
17
  def initialize(project, path)
18
+ raise(ArgumentError, "#{path} is not a template") unless Template.can_handle?(path)
16
19
  super(project, path)
17
- path.to_s =~ /^(.*)\.(.*)$/
20
+ path.to_str =~ /^(.+)\.(.+)$/ || raise("huh?")
18
21
  @output_path = Pathname($1)
19
22
  @type = $2
20
- raise(UnrecognisedType, @type) unless Tilt.registered?(@type)
21
23
  load
22
24
  end
23
25
 
@@ -88,6 +90,10 @@ module Pith
88
90
  end
89
91
 
90
92
  private
93
+
94
+ def default_title
95
+ path.basename.to_s.sub(/\..*/, '').tr('_-', ' ').capitalize
96
+ end
91
97
 
92
98
  # Read input file, extracting YAML meta-data header, and template content.
93
99
  #
data/lib/pith/input.rb CHANGED
@@ -6,6 +6,8 @@ module Pith
6
6
 
7
7
  class << self
8
8
 
9
+ # Construct an object representing a project input file.
10
+ #
9
11
  def new(project, path)
10
12
  if Template.can_handle?(path)
11
13
  Template.new(project, path)
data/lib/pith/input.rb~ CHANGED
@@ -1,5 +1,5 @@
1
1
  require "pith/input/template"
2
- require "pith/input/verbatim"
2
+ require "pith/input/resource"
3
3
 
4
4
  module Pith
5
5
  module Input
@@ -7,9 +7,11 @@ module Pith
7
7
  class << self
8
8
 
9
9
  def new(project, path)
10
- Template.new(project, path)
11
- rescue Template::UnrecognisedType
12
- Verbatim.new(project, path)
10
+ if Template.can_handle?(path)
11
+ Template.new(project, path)
12
+ else
13
+ Resource.new(project, path)
14
+ end
13
15
  end
14
16
 
15
17
  end
@@ -7,8 +7,10 @@ class Pathname
7
7
  utime(mtime, mtime) if mtime
8
8
  end
9
9
 
10
- def glob_all(pattern)
11
- Pathname.glob(self + pattern, File::FNM_DOTMATCH)
10
+ def all_files(pattern = "**/*")
11
+ Pathname.glob(self + pattern, File::FNM_DOTMATCH).select do |path|
12
+ path.file?
13
+ end
12
14
  end
13
15
 
14
16
  end
@@ -0,0 +1,16 @@
1
+ require "pathname"
2
+
3
+ class Pathname
4
+
5
+ def touch(mtime = nil)
6
+ FileUtils.touch(self.to_s)
7
+ utime(mtime, mtime) if mtime
8
+ end
9
+
10
+ def all_files(pattern = "**/*")
11
+ Pathname.glob(self + pattern, File::FNM_DOTMATCH).select do |path|
12
+ path.file?
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,50 @@
1
+ require "pith/input"
2
+ require "time"
3
+
4
+ module Pith
5
+ module Plugins
6
+ module Publication
7
+
8
+ module TemplateMethods
9
+
10
+ def published?
11
+ !published_at.nil?
12
+ end
13
+
14
+ def published_at
15
+ parse_date(meta["published"])
16
+ end
17
+
18
+ def updated_at
19
+ parse_date(meta["updated"]) || published_at
20
+ end
21
+
22
+ private
23
+
24
+ def parse_date(date_string)
25
+ Time.parse(date_string) if date_string
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+
34
+ module Pith
35
+ module Input
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
+
49
+ end
50
+ end
@@ -5,6 +5,8 @@ module Pith
5
5
 
6
6
  class Project
7
7
 
8
+ # Return all the published inputs, in order of publication.
9
+ #
8
10
  def published_inputs
9
11
  inputs.select { |i| i.published? }.sort_by { |i| i.published_at }
10
12
  end
@@ -0,0 +1,14 @@
1
+ require "pith/project"
2
+ require "pith/plugins/publication/input"
3
+
4
+ module Pith
5
+
6
+ class Project
7
+
8
+ def published_inputs
9
+ inputs.select { |i| i.published? }.sort_by { |i| i.published_at }
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,2 @@
1
+ require "pith/plugins/publication/input"
2
+ require "pith/plugins/publication/project"
data/lib/pith/project.rb CHANGED
@@ -37,8 +37,7 @@ module Pith
37
37
  # call #refresh to discard the cached data.
38
38
  #
39
39
  def inputs
40
- @inputs ||= input_dir.glob_all("**/*").map do |input_file|
41
- next if input_file.directory?
40
+ @inputs ||= input_dir.all_files.map do |input_file|
42
41
  path = input_file.relative_path_from(input_dir)
43
42
  find_or_create_input(path)
44
43
  end.compact
@@ -64,9 +63,8 @@ module Pith
64
63
  def build
65
64
  refresh
66
65
  load_config
67
- inputs.each do |input|
68
- input.build
69
- end
66
+ remove_old_outputs
67
+ generate_outputs
70
68
  output_dir.touch
71
69
  end
72
70
 
@@ -97,7 +95,7 @@ module Pith
97
95
 
98
96
  def config_files
99
97
  @config_files ||= begin
100
- input_dir.glob_all("_pith/**")
98
+ input_dir.all_files("_pith/**")
101
99
  end.to_set
102
100
  end
103
101
 
@@ -111,6 +109,23 @@ module Pith
111
109
  end
112
110
  end
113
111
 
112
+ def remove_old_outputs
113
+ valid_output_paths = inputs.map { |i| i.output_path }
114
+ output_dir.all_files.each do |output_file|
115
+ output_path = output_file.relative_path_from(output_dir)
116
+ unless valid_output_paths.member?(output_path)
117
+ logger.info("removing #{output_path}")
118
+ FileUtils.rm(output_file)
119
+ end
120
+ end
121
+ end
122
+
123
+ def generate_outputs
124
+ inputs.each do |input|
125
+ input.build
126
+ end
127
+ end
128
+
114
129
  def input_cache
115
130
  @input_cache ||= Hash.new do |h, cache_key|
116
131
  h[cache_key] = Input.new(self, cache_key.first)
data/lib/pith/project.rb~ CHANGED
@@ -1,6 +1,7 @@
1
- require "pathname"
2
1
  require "logger"
3
2
  require "pith/input"
3
+ require "pith/pathname_ext"
4
+ require "pith/reference_error"
4
5
  require "tilt"
5
6
 
6
7
  module Pith
@@ -13,8 +14,21 @@ module Pith
13
14
  end
14
15
  end
15
16
 
16
- attr_accessor :input_dir, :output_dir
17
+ attr_reader :input_dir
18
+
19
+ def input_dir=(dir)
20
+ @input_dir = Pathname(dir)
21
+ end
22
+
23
+ attr_reader :output_dir
24
+
25
+ def output_dir=(dir)
26
+ @output_dir = Pathname(dir)
27
+ end
17
28
 
29
+ attr_accessor :assume_content_negotiation
30
+ attr_accessor :assume_directory_index
31
+
18
32
  # Public: get inputs
19
33
  #
20
34
  # Returns Pith::Input objects representing the files in the input_dir.
@@ -23,8 +37,7 @@ module Pith
23
37
  # call #refresh to discard the cached data.
24
38
  #
25
39
  def inputs
26
- @inputs ||= Pathname.glob(input_dir + "**/*").map do |input_file|
27
- next if input_file.directory?
40
+ @inputs ||= input_dir.all_files.map do |input_file|
28
41
  path = input_file.relative_path_from(input_dir)
29
42
  find_or_create_input(path)
30
43
  end.compact
@@ -42,7 +55,7 @@ module Pith
42
55
  inputs.each do |input|
43
56
  return input if input.path == path || input.output_path == path
44
57
  end
45
- raise ReferenceError, "Can't find #{path.inspect}"
58
+ nil
46
59
  end
47
60
 
48
61
  # Public: build the project, generating output files.
@@ -50,9 +63,9 @@ module Pith
50
63
  def build
51
64
  refresh
52
65
  load_config
53
- inputs.each do |input|
54
- input.build
55
- end
66
+ remove_old_outputs
67
+ generate_outputs
68
+ output_dir.touch
56
69
  end
57
70
 
58
71
  # Public: discard cached data that is out-of-sync with the file-system.
@@ -62,6 +75,10 @@ module Pith
62
75
  @config_files = nil
63
76
  end
64
77
 
78
+ def last_built_at
79
+ output_dir.mtime
80
+ end
81
+
65
82
  def logger
66
83
  @logger ||= Logger.new(nil)
67
84
  end
@@ -78,7 +95,7 @@ module Pith
78
95
 
79
96
  def config_files
80
97
  @config_files ||= begin
81
- Pathname.glob("#{input_dir}/_pith/**")
98
+ input_dir.all_files("_pith/**")
82
99
  end.to_set
83
100
  end
84
101
 
@@ -92,6 +109,23 @@ module Pith
92
109
  end
93
110
  end
94
111
 
112
+ def remove_old_outputs
113
+ valid_output_paths = inputs.map { |i| i.output_path }
114
+ output_dir.all_files.each do |output_file|
115
+ output_path = output_file.relative_path_from(output_dir)
116
+ unless valid_output_paths.member?(output_path)
117
+ logger.info("removing #{output_path}")
118
+ FileUtils.rm(output_file)
119
+ end
120
+ end
121
+ end
122
+
123
+ def generate_outputs
124
+ inputs.each do |input|
125
+ input.build
126
+ end
127
+ end
128
+
95
129
  def input_cache
96
130
  @input_cache ||= Hash.new do |h, cache_key|
97
131
  h[cache_key] = Input.new(self, cache_key.first)
@@ -105,7 +139,5 @@ module Pith
105
139
  end
106
140
 
107
141
  end
108
-
109
- class ReferenceError < StandardError; end
110
142
 
111
143
  end
@@ -0,0 +1,5 @@
1
+ module Pith
2
+
3
+ class ReferenceError < StandardError; end
4
+
5
+ end
@@ -1,6 +1,7 @@
1
- require "set"
2
- require "pathname"
3
1
  require "ostruct"
2
+ require "pathname"
3
+ require "pith/reference_error"
4
+ require "set"
4
5
  require "tilt"
5
6
 
6
7
  module Pith
@@ -18,7 +19,7 @@ module Pith
18
19
 
19
20
  attr_reader :project
20
21
 
21
- def initial_input
22
+ def page
22
23
  @input_stack.first
23
24
  end
24
25
 
@@ -29,7 +30,7 @@ module Pith
29
30
  def render(input, locals = {}, &block)
30
31
  with_input(input) do
31
32
  result = input.render(self, locals, &block)
32
- layout_ref = current_input.meta["layout"]
33
+ layout_ref = input.meta["layout"]
33
34
  result = render_ref(layout_ref) { result } if layout_ref
34
35
  result
35
36
  end
@@ -51,42 +52,53 @@ module Pith
51
52
  @content_for_hash ||= Hash.new { "" }
52
53
  end
53
54
 
54
- def page
55
- @page ||= OpenStruct.new(initial_input.meta)
55
+ def relative_url_to(target_path)
56
+ url = target_path.relative_path_from(page.path.parent).to_str
57
+ url = url.sub(/index\.html$/, "") if project.assume_directory_index
58
+ url = url.sub(/\.html$/, "") if project.assume_content_negotiation
59
+ url = "./" if url.empty?
60
+ Pathname(url)
56
61
  end
57
62
 
58
63
  def href(target_ref)
59
- relative_path_to(resolve_path(target_ref))
64
+ relative_url_to(resolve_reference(target_ref))
60
65
  end
61
66
 
62
67
  def link(target_ref, label = nil)
63
- target_path = resolve_path(target_ref)
64
- label ||= begin
65
- find_input(target_path).title
66
- rescue Pith::ReferenceError
67
- "???"
68
+ target_path = resolve_reference(target_ref)
69
+ label ||= begin
70
+ target_input = input(target_path)
71
+ record_dependency_on(target_input.file)
72
+ target_input.title
73
+ rescue ReferenceError
74
+ "???"
68
75
  end
69
- %{<a href="#{relative_path_to(target_path)}">#{label}</a>}
76
+ url = relative_url_to(target_path)
77
+ %{<a href="#{url}">#{label}</a>}
70
78
  end
71
-
72
- private
73
79
 
74
- def relative_path_to(target_path)
75
- target_path.relative_path_from(initial_input.path.parent)
80
+ def record_dependency_on(file)
81
+ @dependencies << file
76
82
  end
77
83
 
78
- def resolve_path(ref)
79
- current_input.resolve_path(ref)
84
+ private
85
+
86
+ def resolve_reference(ref)
87
+ if ref.respond_to?(:output_path)
88
+ ref.output_path
89
+ else
90
+ current_input.resolve_path(ref)
91
+ end
80
92
  end
81
93
 
82
- def find_input(path)
94
+ def input(path)
83
95
  input = project.input(path)
84
- @dependencies << input.file if input
96
+ raise(ReferenceError, %{Can't find "#{path}"}) if input.nil?
85
97
  input
86
98
  end
87
99
 
88
100
  def with_input(input)
89
- @dependencies << input.file
101
+ record_dependency_on(input.file)
90
102
  @input_stack.push(input)
91
103
  begin
92
104
  yield
@@ -96,9 +108,8 @@ module Pith
96
108
  end
97
109
 
98
110
  def render_ref(template_ref, locals = {}, &block)
99
- template_path = resolve_path(template_ref)
100
- template = find_input(template_path)
101
- render(template, locals, &block)
111
+ template_input = input(resolve_reference(template_ref))
112
+ render(template_input, locals, &block)
102
113
  end
103
114
 
104
115
  end
data/lib/pith/server.rb~ CHANGED
@@ -7,31 +7,36 @@ module Pith
7
7
 
8
8
  def new(project)
9
9
  Rack::Builder.new do
10
- use Rack::CommonLogger, project.logger
10
+ use Rack::CommonLogger
11
11
  use Rack::ShowExceptions
12
12
  use Rack::Lint
13
- use Pith::Server::AutoBuild, project
14
13
  use Adsf::Rack::IndexFileFinder, :root => project.output_dir
14
+ use Pith::Server::DefaultToHtml, project.output_dir
15
15
  run Rack::Directory.new(project.output_dir)
16
16
  end
17
17
  end
18
18
 
19
- def run(project, options = {})
20
- Rack::Handler.get("thin").run(new(project), options)
21
- end
22
-
23
19
  extend self
24
-
25
- class AutoBuild
26
20
 
27
- def initialize(app, project)
28
- @app = app
29
- @project = project
21
+ class DefaultToHtml
22
+
23
+ def initialize(app, root)
24
+ @app = app
25
+ @root = root
30
26
  end
31
27
 
32
28
  def call(env)
33
- @project.build
29
+
30
+ path_info = ::Rack::Utils.unescape(env["PATH_INFO"])
31
+ file = "#{@root}#{path_info}"
32
+ unless File.exist?(file)
33
+ if File.exist?("#{file}.html")
34
+ env["PATH_INFO"] += ".html"
35
+ end
36
+ end
37
+
34
38
  @app.call(env)
39
+
35
40
  end
36
41
 
37
42
  end
data/lib/pith/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pith
2
- VERSION = "0.0.9".freeze
2
+ VERSION = "0.0.10".freeze
3
3
  end
data/lib/pith/version.rb~ CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pith
2
- VERSION = "0.0.6".freeze
2
+ VERSION = "0.0.9".freeze
3
3
  end
@@ -0,0 +1,32 @@
1
+ module Pith
2
+
3
+ class Watcher
4
+
5
+ DEFAULT_INTERVAL = 2
6
+
7
+ def initialize(project, options = {})
8
+ @project = project
9
+ @interval = DEFAULT_INTERVAL
10
+ options.each do |k,v|
11
+ send("#{k}=", v)
12
+ end
13
+ end
14
+
15
+ attr_accessor :project
16
+ attr_accessor :interval
17
+
18
+ def call
19
+ loop do
20
+ begin
21
+ project.build
22
+ rescue Exception => e
23
+ $stderr.puts "ERROR: #{e}"
24
+ e.backtrace.each { |line| $stderr.puts line }
25
+ end
26
+ sleep(interval)
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require "pith/input/abstract"
3
+ require "pith/project"
4
+
5
+ describe Pith::Input::Template do
6
+
7
+ before do
8
+ $input_dir.mkpath
9
+ @project = Pith::Project.new(:input_dir => $input_dir)
10
+ end
11
+
12
+ def make_input(path)
13
+ input_file = $input_dir + path
14
+ input_file.parent.mkpath
15
+ input_file.open("w") do |io|
16
+ yield io if block_given?
17
+ end
18
+ @project.input(path)
19
+ end
20
+
21
+ describe ".can_handle?" do
22
+
23
+ it "returns true for template paths" do
24
+ Pith::Input::Template.can_handle?("xyz.html.haml").should be_true
25
+ Pith::Input::Template.can_handle?("xyz.html.md").should be_true
26
+ end
27
+
28
+ it "handles directories" do
29
+ Pith::Input::Template.can_handle?("dir/xyz.haml").should be_true
30
+ end
31
+
32
+ it "accepts Pathname objects" do
33
+ Pith::Input::Template.can_handle?(Pathname("xyz.html.haml")).should be_true
34
+ end
35
+
36
+ it "returns false for non-template paths" do
37
+ Pith::Input::Template.can_handle?("foo.html").should be_false
38
+ Pith::Input::Template.can_handle?("foo").should be_false
39
+ end
40
+
41
+ end
42
+
43
+ describe "#title" do
44
+
45
+ it "is based on last component of filename" do
46
+ @input = make_input("dir/some_page.html.haml")
47
+ @input.title.should == "Some page"
48
+ end
49
+
50
+ it "can be over-ridden in metadata" do
51
+ @input = make_input("dir/some_page.html.haml") do |i|
52
+ i.puts "---"
53
+ i.puts "title: Blah blah"
54
+ i.puts "..."
55
+ end
56
+ @input.title.should == "Blah blah"
57
+ end
58
+
59
+ end
60
+
61
+ describe "#output_path" do
62
+
63
+ it "excludes the template-type extension" do
64
+ @input = make_input("dir/some_page.html.haml")
65
+ @input.output_path.should == Pathname("dir/some_page.html")
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require "pith/plugins/publication"
3
+
4
+ describe Pith::Plugins::Publication::TemplateMethods do
5
+
6
+ before do
7
+ @template = OpenStruct.new(:meta => {})
8
+ @template.extend(Pith::Plugins::Publication::TemplateMethods)
9
+ end
10
+
11
+ describe "#published_at" do
12
+
13
+ it "defaults to nil" do
14
+ @template.published_at.should be_nil
15
+ end
16
+
17
+ it "is derived by parsing the 'published' meta-field" do
18
+ @template.meta["published"] = "25 Dec 1999 22:30"
19
+ @template.published_at.should == Time.local(1999, 12, 25, 22, 30)
20
+ end
21
+
22
+ end
23
+
24
+ describe "#updated_at" do
25
+
26
+ it "defaults to #published_at" do
27
+ @template.meta["published"] = "25 Dec 1999 22:30"
28
+ @template.updated_at.should == Time.local(1999, 12, 25, 22, 30)
29
+ end
30
+
31
+ it "can be overridden with an 'updated' meta-field" do
32
+ @template.meta["published"] = "25 Dec 1999 22:30"
33
+ @template.meta["published"] = "1 Jan 2000 03:00"
34
+ @template.updated_at.should == Time.local(2000, 1, 1, 3, 0)
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -17,9 +17,9 @@ describe Pith::Project do
17
17
  @input_file.touch
18
18
  end
19
19
 
20
- it "constructs an Verbatim object" do
20
+ it "constructs an Resource object" do
21
21
  @input = @project.input("input.txt")
22
- @input.should be_kind_of(Pith::Input::Verbatim)
22
+ @input.should be_kind_of(Pith::Input::Resource)
23
23
  @input.file.should == @input_file
24
24
  end
25
25
 
@@ -55,10 +55,8 @@ describe Pith::Project do
55
55
 
56
56
  describe "(with an invalid input path)" do
57
57
 
58
- it "complains" do
59
- lambda do
60
- @project.input("bogus.path")
61
- end.should raise_error(Pith::ReferenceError)
58
+ it "returns nil" do
59
+ @project.input("bogus.path").should be_nil
62
60
  end
63
61
 
64
62
  end
@@ -112,4 +110,28 @@ describe Pith::Project do
112
110
 
113
111
  end
114
112
 
113
+ describe "when an input file is removed" do
114
+
115
+ before do
116
+ @input_file = $input_dir + "input.html.haml"
117
+ @input_file.touch(Time.now - 10)
118
+ end
119
+
120
+ describe "a second call to #input" do
121
+ it "returns nil" do
122
+
123
+ first_time = @project.input("input.html.haml")
124
+ first_time.should_not be_nil
125
+
126
+ FileUtils.rm(@input_file)
127
+
128
+ @project.refresh
129
+ second_time = @project.input("input.html.haml")
130
+ second_time.should be_nil
131
+
132
+ end
133
+ end
134
+
135
+ end
136
+
115
137
  end
data/spec/spec_helper.rb~ CHANGED
@@ -1,16 +1,6 @@
1
1
  require "rubygems"
2
2
 
3
3
  require "fileutils"
4
- require "pathname"
5
-
6
- class Pathname
7
-
8
- def touch(mtime = nil)
9
- FileUtils.touch(self.to_s)
10
- utime(mtime, mtime) if mtime
11
- end
12
-
13
- end
14
4
 
15
5
  $project_dir = Pathname(__FILE__).expand_path.parent.parent
16
6
  $tmp_dir = $project_dir + "tmp"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pith
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 9
10
- version: 0.0.9
9
+ - 10
10
+ version: 0.0.10
11
11
  platform: ruby
12
12
  authors:
13
13
  - Mike Williams
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-19 00:00:00 +10:00
18
+ date: 2010-10-01 00:00:00 +10:00
19
19
  default_executable: pith
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -112,7 +112,9 @@ files:
112
112
  - lib/pith/console_logger.rb~
113
113
  - lib/pith/input/abstract.rb
114
114
  - lib/pith/input/abstract.rb~
115
+ - lib/pith/input/base.rb~
115
116
  - lib/pith/input/resource.rb
117
+ - lib/pith/input/resource.rb~
116
118
  - lib/pith/input/template.rb
117
119
  - lib/pith/input/template.rb~
118
120
  - lib/pith/input/verbatim.rb~
@@ -120,12 +122,17 @@ files:
120
122
  - lib/pith/input.rb~
121
123
  - lib/pith/metadata.rb~
122
124
  - lib/pith/pathname_ext.rb
125
+ - lib/pith/pathname_ext.rb~
123
126
  - lib/pith/plugins/publication/input.rb
127
+ - lib/pith/plugins/publication/input.rb~
124
128
  - lib/pith/plugins/publication/project.rb
129
+ - lib/pith/plugins/publication/project.rb~
125
130
  - lib/pith/plugins/publication.rb
131
+ - lib/pith/plugins/publication.rb~
126
132
  - lib/pith/project.rb
127
133
  - lib/pith/project.rb~
128
134
  - lib/pith/reference_error.rb
135
+ - lib/pith/reference_error.rb~
129
136
  - lib/pith/render_context.rb
130
137
  - lib/pith/render_context.rb~
131
138
  - lib/pith/server.rb
@@ -133,6 +140,7 @@ files:
133
140
  - lib/pith/version.rb
134
141
  - lib/pith/version.rb~
135
142
  - lib/pith/watcher.rb
143
+ - lib/pith/watcher.rb~
136
144
  - lib/pith.rb
137
145
  - lib/pith.rb~
138
146
  - sample/_layouts/standard.haml
@@ -146,12 +154,15 @@ files:
146
154
  - Rakefile
147
155
  - spec/pith/input/abstract_spec.rb~
148
156
  - spec/pith/input/template_spec.rb
157
+ - spec/pith/input/template_spec.rb~
149
158
  - spec/pith/metadata_spec.rb~
150
159
  - spec/pith/plugins/publication_spec.rb
160
+ - spec/pith/plugins/publication_spec.rb~
151
161
  - spec/pith/project_spec.rb
152
162
  - spec/pith/project_spec.rb~
153
163
  - spec/spec_helper.rb
154
164
  - spec/spec_helper.rb~
165
+ - features/cleanup.feature
155
166
  - features/content_for.feature
156
167
  - features/content_for.feature~
157
168
  - features/haml.feature
@@ -222,12 +233,15 @@ test_files:
222
233
  - Rakefile
223
234
  - spec/pith/input/abstract_spec.rb~
224
235
  - spec/pith/input/template_spec.rb
236
+ - spec/pith/input/template_spec.rb~
225
237
  - spec/pith/metadata_spec.rb~
226
238
  - spec/pith/plugins/publication_spec.rb
239
+ - spec/pith/plugins/publication_spec.rb~
227
240
  - spec/pith/project_spec.rb
228
241
  - spec/pith/project_spec.rb~
229
242
  - spec/spec_helper.rb
230
243
  - spec/spec_helper.rb~
244
+ - features/cleanup.feature
231
245
  - features/content_for.feature
232
246
  - features/content_for.feature~
233
247
  - features/haml.feature