contraption 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +33 -0
  7. data/Rakefile +12 -0
  8. data/bin/contraption +7 -0
  9. data/contraption.gemspec +29 -0
  10. data/features/author_finalizes_draft.feature +21 -0
  11. data/features/author_generates_site.feature +35 -0
  12. data/features/step_definitions/author_steps.rb +52 -0
  13. data/features/support/env.rb +0 -0
  14. data/features/support/example_inputs.rb +188 -0
  15. data/features/support/hooks.rb +3 -0
  16. data/lib/contraption/catalog.rb +65 -0
  17. data/lib/contraption/formatter.rb +30 -0
  18. data/lib/contraption/header.rb +83 -0
  19. data/lib/contraption/http_handler.rb +11 -0
  20. data/lib/contraption/location.rb +81 -0
  21. data/lib/contraption/options.rb +56 -0
  22. data/lib/contraption/post.rb +69 -0
  23. data/lib/contraption/repository.rb +58 -0
  24. data/lib/contraption/rss_builder.rb +30 -0
  25. data/lib/contraption/runner.rb +60 -0
  26. data/lib/contraption/s3_uploader.rb +30 -0
  27. data/lib/contraption/site.rb +85 -0
  28. data/lib/contraption/tag.rb +20 -0
  29. data/lib/contraption/tag_cloud.rb +35 -0
  30. data/lib/contraption/version.rb +3 -0
  31. data/lib/contraption.rb +14 -0
  32. data/spec/contraption/lib/catalog_spec.rb +105 -0
  33. data/spec/contraption/lib/formatter_spec.rb +30 -0
  34. data/spec/contraption/lib/header_spec.rb +198 -0
  35. data/spec/contraption/lib/location_spec.rb +148 -0
  36. data/spec/contraption/lib/options_spec.rb +25 -0
  37. data/spec/contraption/lib/post_spec.rb +50 -0
  38. data/spec/contraption/lib/repository_spec.rb +38 -0
  39. data/spec/contraption/lib/tag_cloud_spec.rb +39 -0
  40. data/spec/contraption/lib/tag_spec.rb +38 -0
  41. data/spec/contraption/lib/version_spec.rb +9 -0
  42. data/spec/spec_helper.rb +6 -0
  43. metadata +201 -0
