pith 0.0.9 → 0.0.10

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/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