hx 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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