contraption 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +12 -0
- data/bin/contraption +7 -0
- data/contraption.gemspec +29 -0
- data/features/author_finalizes_draft.feature +21 -0
- data/features/author_generates_site.feature +35 -0
- data/features/step_definitions/author_steps.rb +52 -0
- data/features/support/env.rb +0 -0
- data/features/support/example_inputs.rb +188 -0
- data/features/support/hooks.rb +3 -0
- data/lib/contraption/catalog.rb +65 -0
- data/lib/contraption/formatter.rb +30 -0
- data/lib/contraption/header.rb +83 -0
- data/lib/contraption/http_handler.rb +11 -0
- data/lib/contraption/location.rb +81 -0
- data/lib/contraption/options.rb +56 -0
- data/lib/contraption/post.rb +69 -0
- data/lib/contraption/repository.rb +58 -0
- data/lib/contraption/rss_builder.rb +30 -0
- data/lib/contraption/runner.rb +60 -0
- data/lib/contraption/s3_uploader.rb +30 -0
- data/lib/contraption/site.rb +85 -0
- data/lib/contraption/tag.rb +20 -0
- data/lib/contraption/tag_cloud.rb +35 -0
- data/lib/contraption/version.rb +3 -0
- data/lib/contraption.rb +14 -0
- data/spec/contraption/lib/catalog_spec.rb +105 -0
- data/spec/contraption/lib/formatter_spec.rb +30 -0
- data/spec/contraption/lib/header_spec.rb +198 -0
- data/spec/contraption/lib/location_spec.rb +148 -0
- data/spec/contraption/lib/options_spec.rb +25 -0
- data/spec/contraption/lib/post_spec.rb +50 -0
- data/spec/contraption/lib/repository_spec.rb +38 -0
- data/spec/contraption/lib/tag_cloud_spec.rb +39 -0
- data/spec/contraption/lib/tag_spec.rb +38 -0
- data/spec/contraption/lib/version_spec.rb +9 -0
- data/spec/spec_helper.rb +6 -0
- metadata +201 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d1c34da43377fb2b0cec9e0c8cf821867a18a85b
|
4
|
+
data.tar.gz: 8623c83b4113c1878756257ea4f43f02d8fa8ab4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 873f12c0aa72c64145879a0b09564419aef292d60fd3eee8d00553c9639279ec353b21a0c5bc77e3558ad6dc8ab7223f34b186b98263daff0c847aa1c20e4d4a
|
7
|
+
data.tar.gz: 78c1b4f321c8327e687f542dfc508a0f4bf8df61ed8e71a4f463fd1a5e2bdced4b9b5bf82987d5e642c5bd5f7b721e38612a2b6f2c248eceb18e7e001f195fb9
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Casey Robinson
|
2
|
+
|
3
|
+
MIT License
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Contraption
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'contraption'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install contraption
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Development
|
24
|
+
[![Code Climate](https://codeclimate.com/github/rampantmonkey/contraption.png)](https://codeclimate.com/github/rampantmonkey/contraption)
|
25
|
+
[![Build Status](https://travis-ci.org/rampantmonkey/contraption.png?branch=master)](https://travis-ci.org/rampantmonkey/contraption)
|
26
|
+
[![Coverage Status](https://coveralls.io/repos/rampantmonkey/contraption/badge.png?branch=master)](https://coveralls.io/r/rampantmonkey/contraption?branch=master)
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'cucumber/rake/task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
8
|
+
t.cucumber_opts = "features --format pretty"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => [:spec, :features]
|
12
|
+
|
data/bin/contraption
ADDED
data/contraption.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'contraption/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "contraption"
|
8
|
+
spec.version = Contraption::VERSION
|
9
|
+
spec.authors = ["Casey Robinson"]
|
10
|
+
spec.email = ["kc@rampantmonkey.com"]
|
11
|
+
spec.description = %q{Static site generator}
|
12
|
+
spec.summary = %q{Static site generator}
|
13
|
+
spec.homepage = "http://rampantmonkey.com"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.13"
|
24
|
+
spec.add_development_dependency "cucumber", "~> 1.3"
|
25
|
+
spec.add_development_dependency "coveralls", "~> 0.6"
|
26
|
+
|
27
|
+
spec.add_dependency "redcarpet", "~> 2.3"
|
28
|
+
spec.add_dependency "aws-s3", "~> 0.6.3"
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: Author finalizes draft
|
2
|
+
|
3
|
+
As an author
|
4
|
+
I have a draft
|
5
|
+
That I want to finalize for publication
|
6
|
+
|
7
|
+
Scenario: finalize draft
|
8
|
+
Given I have a valid project
|
9
|
+
Given I have a completed draft
|
10
|
+
Given I have a raw format
|
11
|
+
Given I have a article format
|
12
|
+
Given I have a page format
|
13
|
+
Given I have a month format
|
14
|
+
Given I have a year format
|
15
|
+
Given I have a tag format
|
16
|
+
Given I have a tag_cloud format
|
17
|
+
Given I have a landing_page format
|
18
|
+
When I run contraption
|
19
|
+
Then I should have a new post
|
20
|
+
And The post should be the formatted version of the draft
|
21
|
+
And The draft should be removed
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Feature: Author generates website
|
2
|
+
|
3
|
+
As an author
|
4
|
+
I have many posts
|
5
|
+
That I want to publish
|
6
|
+
|
7
|
+
Scenario: generate individual posts
|
8
|
+
Given I have a valid project
|
9
|
+
Given I have a raw format
|
10
|
+
Given I have a article format
|
11
|
+
Given I have a page format
|
12
|
+
Given I have a month format
|
13
|
+
Given I have a year format
|
14
|
+
Given I have a tag format
|
15
|
+
Given I have a tag_cloud format
|
16
|
+
Given I have a landing_page format
|
17
|
+
Given The project has a post titled "A sample post" from 3 Feb 2012 tagged funny
|
18
|
+
Given The project has a post titled "Purple People Eaters" from 1 Mar 2013 tagged funny
|
19
|
+
Given The project has a post titled "Flying Monkeys" from 8 Mar 2013 tagged strange
|
20
|
+
When I run contraption
|
21
|
+
Then The site should contain 2012/02/a-sample-post.html
|
22
|
+
Then The site should contain 2013/03/purple-people-eaters.html
|
23
|
+
Then The site should contain 2013/03/flying-monkeys.html
|
24
|
+
Then The site should contain 2012/02/index.html
|
25
|
+
Then The site should contain 2013/03/index.html
|
26
|
+
Then The site should contain 2012/index.html
|
27
|
+
Then The site should contain 2013/index.html
|
28
|
+
Then The site should contain tags/funny/index.html
|
29
|
+
Then The site should contain tags/strange/index.html
|
30
|
+
Then The site should contain tags/index.html
|
31
|
+
Then The site should contain recent.html
|
32
|
+
And 'recent.html' should have "Flying Monkeys" before "Purple People Eaters".
|
33
|
+
Then The site should contain index.html
|
34
|
+
Then The site should contain rss
|
35
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
Given(/^I have a valid project$/) do
|
4
|
+
@root = Pathname.new('tmp/data')
|
5
|
+
@root.mkpath
|
6
|
+
@required_paths = %w[drafts posts formats].map{|p| @root + p}
|
7
|
+
@required_paths.each{|p| p.mkpath}
|
8
|
+
end
|
9
|
+
|
10
|
+
Given(/^I have a (\w+?) format$/) do |format_name|
|
11
|
+
create_format "#{format_name}", @root + 'formats'
|
12
|
+
end
|
13
|
+
|
14
|
+
Given(/^I have a completed draft$/) do
|
15
|
+
@post_name = 'testing-testing.md'
|
16
|
+
create_test_draft @post_name, (@root + 'drafts')
|
17
|
+
end
|
18
|
+
|
19
|
+
When(/^I run contraption$/) do
|
20
|
+
@output = @root + '..' + 'output'
|
21
|
+
`ruby -Ilib bin/contraption -s #{@root} -d #{@output}`
|
22
|
+
end
|
23
|
+
|
24
|
+
Then(/^I should have a new post$/) do
|
25
|
+
@post_path = @root + 'posts' + "#{Date.today.strftime('%Y%m%d-')+@post_name}"
|
26
|
+
File.exist?(@post_path).should be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
Then(/^The draft should be removed$/) do
|
30
|
+
File.exist?(@root + 'drafts' + @post_name).should be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
Then(/^The post should be the formatted version of the draft$/) do
|
34
|
+
post_content = File.read(@post_path)
|
35
|
+
post_content.should_not include('Contraption::Post')
|
36
|
+
post_content.should_not include('Published: now')
|
37
|
+
end
|
38
|
+
|
39
|
+
Given(/^The project has a post titled "(.*)" from (\d+) (\S+) (\d+) tagged (\S+)$/) do |title, day, month, year, tags|
|
40
|
+
publication = "#{day} #{month} #{year}"
|
41
|
+
p = create_post_with published: publication, title: title, tags: tags
|
42
|
+
save_post p, @root + 'posts'
|
43
|
+
end
|
44
|
+
|
45
|
+
Then(/^The site should contain (.*)$/) do |path|
|
46
|
+
File.exist?(@output + path).should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
Then(/^'(.*?)' should have "(.*?)" before "(.*?)"\.$/) do |path, first_string, second_string|
|
50
|
+
content = File.read(@output+path)
|
51
|
+
content.index(first_string).should < content.index(second_string)
|
52
|
+
end
|
File without changes
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require_relative '../../lib/contraption/header'
|
2
|
+
require_relative '../../lib/contraption/post'
|
3
|
+
|
4
|
+
def create_test_draft name, path
|
5
|
+
content =<<-POST
|
6
|
+
Testing... Testing...
|
7
|
+
Type: Article
|
8
|
+
Tags: Test
|
9
|
+
Summary: Testing.
|
10
|
+
Published: now
|
11
|
+
|
12
|
+
Testing whether the markdown support works.
|
13
|
+
|
14
|
+
Code sample
|
15
|
+
#include<iostream>
|
16
|
+
|
17
|
+
int main(){
|
18
|
+
std::cout << "Hello World.\\n";
|
19
|
+
return 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
Some __bold text__.
|
23
|
+
|
24
|
+
And an image for good luck ![test](test2.jpg)
|
25
|
+
POST
|
26
|
+
write_file content, path+name
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_post_with opts={}
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_format name="raw", path
|
34
|
+
format = case name
|
35
|
+
when /article/
|
36
|
+
article_format
|
37
|
+
when /landing_page/
|
38
|
+
landing_page_format
|
39
|
+
when /page/
|
40
|
+
page_format
|
41
|
+
when /month/
|
42
|
+
month_format
|
43
|
+
when /year/
|
44
|
+
year_format
|
45
|
+
when /tag_cloud/
|
46
|
+
tag_cloud_format
|
47
|
+
when /tag/
|
48
|
+
tag_format
|
49
|
+
else
|
50
|
+
raw_format
|
51
|
+
end
|
52
|
+
write_file format, path+"#{name}.erb"
|
53
|
+
end
|
54
|
+
|
55
|
+
def raw_format
|
56
|
+
<<-'FORMAT'
|
57
|
+
<%=context.title%>
|
58
|
+
Type: <%=context.type%>
|
59
|
+
Tags: <%=context.tags%>
|
60
|
+
Published: <%=context.publication_date%>
|
61
|
+
Summary: <%=context.summary%>
|
62
|
+
|
63
|
+
<%=context.body%>
|
64
|
+
FORMAT
|
65
|
+
end
|
66
|
+
|
67
|
+
def month_format
|
68
|
+
<<-'FORMAT'
|
69
|
+
<h1 class=\"archive\">ARCHIVE FOR '#{context[:month].month} #{context[:month].year}'</h1>\n<hr class=\"subtleSep\" />
|
70
|
+
<%= context[:content] %>
|
71
|
+
|
72
|
+
FORMAT
|
73
|
+
end
|
74
|
+
|
75
|
+
def year_format
|
76
|
+
<<-'FORMAT'
|
77
|
+
<h1 class=\"archive\">ARCHIVE FOR '#{context[:year].year}'</h1>\n<hr class=\"subtleSep\" />
|
78
|
+
<%= context[:content] %>
|
79
|
+
|
80
|
+
FORMAT
|
81
|
+
end
|
82
|
+
|
83
|
+
def tag_format
|
84
|
+
<<-'FORMAT'
|
85
|
+
<h1 class-\"archive\"ARCHIVE FOR '#{context[:tag]}'</h1><\n<hr class=\"subtleSep\" />
|
86
|
+
<%= context[:content] %>
|
87
|
+
FORMAT
|
88
|
+
end
|
89
|
+
|
90
|
+
def tag_cloud_format
|
91
|
+
<<-'FORMAT'
|
92
|
+
<%= context[:content] %>
|
93
|
+
FORMAT
|
94
|
+
end
|
95
|
+
|
96
|
+
def article_format
|
97
|
+
<<-'FORMAT'
|
98
|
+
<article class="group">
|
99
|
+
<div class="grid14 thetitle">
|
100
|
+
<h1><%= context.title %></h1>
|
101
|
+
</div>
|
102
|
+
<div class="metadata grid2">
|
103
|
+
<span class="date">
|
104
|
+
<%= context.publication_date.strftime("%-d %b %Y")%>
|
105
|
+
</span>
|
106
|
+
<% context.tags.each do |t| %>
|
107
|
+
<span class="tag">
|
108
|
+
<a href="/tags/<%= t.to_s.gsub ' ', '_' %>">
|
109
|
+
<%= t.to_s %>
|
110
|
+
</a>
|
111
|
+
</span>
|
112
|
+
<% end %>
|
113
|
+
</div>
|
114
|
+
<div class="grid14">
|
115
|
+
<%= context.body %>
|
116
|
+
</div>
|
117
|
+
</article>
|
118
|
+
FORMAT
|
119
|
+
end
|
120
|
+
|
121
|
+
def page_format
|
122
|
+
<<-'FORMAT'
|
123
|
+
<!doctype html>
|
124
|
+
<body>
|
125
|
+
<section class="sidebar">
|
126
|
+
</section>
|
127
|
+
<section class="content">
|
128
|
+
<%= context %>
|
129
|
+
</section>
|
130
|
+
</body>
|
131
|
+
FORMAT
|
132
|
+
end
|
133
|
+
|
134
|
+
def landing_page_format
|
135
|
+
<<-'FORMAT'
|
136
|
+
<!doctype html>
|
137
|
+
<body class="land">
|
138
|
+
<section class="banner">
|
139
|
+
<img src="logo.png" />
|
140
|
+
<nav>
|
141
|
+
<ul>
|
142
|
+
<li><a href="recent.html">Posts</a></li>
|
143
|
+
<li><a href="projects.html">Projects</a></li>
|
144
|
+
</ul>
|
145
|
+
</nav>
|
146
|
+
</section>
|
147
|
+
<%= context[:most_recent].title %>
|
148
|
+
</body>
|
149
|
+
</html>
|
150
|
+
FORMAT
|
151
|
+
end
|
152
|
+
|
153
|
+
def default_post_params
|
154
|
+
{
|
155
|
+
title: random_string,
|
156
|
+
type: "Article",
|
157
|
+
tags: "Test, demo",
|
158
|
+
summary: "Teaser",
|
159
|
+
published: "now",
|
160
|
+
content: "Some repeated content.\n\n"*10
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_post_with options={}
|
165
|
+
opts = default_post_params.merge(options)
|
166
|
+
s = []
|
167
|
+
s << opts.fetch(:title)
|
168
|
+
s << "Type: #{opts.fetch(:type)}"
|
169
|
+
s << "Tags: #{opts.fetch(:tags)}"
|
170
|
+
s << "Summary: #{opts.fetch(:summary)}"
|
171
|
+
s << "Published: #{opts.fetch(:published)}"
|
172
|
+
s << ""
|
173
|
+
s << opts.fetch(:content)
|
174
|
+
s.join "\n"
|
175
|
+
end
|
176
|
+
|
177
|
+
def save_post post, path
|
178
|
+
filename = post.split("\n").first.downcase.gsub(/ /, '-') + ".md"
|
179
|
+
write_file post, path + filename
|
180
|
+
end
|
181
|
+
|
182
|
+
def write_file content, path
|
183
|
+
File.open(path, "w:UTF-8"){|o| o.puts content}
|
184
|
+
end
|
185
|
+
|
186
|
+
def random_string length=8
|
187
|
+
(1..length).map{('a'..'z').to_a[rand(26)] }.join
|
188
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../contraption'
|
2
|
+
|
3
|
+
module Contraption
|
4
|
+
class Catalog
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :items
|
8
|
+
|
9
|
+
def initialize items
|
10
|
+
@items = Array items
|
11
|
+
end
|
12
|
+
|
13
|
+
def << new_item
|
14
|
+
@items.concat(Array new_item)
|
15
|
+
end
|
16
|
+
|
17
|
+
def by_year
|
18
|
+
by_date { |date| Year.new date.year }
|
19
|
+
end
|
20
|
+
|
21
|
+
def by_month
|
22
|
+
by_date { |date| Month.new date.year, date.month }
|
23
|
+
end
|
24
|
+
|
25
|
+
def by_day
|
26
|
+
by_date { |date| Day.new date.year, date.month, date.day }
|
27
|
+
end
|
28
|
+
|
29
|
+
def by_tag
|
30
|
+
all(:tags).each_with_object(Hash.new {|h,k| h[k] = []}) do |tag, result|
|
31
|
+
items.each do |item|
|
32
|
+
result[tag.to_sym] << item if item.tags.include? tag
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def each
|
38
|
+
items.each
|
39
|
+
end
|
40
|
+
|
41
|
+
def most_recent n=1
|
42
|
+
n = items.length if n < 0
|
43
|
+
items.sort_by{ |item| item.publication_date }
|
44
|
+
.reverse
|
45
|
+
.take n
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def by &block
|
50
|
+
items.group_by do |item|
|
51
|
+
yield item
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def by_date &block
|
56
|
+
by { |i| yield block.call(i.publication_date) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def all attribute
|
60
|
+
items.map{|item| item.public_send(attribute.to_sym)}
|
61
|
+
.flatten
|
62
|
+
.uniq
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Contraption
|
4
|
+
class Formatter
|
5
|
+
def initialize location
|
6
|
+
@source = location
|
7
|
+
find_formats
|
8
|
+
end
|
9
|
+
|
10
|
+
def format context, method=nil
|
11
|
+
renderer(method || context.type).result binding
|
12
|
+
end
|
13
|
+
|
14
|
+
def formats
|
15
|
+
@formats.keys
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def find_formats
|
20
|
+
@formats = @source.list('.erb')
|
21
|
+
.inject({}){|collection, f| collection[f.split('.').first.to_sym] = f; collection}
|
22
|
+
end
|
23
|
+
|
24
|
+
def renderer type
|
25
|
+
ERB.new @source.read(
|
26
|
+
@formats.fetch(type){ raise ArgumentError, "#{type} is not a valid format" }
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'date'
|
2
|
+
require_relative 'tag'
|
3
|
+
|
4
|
+
module Contraption
|
5
|
+
class Header
|
6
|
+
class << self
|
7
|
+
def from string
|
8
|
+
Header.new (Hash[
|
9
|
+
string.lines
|
10
|
+
.map{|l| process l}
|
11
|
+
.reject{|l| l == nil}
|
12
|
+
])
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def process line
|
17
|
+
return nil if /^=+$/.match line
|
18
|
+
splitted = line.strip.split /^(\w+):\s+/, 2
|
19
|
+
return [:title, line.strip.chomp] if splitted.count == 1
|
20
|
+
|
21
|
+
key = splitted[1].downcase.to_sym
|
22
|
+
raw_value = splitted[2].strip
|
23
|
+
case key
|
24
|
+
when :external
|
25
|
+
[:external, raw_value]
|
26
|
+
when :filename
|
27
|
+
[:filename, raw_value]
|
28
|
+
when :published
|
29
|
+
[:publication_date, determine_date(raw_value)]
|
30
|
+
when :summary
|
31
|
+
[:summary, raw_value]
|
32
|
+
when :tags
|
33
|
+
[:tags, raw_value.split(/,\s+/).map{|t| Tag.new t}]
|
34
|
+
when :type
|
35
|
+
[:type, raw_value.downcase.to_sym]
|
36
|
+
when :title
|
37
|
+
[:title, raw_value]
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def determine_date raw_value
|
44
|
+
if raw_value.downcase == "now"
|
45
|
+
:now
|
46
|
+
else
|
47
|
+
DateTime.parse(raw_value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize opts={}
|
53
|
+
@defaults = { title: "",
|
54
|
+
publication_date: "",
|
55
|
+
summary: "",
|
56
|
+
type: "",
|
57
|
+
tags: []}.merge!(opts)
|
58
|
+
end
|
59
|
+
|
60
|
+
def filename ext='.md'
|
61
|
+
fn = @defaults.fetch(:filename) do
|
62
|
+
title.downcase
|
63
|
+
.gsub(/[^\w\s-]+/, ' ')
|
64
|
+
.strip
|
65
|
+
.gsub(/\s+/, '-') + ext
|
66
|
+
end.split ext
|
67
|
+
fn.join + ext
|
68
|
+
end
|
69
|
+
|
70
|
+
def update new_opts={}
|
71
|
+
Header.new @defaults.merge(new_opts)
|
72
|
+
end
|
73
|
+
|
74
|
+
def new?
|
75
|
+
publication_date == :now
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def method_missing m, *a, &b
|
80
|
+
@defaults.fetch(m) { super }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|