pith 0.3.0 → 0.3.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 CHANGED
@@ -69,10 +69,7 @@ class PithCommand < Clamp::Command
69
69
  signal_usage_error %(No "#{pith_dir}" directory ... this doesn't look right!)
70
70
  end
71
71
  puts %{Generating to "#{output_dir}"}
72
- @project = Pith::Project.new(
73
- :input_dir => input_dir, :output_dir => output_dir,
74
- :logger => Pith::ConsoleLogger.new
75
- )
72
+ @project = Pith::Project.new(input_dir, output_dir, :logger => Pith::ConsoleLogger.new)
76
73
  end
77
74
  end
78
75
 
@@ -22,3 +22,51 @@ Scenario: don't alter an input, and the output is untouched
22
22
  When I rebuild the site
23
23
 
24
24
  Then output file "page.html" should not be re-generated
25
+
26
+ Scenario: alter a dependency
27
+
28
+ Given input file "page.html.haml" contains "= include('_partials/foo.haml')"
29
+ And input file "_partials/foo.haml" contains "bananas"
30
+ And the site is up-to-date
31
+
32
+ When I change input file "_partials/foo.haml" to contain "New content"
33
+ And I rebuild the site
34
+
35
+ Then output file "page.html" should be re-generated
36
+ And output file "page.html" should contain "New content"
37
+
38
+ Scenario: delete a dependency
39
+
40
+ Given input file "fruits.html.haml" contains
41
+ """
42
+ - project.inputs.map(&:path).map(&:to_s).grep(/^_fruit/).sort.each do |input_name|
43
+ %p= include(input_name)
44
+ """
45
+
46
+ And input file "_fruit/a.html.haml" contains
47
+ """
48
+ apple
49
+ """
50
+
51
+ And input file "_fruit/b.html.haml" contains
52
+ """
53
+ banana
54
+ """
55
+
56
+ And the site is up-to-date
57
+
58
+ Then output file "fruits.html" should contain
59
+ """
60
+ <p>apple</p>
61
+ <p>banana</p>
62
+ """
63
+
64
+ When I remove input file "_fruit/a.html.haml"
65
+ And I rebuild the site
66
+
67
+ Then output file "fruits.html" should be re-generated
68
+ And output file "fruits.html" should contain
69
+ """
70
+ <p>banana</p>
71
+ """
72
+
@@ -3,11 +3,6 @@ Feature: linking between files
3
3
  I want to be able to generate relative reference to other pages
4
4
  So that the generated site is re-locateable
5
5
 
6
- Background:
7
-
8
- Given the "assume_content_negotiation" flag is disabled
9
- And the "assume_directory_index" flag is disabled
10
-
11
6
  Scenario: link from one top-level page to another
12
7
 
13
8
  Given input file "index.html.haml" contains
@@ -148,7 +143,11 @@ Scenario: link to a missing resource
148
143
 
149
144
  Scenario: assume content negotiation
150
145
 
151
- Given the "assume_content_negotiation" flag is enabled
146
+ Given the config file contains
147
+ """
148
+ project.assume_content_negotiation = true
149
+ """
150
+
152
151
  And input file "index.html.haml" contains
153
152
  """
154
153
  = link("page.html", "Page")
@@ -162,7 +161,10 @@ Scenario: assume content negotiation
162
161
 
163
162
  Scenario: link to an index page
164
163
 
165
- Given the "assume_directory_index" flag is enabled
164
+ Given the config file contains
165
+ """
166
+ project.assume_directory_index = true
167
+ """
166
168
 
167
169
  And input file "page.html.haml" contains
168
170
  """
@@ -177,7 +179,10 @@ Scenario: link to an index page
177
179
 
178
180
  Scenario: link to an index page in the same directory
179
181
 
180
- Given the "assume_directory_index" flag is enabled
182
+ Given the config file contains
183
+ """
184
+ project.assume_directory_index = true
185
+ """
181
186
 
182
187
  And input file "page.html.haml" contains
183
188
  """
@@ -14,12 +14,8 @@ 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)
17
+ Given /^the config file contains$/ do |content|
18
+ @inputs.write("_pith/config.rb", content, :mtime => (Time.now - 5))
23
19
  end
24
20
 
25
21
  When /^I change input file "([^\"]*)" to contain "([^\"]*)"$/ do |path, content|
@@ -1,7 +1,7 @@
1
1
  require "pathname"
2
2
 
3
3
  class DirHash
4
-
4
+
5
5
  def initialize(dir)
6
6
  @dir = Pathname(dir)
7
7
  end
@@ -34,7 +34,7 @@ class DirHash
34
34
  file_path.utime(timestamp, timestamp)
35
35
  end
36
36
  end
37
-
37
+
38
38
  end
39
39
 
40
40
  class InternalLogger
@@ -42,13 +42,13 @@ class InternalLogger
42
42
  def initialize
43
43
  @messages = []
44
44
  end
45
-
45
+
46
46
  attr_reader :messages
47
-
47
+
48
48
  def clear
49
49
  @messages.clear
50
50
  end
51
-
51
+
52
52
  def info(message, &block)
53
53
  message ||= block.call
54
54
  write(message)
@@ -56,11 +56,11 @@ class InternalLogger
56
56
 
57
57
  alias :warn :info
58
58
  alias :debug :info
59
-
59
+
60
60
  def write(message)
61
61
  @messages << message
62
62
  end
63
-
63
+
64
64
  end
65
65
 
66
66
  $project_dir = Pathname(__FILE__).expand_path.parent.parent.parent
@@ -79,8 +79,7 @@ Before do
79
79
  dir.rmtree if dir.exist?
80
80
  dir.mkpath
81
81
  end
82
- @project = Pith::Project.new(:input_dir => $input_dir, :output_dir => $output_dir)
83
- @project.logger = InternalLogger.new
82
+ @project = Pith::Project.new($input_dir, $output_dir, :logger => InternalLogger.new)
84
83
  @inputs = DirHash.new($input_dir)
85
84
  @outputs = DirHash.new($output_dir)
86
85
  end
@@ -0,0 +1,42 @@
1
+ module Pith
2
+
3
+ class Config
4
+
5
+ DEFAULT_IGNORE_PATTERNS = ["_*", ".git", ".gitignore", ".svn", ".sass-cache", "*~", "*.sw[op]"].to_set.freeze
6
+
7
+ def initialize
8
+ @ignore_patterns = DEFAULT_IGNORE_PATTERNS.dup
9
+ @helper_module = Module.new
10
+ end
11
+
12
+ attr_accessor :assume_content_negotiation
13
+ attr_accessor :assume_directory_index
14
+
15
+ attr_reader :ignore_patterns
16
+
17
+ def ignore(pattern)
18
+ ignore_patterns << pattern
19
+ end
20
+
21
+ attr_reader :helper_module
22
+
23
+ def helpers(&block)
24
+ helper_module.module_eval(&block)
25
+ end
26
+
27
+ class << self
28
+
29
+ def load(config_file)
30
+ config = self.new
31
+ if config_file.exist?
32
+ project = config # for backward compatibility
33
+ eval(config_file.read, binding, config_file.to_s, 1)
34
+ end
35
+ config
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,36 @@
1
+ require "pith/config"
2
+ require "pith/observable"
3
+
4
+ module Pith
5
+
6
+ class ConfigProvider
7
+
8
+ include Pith::Observable
9
+
10
+ def initialize(project)
11
+ @project = project
12
+ @last_load_mtime = :never
13
+ sync
14
+ end
15
+
16
+ attr_reader :config
17
+
18
+ def sync
19
+ config_mtime = config_file.mtime rescue nil
20
+ unless config_mtime == @last_load_mtime
21
+ @last_load_mtime = config_mtime
22
+ @project.logger.debug "loading config"
23
+ @config = Pith::Config.load(config_file)
24
+ notify_observers
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def config_file
31
+ @project.input_dir + "_pith/config.rb"
32
+ end
33
+
34
+ end
35
+
36
+ end
data/lib/pith/input.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "fileutils"
2
2
  require "pathname"
3
- require "observer"
3
+ require "pith/observable"
4
4
  require "pith/output"
5
5
  require "tilt"
6
6
  require "yaml"
@@ -9,13 +9,12 @@ module Pith
9
9
 
10
10
  class Input
11
11
 
12
- include Observable
12
+ include Pith::Observable
13
13
 
14
14
  def initialize(project, path)
15
15
  @project = project
16
16
  @path = path
17
17
  determine_pipeline
18
- when_created
19
18
  end
20
19
 
21
20
  attr_reader :project, :path
@@ -37,7 +36,7 @@ module Pith
37
36
  #
38
37
  def ignorable?
39
38
  @ignorable ||= path.each_filename do |path_component|
40
- project.ignore_patterns.each do |pattern|
39
+ project.config.ignore_patterns.each do |pattern|
41
40
  return true if File.fnmatch(pattern, path_component)
42
41
  end
43
42
  end
@@ -126,37 +125,20 @@ module Pith
126
125
  end
127
126
  end
128
127
 
129
- # Synchronise the state of the Input with the filesystem
130
- #
131
- # Returns true if the file still exists.
132
- #
133
- def sync
134
- mtime = file.mtime
135
- if mtime.to_i > @last_mtime.to_i
136
- @last_mtime = mtime
137
- when_changed
138
- end
139
- true
140
- rescue Errno::ENOENT => e
141
- when_deleted
142
- nil
143
- end
144
-
145
- def when_created
128
+ def when_added
146
129
  log_lifecycle "+"
147
- @last_mtime = file.mtime
148
130
  end
149
131
 
150
- def when_changed
132
+ def when_modified
151
133
  log_lifecycle "~"
152
134
  unload if loaded?
153
- changed(true)
154
135
  notify_observers
155
136
  end
156
137
 
157
- def when_deleted
138
+ def when_removed
158
139
  log_lifecycle "X"
159
140
  output.delete if output
141
+ notify_observers
160
142
  end
161
143
 
162
144
  private
@@ -0,0 +1,27 @@
1
+ module Pith
2
+
3
+ module Observable
4
+
5
+ def add_observer(observer, signal = :update)
6
+ observer_map[observer] = signal
7
+ end
8
+
9
+ def remove_observer(observer)
10
+ observer_map.delete(observer)
11
+ end
12
+
13
+ def notify_observers
14
+ observer_map.each do |observer, signal|
15
+ observer.send(signal)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def observer_map
22
+ @observer_map ||= {}
23
+ end
24
+
25
+ end
26
+
27
+ end
data/lib/pith/output.rb CHANGED
@@ -45,32 +45,30 @@ module Pith
45
45
  def record_dependency_on(*inputs)
46
46
  inputs.each do |input|
47
47
  @dependencies << input
48
- input.add_observer(self)
48
+ input.add_observer(self, :invalidate)
49
49
  end
50
50
  end
51
51
 
52
52
  def delete
53
53
  invalidate
54
- logger.info("--X #{path}")
55
- FileUtils.rm_f(file)
56
- end
57
-
58
- def update # called by dependencies that change
59
- invalidate
54
+ if file.exist?
55
+ logger.info("--X #{path}")
56
+ FileUtils.rm_f(file)
57
+ end
60
58
  end
61
59
 
62
- private
63
-
64
60
  def invalidate
65
61
  if @generated
66
62
  @dependencies.each do |d|
67
- d.delete_observer(self)
63
+ d.remove_observer(self)
68
64
  end
69
65
  @dependencies = nil
70
66
  @generated = nil
71
67
  end
72
68
  end
73
69
 
70
+ private
71
+
74
72
  def copy_resource
75
73
  FileUtils.copy(input.file, file)
76
74
  record_dependency_on(input)
@@ -89,7 +87,7 @@ module Pith
89
87
  out.puts exception_summary(e)
90
88
  end
91
89
  end
92
- record_dependency_on(*project.config_inputs)
90
+ record_dependency_on(project.config_provider)
93
91
  end
94
92
 
95
93
  def logger
@@ -7,12 +7,6 @@ class Pathname
7
7
  utime(mtime, mtime) if mtime
8
8
  end
9
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
10
  def in?(dir)
17
11
  prefix = "#{dir}/"
18
12
  self.to_s[0,prefix.length] == prefix
data/lib/pith/project.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "logger"
2
+ require "pith/config_provider"
2
3
  require "pith/input"
3
4
  require "pith/pathname_ext"
4
5
  require "pith/reference_error"
@@ -9,37 +10,20 @@ module Pith
9
10
 
10
11
  class Project
11
12
 
12
- DEFAULT_IGNORE_PATTERNS = ["_*", ".git", ".gitignore", ".svn", ".sass-cache", "*~", "*.sw[op]"].to_set.freeze
13
-
14
- def initialize(attributes = {})
15
- @ignore_patterns = DEFAULT_IGNORE_PATTERNS.dup
13
+ def initialize(input_dir, output_dir = nil, attributes = {})
14
+ @input_dir = Pathname(input_dir)
15
+ @output_dir = output_dir ? Pathname(output_dir) : (@input_dir + "_out")
16
+ @input_map = {}
17
+ @output_map = {}
16
18
  attributes.each do |k,v|
17
19
  send("#{k}=", v)
18
20
  end
21
+ FileUtils.rm_rf(output_dir.to_s)
19
22
  end
20
23
 
21
24
  attr_reader :input_dir
22
- attr_reader :ignore_patterns
23
-
24
- def input_dir=(dir)
25
- @input_dir = Pathname(dir)
26
- end
27
-
28
25
  attr_reader :output_dir
29
26
 
30
- def output_dir=(dir)
31
- @output_dir = Pathname(dir)
32
- FileUtils.rm_rf(@output_dir)
33
- @output_dir.mkpath
34
- end
35
-
36
- attr_accessor :assume_content_negotiation
37
- attr_accessor :assume_directory_index
38
-
39
- def ignore(pattern)
40
- ignore_patterns << pattern
41
- end
42
-
43
27
  # Public: get inputs
44
28
  #
45
29
  # Returns Pith::Input objects representing the files in the input_dir.
@@ -90,12 +74,8 @@ module Pith
90
74
  # Public: re-sync with the file-system.
91
75
  #
92
76
  def sync
93
- @input_map ||= {}
94
- @output_map ||= {}
95
- @config_inputs = nil
96
- load_config
97
- validate_known_inputs
98
- find_new_inputs
77
+ config_provider.sync
78
+ sync_input_files
99
79
  end
100
80
 
101
81
  def sync_every(period)
@@ -111,7 +91,7 @@ module Pith
111
91
  #
112
92
  # Returns true if any errors were encountered during the last build.
113
93
  def has_errors?
114
- inputs.map(&:output).compact.any?(&:error)
94
+ outputs.any?(&:error)
115
95
  end
116
96
 
117
97
  def last_built_at
@@ -122,56 +102,59 @@ module Pith
122
102
  @logger ||= Logger.new(nil)
123
103
  end
124
104
 
125
- attr_writer :logger
126
-
127
- def helpers(&block)
128
- helper_module.module_eval(&block)
105
+ def config_provider
106
+ @config_provider ||= Pith::ConfigProvider.new(self)
129
107
  end
130
108
 
131
- def helper_module
132
- @helper_module ||= Module.new
133
- end
134
-
135
- def config_inputs
136
- @config_inputs ||= inputs.select do |input|
137
- input.path.to_s[0,6] == "_pith/"
138
- end
109
+ def config
110
+ config_provider.config
139
111
  end
140
112
 
141
113
  private
142
114
 
143
- def load_config
144
- config_file = input_dir + "_pith/config.rb"
145
- project = self
146
- if config_file.exist?
147
- eval(config_file.read, binding, config_file.to_s, 1)
115
+ attr_writer :logger
116
+
117
+ def sync_input_files
118
+ @mtimes ||= {}
119
+ removed_file_paths = @mtimes.keys
120
+ Pathname.glob(input_dir + "**/*", File::FNM_DOTMATCH) do |file|
121
+ next unless file.file?
122
+ next if file.in?(output_dir)
123
+ mtime = file.mtime
124
+ path = file.relative_path_from(input_dir)
125
+ if @mtimes.has_key?(path)
126
+ if @mtimes[path] < mtime
127
+ when_file_modified(path)
128
+ end
129
+ else
130
+ when_file_added(path)
131
+ end
132
+ @mtimes[path] = mtime
133
+ removed_file_paths.delete(path)
134
+ end
135
+ removed_file_paths.each do |path|
136
+ when_file_removed(path)
148
137
  end
149
138
  end
150
139
 
151
- def load_input(path)
140
+ def when_file_added(path)
152
141
  i = Input.new(self, path)
142
+ i.when_added
153
143
  @input_map[path] = i
154
144
  if o = i.output
155
145
  @output_map[o.path] = o
156
146
  end
157
- i
158
147
  end
159
148
 
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
167
- end
149
+ def when_file_modified(path)
150
+ @input_map[path].when_modified
168
151
  end
169
152
 
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)
153
+ def when_file_removed(path)
154
+ i = @input_map.delete(path)
155
+ i.when_removed
156
+ if o = i.output
157
+ @output_map.delete(o.path)
175
158
  end
176
159
  end
177
160
 
@@ -15,7 +15,7 @@ module Pith
15
15
  @page = @output.input
16
16
  @project = @page.project
17
17
  @input_stack = []
18
- self.extend(project.helper_module)
18
+ self.extend(project.config.helper_module)
19
19
  end
20
20
 
21
21
  attr_reader :output
@@ -55,8 +55,8 @@ module Pith
55
55
 
56
56
  def relative_url_to(target_path)
57
57
  url = target_path.relative_path_from(page.path.parent).to_s
58
- url = url.sub(/index\.html$/, "") if project.assume_directory_index
59
- url = url.sub(/\.html$/, "") if project.assume_content_negotiation
58
+ url = url.sub(/index\.html$/, "") if project.config.assume_directory_index
59
+ url = url.sub(/\.html$/, "") if project.config.assume_content_negotiation
60
60
  url = "./" if url.empty?
61
61
  Pathname(url)
62
62
  end
data/lib/pith/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pith
2
- VERSION = "0.3.0".freeze
2
+ VERSION = "0.3.1".freeze
3
3
  end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'pith/config'
3
+
4
+ describe Pith::Config do
5
+
6
+ let(:config) { Pith::Config.new }
7
+
8
+ describe "#ignore_patterns" do
9
+
10
+ it "is a set" do
11
+ config.ignore_patterns.should be_kind_of(Set)
12
+ end
13
+
14
+ it "includes some sensible defaults" do
15
+ config.ignore_patterns.should include("_*")
16
+ config.ignore_patterns.should include(".git")
17
+ config.ignore_patterns.should include(".svn")
18
+ end
19
+
20
+ end
21
+
22
+ describe "#ignore" do
23
+
24
+ it "adds to ignore_patterns" do
25
+ config.ignore("foo")
26
+ config.ignore_patterns
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -5,7 +5,7 @@ require "pith/project"
5
5
  describe Pith::Input do
6
6
 
7
7
  before do
8
- @project = Pith::Project.new(:input_dir => $input_dir)
8
+ @project = Pith::Project.new($input_dir)
9
9
  end
10
10
 
11
11
  def make_input(path)
@@ -4,7 +4,7 @@ require 'pith/project'
4
4
  describe Pith::Project do
5
5
 
6
6
  before do
7
- @project = Pith::Project.new(:input_dir => $input_dir, :output_dir => $output_dir)
7
+ @project = Pith::Project.new($input_dir, $output_dir)
8
8
  end
9
9
 
10
10
  describe "#build" do
@@ -110,27 +110,4 @@ describe Pith::Project do
110
110
 
111
111
  end
112
112
 
113
- describe "#ignore_patterns" do
114
-
115
- it "is a set" do
116
- @project.ignore_patterns.should be_kind_of(Set)
117
- end
118
-
119
- it "includes some sensible defaults" do
120
- @project.ignore_patterns.should include("_*")
121
- @project.ignore_patterns.should include(".git")
122
- @project.ignore_patterns.should include(".svn")
123
- end
124
-
125
- end
126
-
127
- describe "#ignore" do
128
-
129
- it "adds to ignore_patterns" do
130
- @project.ignore("foo")
131
- @project.ignore_patterns
132
- end
133
-
134
- end
135
-
136
113
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pith
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-29 00:00:00.000000000 Z
12
+ date: 2012-05-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: tilt
@@ -85,11 +85,14 @@ executables:
85
85
  extensions: []
86
86
  extra_rdoc_files: []
87
87
  files:
88
+ - lib/pith/config.rb
89
+ - lib/pith/config_provider.rb
88
90
  - lib/pith/console_logger.rb
89
91
  - lib/pith/console_logger.rb~
90
92
  - lib/pith/input.rb
91
93
  - lib/pith/input.rb~
92
94
  - lib/pith/metadata.rb~
95
+ - lib/pith/observable.rb
93
96
  - lib/pith/output.rb
94
97
  - lib/pith/pathname_ext.rb
95
98
  - lib/pith/pathname_ext.rb~
@@ -120,6 +123,7 @@ files:
120
123
  - sample/stylesheets/app.css.sass
121
124
  - README.markdown
122
125
  - Rakefile
126
+ - spec/pith/config_spec.rb
123
127
  - spec/pith/input_spec.rb
124
128
  - spec/pith/pathname_ext_spec.rb
125
129
  - spec/pith/plugins/publication_spec.rb
@@ -176,7 +180,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
180
  version: '0'
177
181
  segments:
178
182
  - 0
179
- hash: -22568693852038165
183
+ hash: 1629984080237938528
180
184
  required_rubygems_version: !ruby/object:Gem::Requirement
181
185
  none: false
182
186
  requirements:
@@ -185,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
189
  version: '0'
186
190
  segments:
187
191
  - 0
188
- hash: -22568693852038165
192
+ hash: 1629984080237938528
189
193
  requirements: []
190
194
  rubyforge_project:
191
195
  rubygems_version: 1.8.21
@@ -194,6 +198,7 @@ specification_version: 3
194
198
  summary: A static website generator
195
199
  test_files:
196
200
  - Rakefile
201
+ - spec/pith/config_spec.rb
197
202
  - spec/pith/input_spec.rb
198
203
  - spec/pith/pathname_ext_spec.rb
199
204
  - spec/pith/plugins/publication_spec.rb