hx 0.3.2

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.
@@ -0,0 +1,161 @@
1
+ # hx/commandline - A very small website generator; commandline interface
2
+ #
3
+ # Copyright (c) 2009 MenTaLguY <mental@rydia.net>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require 'hx'
25
+ require 'ostruct'
26
+ require 'optparse'
27
+ require 'pathname'
28
+ require 'tempfile'
29
+
30
+ module Hx
31
+ module Commandline
32
+
33
+ DEFAULT_CONFIG_FILENAME = "hx-config.yaml"
34
+
35
+ def self.main(*args)
36
+ options = OpenStruct.new
37
+ options.config_file = nil
38
+
39
+ OptionParser.new do |opts|
40
+ opts.banner = "Usage: hx [--config CONFIG_FILE]"
41
+
42
+ opts.on("-c", "--config CONFIG_FILE",
43
+ "Use CONFIG_FILE instead of searching for " +
44
+ DEFAULT_CONFIG_FILENAME) \
45
+ do |config_file|
46
+ options.config_file = Pathname.new(config_file)
47
+ end
48
+
49
+ opts.on_tail("-h", "--help", "Show this usage information") do
50
+ puts opts
51
+ return
52
+ end
53
+
54
+ opts.parse!(args)
55
+ end
56
+
57
+ options.config_file ||= ENV['HX_CONFIG']
58
+
59
+ unless options.config_file
60
+ Pathname.getwd.ascend do |ancestor|
61
+ filename = ancestor + DEFAULT_CONFIG_FILENAME
62
+ if filename.exist?
63
+ options.config_file = filename
64
+ break
65
+ end
66
+ end
67
+ unless options.config_file
68
+ raise RuntimeError, "No #{DEFAULT_CONFIG_FILENAME} found"
69
+ end
70
+ end
71
+
72
+ site = nil
73
+ options.config_file.open("r") do |stream|
74
+ site = Hx::Site.load(stream, options.config_file)
75
+ end
76
+
77
+ subcommand = args.shift || "regen"
78
+ method_name = "cmd_#{subcommand}".intern
79
+ begin
80
+ m = method(method_name)
81
+ rescue NameError
82
+ raise ArgumentError, "Unrecognized subcommand: #{subcommand}"
83
+ end
84
+ m.call(site, *args)
85
+ end
86
+
87
+ def self.cmd_regen(site)
88
+ output_dir = Hx.get_pathname(site.options, :output_dir)
89
+ builder = Hx::FileBuilder.new(output_dir.to_s)
90
+ site.each_entry do |path, entry|
91
+ puts "===> #{path}"
92
+ builder.build_file(path, entry)
93
+ end
94
+ end
95
+
96
+ def self.cmd_edit(site, pathspec)
97
+ do_edit(site, pathspec, false)
98
+ end
99
+
100
+ def self.cmd_create(site, pathspec)
101
+ do_edit(site, pathspec, true)
102
+ end
103
+
104
+ def self.do_edit(site, pathspec, create)
105
+ source_name, path = pathspec.split(':', 2)
106
+ path, source_name = source_name, path unless source_name
107
+
108
+ if create
109
+ prototype = {
110
+ 'title' => Hx.make_default_title(site.options, path),
111
+ 'author' => Hx.get_default_author(site.options),
112
+ 'content' => ""
113
+ }
114
+ else
115
+ prototype = nil
116
+ end
117
+
118
+ if source_name
119
+ source = site.sources[source_name]
120
+ raise ArgumentError, "No such source #{source_name}" unless source
121
+ else
122
+ source = site
123
+ end
124
+
125
+ catch(:unchanged) do
126
+ begin
127
+ tempfile = Tempfile.new('hx-entry')
128
+ original_text = nil
129
+ loop do
130
+ begin
131
+ source.edit_entry(path, prototype) do |text|
132
+ unless original_text
133
+ File.open(tempfile.path, 'w') { |s| s << text }
134
+ original_text = text
135
+ end
136
+ # TODO: deal with conflict if text != original_text
137
+ editor = ENV['EDITOR'] || 'vi'
138
+ system(editor, tempfile.path)
139
+ new_text = File.open(tempfile.path, 'r') { |s| s.read }
140
+ throw(:unchanged) if new_text == text
141
+ new_text
142
+ end
143
+ break
144
+ rescue Exception => e
145
+ $stderr.puts e
146
+ $stderr.print "Edit failed; retry? [Yn] "
147
+ $stderr.flush
148
+ response = $stdin.gets.strip
149
+ unless response =~ /^y/i
150
+ raise e
151
+ end
152
+ end
153
+ end
154
+ ensure
155
+ tempfile.unlink
156
+ end
157
+ end
158
+ end
159
+
160
+ end
161
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'hx'
4
+ require 'set'
5
+
6
+ describe Hx::Cache do
7
+ before(:each) do
8
+ @source = FakeSource.new
9
+ @source.add_entry('foo', 'BLAH')
10
+ @source.add_entry('bar', 'EEP')
11
+ @cache = Hx::Cache.new(@source)
12
+ end
13
+
14
+ it "should return itself from each_entry" do
15
+ @cache.each_entry {}.should == @cache
16
+ end
17
+
18
+ it "enumerates the same entries from the source" do
19
+ @cache.each_entry do |path, entry|
20
+ entry.should == @source.get_entry(path)
21
+ end
22
+ end
23
+
24
+ it "only reads the source once" do
25
+ @cache.each_entry {}
26
+ def @source.each_entry
27
+ raise RuntimeError, "should not be called"
28
+ end
29
+ @cache.each_entry {}
30
+ end
31
+ end
File without changes
File without changes
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'hx'
4
+
5
+ describe Hx::NullSource do
6
+ before(:each) do
7
+ @null_source = Hx::NullSource.new
8
+ end
9
+
10
+ it "should return itself from each_entry" do
11
+ @null_source.each_entry {}.should == @null_source
12
+ end
13
+
14
+ it "enumerates no entry paths" do
15
+ @null_source.each_entry do |path, entry|
16
+ raise RuntimeError("No entries")
17
+ end
18
+ end
19
+ end
20
+
21
+ describe Hx::NULL_SOURCE do
22
+ it "is an instance of Hx::NullSource" do
23
+ Hx::NULL_SOURCE.should be_an_instance_of(Hx::NullSource)
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'hx'
4
+
5
+ describe Hx::Overlay do
6
+ before(:each) do
7
+ @a = FakeSource.new
8
+ @a.add_entry('foo', 'foo:A')
9
+ @a.add_entry('bar', 'bar:A')
10
+ @b = FakeSource.new
11
+ @b.add_entry('bar', 'bar:B')
12
+ @b.add_entry('baz', 'baz:B')
13
+ @overlay = Hx::Overlay.new(@a, @b)
14
+ end
15
+
16
+ it "should return itself from each_entry" do
17
+ @overlay.each_entry {}.should == @overlay
18
+ end
19
+
20
+ it "should expose the union of paths" do
21
+ actual_paths = []
22
+ @overlay.each_entry do |path, entry|
23
+ actual_paths << path
24
+ end
25
+ actual_paths.sort.should == %w(foo bar baz).sort
26
+ end
27
+
28
+ it "should give earlier sources precedence" do
29
+ @overlay.each_entry do |path, entry|
30
+ entry.should == @a.get_entry('bar') if path == 'bar'
31
+ end
32
+ end
33
+
34
+ it "should expose entries from all sources" do
35
+ @overlay.each_entry do |path, entry|
36
+ entry.should == @a.get_entry('foo') if path == 'foo'
37
+ entry.should == @b.get_entry('baz') if path == 'baz'
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'hx'
4
+
5
+ describe Hx::PathSubset::Predicate do
6
+ it "accepts anything when the accept and reject patterns are nil" do
7
+ filter = Hx::PathSubset::Predicate.new(nil, nil)
8
+ filter.accept?("blah").should be_true
9
+ end
10
+
11
+ it "accepts only paths matching the accept pattern when it is specified" do
12
+ filter = Hx::PathSubset::Predicate.new("foo", nil)
13
+ filter.accept?("foo").should be_true
14
+ filter.accept?("bar").should be_false
15
+ end
16
+
17
+ it "accepts only paths not matching reject, when only that is specified" do
18
+ filter = Hx::PathSubset::Predicate.new(nil, "foo")
19
+ filter.accept?("foo").should be_false
20
+ filter.accept?("bar").should be_true
21
+ end
22
+
23
+ it "matches the difference of accept and reject when both are specified" do
24
+ filter = Hx::PathSubset::Predicate.new("foo/*", "foo/bar")
25
+ filter.accept?("foo/bar").should be_false
26
+ filter.accept?("foo/baz").should be_true
27
+ filter.accept?("bar").should be_false
28
+ end
29
+
30
+ it "shouldn't match across slashes with single star" do
31
+ filter = Hx::PathSubset::Predicate.new("foo/*", nil)
32
+ filter.accept?("bar").should be_false
33
+ filter.accept?("foo").should be_false
34
+ filter.accept?("foo/bar").should be_true
35
+ filter.accept?("foo/bar/baz").should be_false
36
+ end
37
+
38
+ it "should match across slashes with double star" do
39
+ filter = Hx::PathSubset::Predicate.new("foo/**", nil)
40
+ filter.accept?("bar").should be_false
41
+ filter.accept?("foo").should be_false
42
+ filter.accept?("foo/bar").should be_true
43
+ filter.accept?("foo/bar/baz").should be_true
44
+ end
45
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'set'
4
+ require 'hx'
5
+
6
+ describe Hx::AddPath do
7
+ before(:each) do
8
+ @before_paths = Set['foo', 'bar']
9
+ @after_paths = Set['XXXfooYYY', 'XXXbarYYY']
10
+ @source = FakeSource.new
11
+ @source.add_entry('foo', 'FOO')
12
+ @source.add_entry('bar', 'BAR')
13
+ @add = Hx::AddPath.new(@source, :prefix => 'XXX', :suffix => 'YYY')
14
+ end
15
+
16
+ it "should return itself from each_entry" do
17
+ @add.each_entry {}.should == @add
18
+ end
19
+
20
+ it "yields augmented paths from each_entry" do
21
+ paths = Set[]
22
+ @add.each_entry do |path, entry|
23
+ paths.add path
24
+ end
25
+ paths.should == @after_paths
26
+ end
27
+ end
28
+
29
+ describe Hx::StripPath do
30
+ before(:each) do @before_paths = Set['XXXfooYYY', 'XXXbarYYY', 'lemur']
31
+ @after_paths = Set['foo', 'bar']
32
+ @source = FakeSource.new
33
+ @source.add_entry('XXXfooYYY', 'FOO')
34
+ @source.add_entry('XXXbarYYY', 'BAR')
35
+ @strip = Hx::StripPath.new(@source, :prefix => 'XXX', :suffix => 'YYY')
36
+ end
37
+
38
+ it "should return itself from each_entry" do
39
+ @strip.each_entry {}.should == @strip
40
+ end
41
+
42
+ it "yields stripped paths from each_entry" do
43
+ paths = Set[]
44
+ @strip.each_entry do |path, entry|
45
+ paths.add path
46
+ end
47
+ paths.should == @after_paths
48
+ end
49
+ end
50
+
51
+ describe Hx::PathSubset do
52
+ before(:each) do
53
+ @source = FakeSource.new
54
+ @all_paths = Set['lemur', 'foo/bar', 'foo/baz', 'hoge/hoge']
55
+ @all_paths.each do |path|
56
+ @source.add_entry(path, path)
57
+ end
58
+ @subset = Hx::PathSubset.new(@source, :only => 'foo/*')
59
+ end
60
+
61
+ it "returns itself from each_entry" do
62
+ @subset.each_entry {}.should == @subset
63
+ end
64
+
65
+ it "enumerates paths according to the subset filter" do
66
+ actual_paths = Set[]
67
+ @subset.each_entry do |path, entry|
68
+ actual_paths.add path
69
+ end
70
+ actual_paths.should == Set['foo/bar', 'foo/baz']
71
+ end
72
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'hx'
4
+
5
+ describe "Hx::Site.load" do
6
+ it "requires dependencies from the config" do
7
+ yaml_config = YAML.dump({'require' => ['hx_dummy.rb']})
8
+ Hx::Site.load yaml_config, __FILE__
9
+ $".should include('hx_dummy.rb')
10
+
11
+ yaml_config = YAML.dump({'require' => 'hx_dummy2.rb'})
12
+ Hx::Site.load yaml_config, __FILE__
13
+ $".should include('hx_dummy2.rb')
14
+ end
15
+
16
+ it "returns an Hx::Site object" do
17
+ yaml_config = YAML.dump({})
18
+ site = Hx::Site.load yaml_config, __FILE__
19
+ site.should be_an_instance_of(Hx::Site)
20
+ end
21
+
22
+ it "takes default base_dir from config file path" do
23
+ yaml_config = YAML.dump({})
24
+ site = Hx::Site.load yaml_config, 'foo/baz/config.hx'
25
+ site.options[:base_dir].should == 'foo/baz'
26
+ yaml_config = YAML.dump({'options' => {'base_dir' => 'foo/bar'}})
27
+ site = Hx::Site.load yaml_config, 'foo/baz/config.hx'
28
+ site.options[:base_dir].should == 'foo/bar'
29
+ end
30
+
31
+ it "gets global options from the config, with symbol keys" do
32
+ input_options = {'foo' => 'bar', 'base_dir' => "xyz"}
33
+ output_options = {:foo => 'bar', :base_dir => "xyz"}
34
+ yaml_config = YAML.dump({'options' => input_options})
35
+ site = Hx::Site.load yaml_config, __FILE__
36
+ site.options[:foo].should == 'bar'
37
+ site.options[:base_dir].should == 'xyz'
38
+ end
39
+
40
+ it "creates a source for every entry in sources" do
41
+ yaml_config = YAML.dump({'sources' => {'foo' => {},
42
+ 'bar' => {}}})
43
+ site = Hx::Site.load(yaml_config, __FILE__)
44
+ site.sources.size.should == 2
45
+ site.sources.should include('foo')
46
+ site.sources.should include('bar')
47
+ end
48
+
49
+ it "creates an output for every entry in outputs" do
50
+ yaml_config = YAML.dump({'outputs' => [{}, {}]})
51
+ site = Hx::Site.load(yaml_config, __FILE__)
52
+ site.outputs.size.should == 2
53
+ end
54
+ end
55
+
56
+ describe Hx::Site do
57
+ before(:each) do
58
+ yaml_config = YAML.dump({})
59
+ @site = Hx::Site.load(yaml_config, __FILE__)
60
+ end
61
+
62
+ it "returns itself from each_entry" do
63
+ @site.each_entry {}.should == @site
64
+ end
65
+ end