@@ -0,0 +1,198 @@
1
+ require_relative '../../spec_helper.rb'
2
+
3
+ require 'date'
4
+
5
+ module Contraption
6
+ describe Header do
7
+ before(:each) do
8
+ @title = "The Title"
9
+ @date = DateTime.now
10
+ @type = :a_type
11
+ @tags = []
12
+ @summary = "some witty line to get people to read the post"
13
+ @h = Header.new title: @title, publication_date: @date, type: @type, tags: @tags, summary: @summary
14
+ end
15
+
16
+ context "initialization" do
17
+
18
+ it "returns the initial title" do
19
+ @h.title.should eq(@title)
20
+ end
21
+
22
+ it "returns the initial date" do
23
+ @h.publication_date.should eq(@date)
24
+ end
25
+
26
+ it "returns the initial type" do
27
+ @h.type.should eq(@type)
28
+ end
29
+
30
+ it "returns the initial tags" do
31
+ @h.tags.should eq(@tags)
32
+ end
33
+
34
+ it "returns the summary" do
35
+ @h.summary.should eq(@summary)
36
+ end
37
+ end
38
+
39
+ context ".new?" do
40
+ it "knows when it is new" do
41
+ h = Header.from "published: now"
42
+ h.new?.should be_true
43
+ end
44
+
45
+ it "knows when it is not new" do
46
+ h = Header.from "published: #{@date}"
47
+ h.new?.should be_false
48
+ end
49
+ end
50
+
51
+ context "#filename" do
52
+ it "stores the filename if specified in the initializer" do
53
+ h = Header.new ({filename: "a-filename.md"})
54
+ h.filename.should eq "a-filename.md"
55
+ end
56
+
57
+ it "returns a filename computed from the title if no filename given" do
58
+ h = Header.new ({title: "A Filename"})
59
+ h.filename.should eq "a-filename.md"
60
+ end
61
+
62
+ it "removes non-alphanumeric characters" do
63
+ h = Header.new ({title: "Call Me ... Maybe?!"})
64
+ h.filename.should eq "call-me-maybe.md"
65
+ end
66
+
67
+ it "does not remove '-'" do
68
+ h = Header.new ({title: "Hyphenated-title"})
69
+ h.filename.should eq "hyphenated-title.md"
70
+ end
71
+
72
+ it "has only one extension" do
73
+ h = Header.new ({filename: "a-filename.md"})
74
+ h.filename.split('.').length.should eq 2
75
+ end
76
+
77
+ it "has an extension when not specified" do
78
+ h = Header.new ({filename: "a-filename"})
79
+ h.filename.split('.').length.should eq 2
80
+ end
81
+ end
82
+
83
+ context "#update" do
84
+ it "returns a new object" do
85
+ update_h = @h.update
86
+ update_h.should_not eq @h
87
+ end
88
+
89
+ it "does not modify the original header" do
90
+ initial_h = @h
91
+ @h.update
92
+ initial_h.should eq @h
93
+ end
94
+
95
+ it "stores the new title" do
96
+ new_h = @h.update title: "New Title"
97
+ new_h.title.should eq "New Title"
98
+ end
99
+
100
+ it "retains the original values" do
101
+ new_h = @h.update
102
+ new_h.title.should eq @h.title
103
+ end
104
+
105
+ it "stores all of the new values" do
106
+ new_h = @h.update title: "New Title", tags: %w[ruby]
107
+ new_h.title.should eq "New Title"
108
+ new_h.tags.should eq %w[ruby]
109
+ end
110
+ end
111
+
112
+ context "::from" do
113
+ let(:title) { "The Title" }
114
+ let(:pub_date) { "Published: Mon, 31 Dec 2012 15:41:00 EST" }
115
+ let(:expected_date) { DateTime.new(2012, 12, 31, 15, 41, 00, '-5') }
116
+ let(:summary) { "SuMMAry: Something witty to get 'eyeballs'" }
117
+ let(:type) { "tyPe: Article" }
118
+ let(:tags) { "TaGS: red, green, blue, purple" }
119
+
120
+ it "returns a Header" do
121
+ h = Header.from ""
122
+ h.class.should eq Header
123
+ end
124
+
125
+ it "finds the title" do
126
+ h = Header.from title
127
+ h.title.should eq title
128
+ end
129
+
130
+ it "finds the filename" do
131
+ h = Header.from "filenAmE: the-filename-goes-here.md"
132
+ h.filename.should eq "the-filename-goes-here.md"
133
+ end
134
+
135
+ it "finds the publication date" do
136
+ h = Header.from pub_date
137
+ h.publication_date.should eq expected_date
138
+ end
139
+
140
+ it "does not manipulate now" do
141
+ h = Header.from "published: now"
142
+ h.publication_date.should eq :now
143
+ end
144
+
145
+ it "finds the summary" do
146
+ h = Header.from summary
147
+ h.summary.should eq "Something witty to get 'eyeballs'"
148
+ end
149
+
150
+ it "finds the type" do
151
+ h = Header.from type
152
+ h.type.should eq :article
153
+ end
154
+
155
+ it "finds the tags" do
156
+ h = Header.from tags
157
+ h.tags.map{|t| t.to_s}.should eq %w[red green blue purple]
158
+ end
159
+
160
+ it "finds multiple elements" do
161
+ combined_input = [title, pub_date, type, tags].join("\n")
162
+ h = Header.from combined_input
163
+ h.title.should eq title
164
+ h.publication_date.should eq expected_date
165
+ h.type.should eq :article
166
+ h.tags.map{|t| t.to_s}.should eq %w[red green blue purple]
167
+ end
168
+
169
+ it "alternate definition for titles" do
170
+ title = "thetitle: and a subtitle"
171
+ h = Header.from("title: " + title)
172
+ h.title.should eq title
173
+ end
174
+
175
+ it "handles initial whitespace" do
176
+ t = "\t A title starting with Whitespace"
177
+ h = Header.from "title: " + t
178
+ h.title.should eq "A title starting with Whitespace"
179
+ end
180
+
181
+ it "handles unsupported header lines" do
182
+ h = Header.from "unsupported: this should not exist"
183
+ expect {h.unsupported}.to raise_error NameError
184
+ end
185
+
186
+ it "ignores '====' separators" do
187
+ example = [title, "="*15].join("\n")
188
+ h = Header.from example
189
+ h.title.should eq title
190
+ end
191
+
192
+ it "finds external links" do
193
+ h = Header.from "external: http://ruby-lang.org"
194
+ h.external.should eq "http://ruby-lang.org"
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,148 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Contraption
4
+ describe Location do
5
+ before(:all) { `mkdir /tmp/qscfr` }
6
+ after(:all) { `rm -r /tmp/qscfr` }
7
+
8
+ let (:dir) { "/tmp/qscfr" }
9
+ after(:each) { `rm -rf #{dir}/*` }
10
+
11
+ let (:l) { Location.new dir }
12
+
13
+ it "has a path" do
14
+ l.path.should eq dir
15
+ end
16
+
17
+ it "can create itself on disk" do
18
+ new_location = Location.new(dir + '/newdir')
19
+ new_location.create!
20
+ File.exist?(dir+'/newdir').should be_true
21
+ end
22
+
23
+ context "#list" do
24
+ it "handles empty directories" do
25
+ l.list.should eq []
26
+ end
27
+
28
+ it "handles directories with one file" do
29
+ `touch #{dir}/monkeys`
30
+ l.list.should eq ['monkeys']
31
+ end
32
+
33
+ it "handles directories with multiple files" do
34
+ 10.times {|i| `touch #{dir}/#{i}`}
35
+ l.list.should eq (0..9).map{|i| i.to_s}
36
+ end
37
+
38
+ it "only finds files with specific extension" do
39
+ `touch #{dir}/10.txt #{dir}/10.html #{dir}/10.md #{dir}/10.rb`
40
+ l.list(".html").should eq ["10.html"]
41
+ end
42
+
43
+ it "finds files with any of a list of extensions" do
44
+ `touch #{dir}/10.txt #{dir}/10.html #{dir}/10.md #{dir}/10.rb`
45
+ l.list(%w[.html .md]).should eq %w[10.html 10.md]
46
+ end
47
+ end
48
+
49
+ context "#read" do
50
+ let(:content) { "stuff\nthings" }
51
+ before(:each) { File.open("#{dir}/file", "w:UTF-8") { |f| f.print content } }
52
+
53
+ it "returns the contents of the file" do
54
+ l.read("file").should eq content
55
+ end
56
+
57
+ it "yields to block when passed" do
58
+ l.read("file") do |io|
59
+ io.map {|l| l[0]}
60
+ end.should eq ["s","t"]
61
+ end
62
+
63
+ it "returns a useful token if file does not exist" do
64
+ l.read("nonexistent_file").should eq :file_does_not_exist
65
+ end
66
+
67
+ it "returns a useful token if file is not readable" do
68
+ `touch #{dir}/file && chmod -r #{dir}/file`
69
+ l.read("#{dir}/file").should eq :file_not_readable
70
+ end
71
+ end
72
+
73
+ context "#write" do
74
+ it "writes the provided string to a file" do
75
+ l.write "filename", "this is a test string.\nand another line just for fun."
76
+ end
77
+
78
+ it "does not destructively write to a file" do
79
+ `touch #{dir}/a_new_file`
80
+ l.write("a_new_file", "some content that will not be written").should eq :file_already_exists
81
+ end
82
+ end
83
+
84
+ context "#remove" do
85
+ it "forgets an existing file" do
86
+ l.write("a_new_file", "some stuff to write")
87
+ l.remove("a_new_file")
88
+ l.list.should_not include("a_new_file")
89
+ end
90
+
91
+ it "does nothing for non-existing files" do
92
+ l.remove("a_non_existant_file").should eq :file_does_not_exist
93
+ end
94
+ end
95
+
96
+ context "directory trees" do
97
+ it "recursively creates directories" do
98
+ l.write "some/thing", "something"
99
+ l.read("some/thing").should eq "something"
100
+ end
101
+
102
+ it "recursively lists files" do
103
+ l.write "some/thing", "something"
104
+ l.write "a/new/file", "a"
105
+ l.list.should eq %w[a new file some thing]
106
+ end
107
+ end
108
+
109
+ context "equality" do
110
+ it "returns true for Locations with the same path" do
111
+ l.==(Location.new(dir)).should be_true
112
+ end
113
+
114
+ it "returns false for Locations with different paths" do
115
+ l.==(Location.new(dir+"a")).should be_false
116
+ end
117
+ end
118
+
119
+ context "interrogation" do
120
+ it "contains exist?" do
121
+ l.exist?
122
+ end
123
+
124
+ it "contains readable?" do
125
+ l.readable?
126
+ end
127
+
128
+ it "contains writable?" do
129
+ l.writable?
130
+ end
131
+
132
+ it "contains executable?" do
133
+ l.executable?
134
+ end
135
+ end
136
+
137
+ context "cd" do
138
+ it "returns a new Location rooted at argument" do
139
+ `mkdir #{dir}/subdir`
140
+ l.cd("subdir").path.should eq "#{dir}/subdir"
141
+ end
142
+
143
+ it "creates location if directory does not exist" do
144
+ l.cd("subdir").exist?.should be_true
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../../../lib/contraption/options'
2
+
3
+ module Contraption
4
+ describe Options do
5
+ it "accepts an array of arguments" do
6
+ Options.new []
7
+ end
8
+
9
+ it "sets the source with -s" do
10
+ Options.new(["-s", "TheSource"]).source.path.should eq "TheSource"
11
+ end
12
+
13
+ it "sets the source with --source" do
14
+ Options.new(["--source", "TheSource"]).source.path.should eq "TheSource"
15
+ end
16
+
17
+ it "sets the destination with -d" do
18
+ Options.new(["-d", "TheDestination"]).destination.path.should eq "TheDestination"
19
+ end
20
+
21
+ it "sets the destination with --destination" do
22
+ Options.new(["--destination", "TheDestination"]).destination.path.should eq "TheDestination"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ require_relative '../../spec_helper.rb'
2
+
3
+ module Contraption
4
+ describe Post do
5
+ it "has a body" do
6
+ Post.new.body
7
+ end
8
+
9
+ it "has metadata" do
10
+ Post.new.metadata
11
+ end
12
+
13
+ context "::build" do
14
+ it "returns a post object from the given string" do
15
+ Post.build("testing").class.should eq Post
16
+ end
17
+
18
+ it "has a nonempty title" do
19
+ Post.build("testing").title.should_not eq ""
20
+ end
21
+
22
+ it "has a nonempty body" do
23
+ p = Post.build("testing\n\nsome content for the post")
24
+ p.body.should_not eq ""
25
+ end
26
+
27
+ it "captures the entire contents of multiline posts" do
28
+ p = Post.build("testing\n\nsome content for the post\n\nsome more content")
29
+ p.body.should include "<p>some content for the post</p>\n\n<p>some more content</p>"
30
+ end
31
+
32
+ it "translates markdown to html" do
33
+ p = Post.build("testing\n\n__bold text__")
34
+ p.body.should include "<strong>bold text</strong>"
35
+ end
36
+ end
37
+
38
+ context ".publish" do
39
+ it "sets the new date" do
40
+ p = Post.build "\n\n"
41
+ p = p.publish.publication_date.to_s.should eq Time.now.to_s
42
+ end
43
+
44
+ it "does not modify the body" do
45
+ p = Post.build "\n\nSome stuff"
46
+ p.publish.body.should include "Some stuff"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../../../lib/contraption/repository.rb'
2
+
3
+ module Contraption
4
+ describe Repository do
5
+ context "validity" do
6
+ before(:each) do
7
+ @source = double()
8
+ @source.stub(exist?: true)
9
+ @source.stub(:cd).and_return("")
10
+ end
11
+
12
+ it "raises an error if the structure is invalid" do
13
+ @source.stub(list: %w[drafts pictures])
14
+ expect {r = Repository.new(@source)}.to raise_error
15
+ end
16
+ end
17
+
18
+ it "finds the drafts" do
19
+ Repository.any_instance.stub(:validate)
20
+ source = double()
21
+ source.stub(:list).and_return(%w[test.md house.md])
22
+ source.stub(cd: source)
23
+ r = Repository.new(source)
24
+ r.drafts.should eq %w[test.md house.md]
25
+ end
26
+
27
+ it "returns a collection of posts" do
28
+ Repository.any_instance.stub(:validate)
29
+ source = double()
30
+ source.stub(:list).and_return(%w[test.md house.md])
31
+ source.stub(:read).with("test.md").and_return("something")
32
+ source.stub(:read).with("house.md").and_return("something else")
33
+ source.stub(cd: source)
34
+ r = Repository.new(source)
35
+ r.posts.should respond_to :each
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ require_relative '../../../lib/contraption/tag_cloud'
2
+
3
+ module Contraption
4
+ describe TagCloud do
5
+ context "#initialize" do
6
+ it "takes a collection of posts" do
7
+ tc = TagCloud.new []
8
+ end
9
+ end
10
+
11
+ context "#to_s" do
12
+ before do
13
+ tag1 = double
14
+ tag2 = double
15
+ tag1.stub(:to_s).and_return("Ruby")
16
+ tag1.stub(:to_url).and_return("ruby")
17
+ tag2.stub(:to_s).and_return("OS X")
18
+ tag2.stub(:to_url).and_return("os-x")
19
+ posts = double
20
+ posts.stub(:by_tag).and_return( {tag1 => [1, 2], tag2 => [1]} )
21
+ @tc = TagCloud.new posts
22
+ end
23
+
24
+ it "contains all of the tags" do
25
+ @tc.to_s.should include 'ruby'
26
+ @tc.to_s.should include 'os-x'
27
+ end
28
+
29
+ it "includes a link to the tag page" do
30
+ @tc.to_s.should include 'ruby/index.html'
31
+ @tc.to_s.should include 'os-x/index.html'
32
+ end
33
+
34
+ it "sets the size of each tag" do
35
+ @tc.to_s.should include 'font-size'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../../../lib/contraption/tag.rb'
2
+
3
+ module Contraption
4
+ describe Tag do
5
+ it "accepts a string to be displayed" do
6
+ t = Tag.new "Ruby"
7
+ end
8
+
9
+ it "returns the display text" do
10
+ t = Tag.new "Ruby"
11
+ t.to_s.should eq "Ruby"
12
+ end
13
+
14
+ context "#to_url" do
15
+ it "provides a url friendly version" do
16
+ t = Tag.new "Ruby"
17
+ t.to_url
18
+ end
19
+
20
+ it "downcases the output" do
21
+ t = Tag.new "Ruby"
22
+ t.to_url.should eq "ruby"
23
+ end
24
+
25
+ it "replaces spaces with dashes" do
26
+ t = Tag.new "ruby rocks"
27
+ t.to_url.should eq "ruby-rocks"
28
+ end
29
+ end
30
+
31
+ context "#to_sym" do
32
+ it "returns a symbolized version of the display text" do
33
+ t = Tag.new "Qwerty"
34
+ t.to_sym.should eq "Qwerty".to_sym
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Contraption
4
+ describe VERSION do
5
+ it "is defined" do
6
+ Contraption::VERSION
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ require_relative '../lib/contraption'
2
+
3
+ if ENV['TRAVIS']
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+ end