CarmineContraption 0.0.5 → 0.0.6
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/.gitignore +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +7 -0
- data/Rakefile +16 -0
- data/Readme.md +90 -0
- data/carmine_contraption.gemspec +26 -0
- data/lib/carmine_contraption/index.rb +28 -30
- data/lib/carmine_contraption/options.rb +9 -15
- data/lib/carmine_contraption/post.rb +48 -31
- data/lib/carmine_contraption/runner.rb +13 -12
- data/lib/carmine_contraption/version.rb +3 -0
- data/lib/templates/{default.html → default.html.erb} +1 -1
- data/test/test_index.rb +6 -0
- data/test/test_options.rb +62 -74
- data/test/test_post.rb +107 -0
- data/test/test_runner.rb +6 -0
- metadata +70 -4
data/.gitignore
CHANGED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Casey Robinson (kc@rampantmonkey.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
task default: [:test]
|
5
|
+
|
6
|
+
task :test do
|
7
|
+
Pathname.glob("test/test_*.rb") do |path|
|
8
|
+
puts "\nExecuting #{path.basename.to_s.magenta}"
|
9
|
+
ruby path
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
task :build_gem do
|
14
|
+
system "gem build carmine_contraption.gemspec"
|
15
|
+
system "mv *.gem pkg/"
|
16
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
Carmine Contraption is an engine for managing static web content specifically targeted at blogging.
|
2
|
+
|
3
|
+
## Defintion
|
4
|
+
__Contraption__ - *Informal, often facetious or derogatory a device or contrivance, esp one considered strange, unnecessarily intricate, or improvised*
|
5
|
+
|
6
|
+
## Warning
|
7
|
+
Carmine Contraption, as the name implies, is unfriendly and unsuitable for users uncomfortable with [Ruby](http://www.ruby-lang.org/en/) and [Git](http://git-scm.com).
|
8
|
+
|
9
|
+
Despite being used to manage [rampantmonkey.com](http://rampantmonkey.com) for a long time many features are missing and bugs run rampant through the code.
|
10
|
+
|
11
|
+
## Then why publish it?
|
12
|
+
I created Carmine Contraption as a learning experience to explore many of the unique features Ruby provides. This project provides examples of metaprogramming, design patterns, testing, and many more. The small code size allows exploration of these features while maintaining a detailed view of the entire system.
|
13
|
+
|
14
|
+
## Getting Started
|
15
|
+
Currently, Carmine Contraption requires ssh access to a web server and a Git repository for the site's content. First let's take a look at the folder structures that Carmine Contraption works with.
|
16
|
+
|
17
|
+
### Content Folder Structure
|
18
|
+
This is the tree that you write in. More specifically, the drafts folder. `content` is known as `source` within the bowels of Carmine Contraption.
|
19
|
+
|
20
|
+
content
|
21
|
+
|
|
22
|
+
|-- drafts
|
23
|
+
| |
|
24
|
+
| |-- first_draft.html
|
25
|
+
| |
|
26
|
+
| |-- another_draft.md
|
27
|
+
|
|
28
|
+
|-- posts
|
29
|
+
|
|
30
|
+
|-- 20120721-on-metaprogramming.html
|
31
|
+
|
|
32
|
+
|-- 20120801-example-post.md
|
33
|
+
|
34
|
+
### Server Folder Structure
|
35
|
+
This layout is the living, breathing incarnation of your content. `www` is known as `destination` to Carmine Contraption. I have only listed the directories that Carmine Contraption will generate and modify. Other useful directories you may consider including are `css` and `js`.
|
36
|
+
|
37
|
+
www
|
38
|
+
|
|
39
|
+
|-- 2012
|
40
|
+
| |-- index.html
|
41
|
+
| |
|
42
|
+
| |-- 07
|
43
|
+
| | |-- index.html
|
44
|
+
| | |
|
45
|
+
| | |-- on-metaprogramming.html
|
46
|
+
| |
|
47
|
+
| |-- 08
|
48
|
+
| |-- example-post.html
|
49
|
+
|
|
50
|
+
|-- index.html
|
51
|
+
|
|
52
|
+
|-- rss.xml
|
53
|
+
|
|
54
|
+
|-- tags
|
55
|
+
|-- Apple
|
56
|
+
|-- Vim
|
57
|
+
|
58
|
+
### Basic Program Flow
|
59
|
+
When Carmine Contraption is started up it looks in the drafts folder for any post that is ready to publish (see the Post Format section for details). A post that is ready to publish will then be timestamped and moved to the posts folder. These changes are then committed to the git content repo and pushed to the remote server. After the `new post check` is completed Carmine Contraption moves into update mode.
|
60
|
+
|
61
|
+
In update mode, all of the posts are read and sorted. Carmine Contraption then goes through and fills out templates with the relevant content. Current templates include: landing page, recent posts, individual posts, date ranges (yearly and monthly) posts, posts with the same tag, and rss feeds for the whole site as well as each tag. Refining the template system is a high priority for improving Carmine Contraption.
|
62
|
+
|
63
|
+
Once all of the templates are filled out Carmine Contraption shuts down.
|
64
|
+
|
65
|
+
### Post Format
|
66
|
+
Posts follow a specific, yet flexible, file format. Posts are plain text files with a header section.
|
67
|
+
|
68
|
+
This is a Title
|
69
|
+
Type: Article
|
70
|
+
Tags: test, Ruby
|
71
|
+
Summary: Testing...
|
72
|
+
|
73
|
+
<p>Content goes here. Both HTML and Markdown are supported</p>
|
74
|
+
|
75
|
+
The header tags can be placed in any order. Once Carmine Contraption encounters a blank (only whitespace) line the rest of the file is considered the post's content.
|
76
|
+
|
77
|
+
To mark a post as ready to publish add `publish-now` as a line anywhere in the file. This line will be replaced with the Publication timestamp.
|
78
|
+
|
79
|
+
## Contributing
|
80
|
+
### Build Status
|
81
|
+
All statuses provided by [Gitlab CI](https://github.com/gitlabhq/gitlab-ci).
|
82
|
+
|
83
|
+
- Master → 
|
84
|
+
- Dev → 
|
85
|
+
- Testing → 
|
86
|
+
|
87
|
+
## License
|
88
|
+
Carmine Contraption is licensed under [The MIT License](http://opensource.org/licenses/MIT).
|
89
|
+
|
90
|
+
In other words: Do whatever you feel like.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "carmine_contraption/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "CarmineContraption"
|
7
|
+
s.version = CarmineContraption::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Casey Robinson"]
|
10
|
+
s.email = ["kc@rampantmonkey.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = "Static Website Generator"
|
13
|
+
s.description = "Longer and more detailed version of summary..."
|
14
|
+
|
15
|
+
s.rubyforge_project = "carmine_contraption"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency 'shoulda', '~> 3.3.2'
|
23
|
+
s.add_development_dependency 'rake', '~> 0.9.2.2'
|
24
|
+
|
25
|
+
s.add_runtime_dependency 'colorize', '~> 0.5.8'
|
26
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require_relative 'month_transform.rb'
|
2
2
|
require_relative 'post'
|
3
|
+
require 'pathname'
|
3
4
|
require 'time'
|
5
|
+
require 'erb'
|
4
6
|
|
5
7
|
module CarmineContraption
|
6
8
|
class Index
|
@@ -10,7 +12,7 @@ module CarmineContraption
|
|
10
12
|
def initialize source, destination, template
|
11
13
|
@source = source
|
12
14
|
@destination = destination
|
13
|
-
raise "Template not found." unless
|
15
|
+
raise "Template not found." unless template.file?
|
14
16
|
@template = template
|
15
17
|
end
|
16
18
|
|
@@ -37,33 +39,25 @@ module CarmineContraption
|
|
37
39
|
|
38
40
|
def find_all_posts
|
39
41
|
@all_paths = []
|
40
|
-
|
41
|
-
|
42
|
+
Pathname.glob(@source+"*.html") { |path| @all_paths << path}
|
43
|
+
Pathname.glob(@source+"*.md") { |path| @all_paths << path}
|
42
44
|
@all_posts = []
|
43
45
|
@all_paths.sort.each do |path|
|
44
|
-
|
45
|
-
@all_posts << Post.from_file(path, extension)
|
46
|
+
@all_posts << Post.from_file(path, path.extname)
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def write_file filename, content=''
|
54
|
-
output = File.open(@template, 'r').read
|
55
|
-
output.gsub!(/\#\{content\}/, content)
|
56
|
-
File.open("#{filename}", 'w:UTF-8') {|f| f.write(output) }
|
50
|
+
def write_file file, content=''
|
51
|
+
renderer = ERB.new(@template.read)
|
52
|
+
file.open('w:UTF-8') { |f| f.write renderer.result(binding) }
|
57
53
|
end
|
58
54
|
|
59
55
|
def write_individual_posts
|
60
56
|
@all_posts.each do |the_post|
|
61
|
-
path =
|
62
|
-
|
63
|
-
path += "
|
64
|
-
|
65
|
-
path += "/#{the_post.guid}.html"
|
66
|
-
write_file path, the_post.formatted_output unless File.exists? path
|
57
|
+
path = @destination + the_post.year + the_post.month
|
58
|
+
path.mkpath
|
59
|
+
path += "#{the_post.guid}.html"
|
60
|
+
write_file path, the_post.formatted_output unless path.exist?
|
67
61
|
end
|
68
62
|
end
|
69
63
|
|
@@ -77,16 +71,16 @@ module CarmineContraption
|
|
77
71
|
content << %Q{<a href="/#{k[0..3]}/#{k[4..5]}/" class="nohover">#{month_alpha_long k[4..5]} #{k[0..3]}</a>\n}
|
78
72
|
end
|
79
73
|
content << %Q{</section>\n</section>\n}
|
80
|
-
write_file
|
74
|
+
write_file @destination + "recent.html", content
|
81
75
|
end
|
82
76
|
|
83
77
|
def write_monthly_archives
|
84
78
|
@monthly_archives.keys.each do |k|
|
85
|
-
|
86
|
-
|
79
|
+
path = @destination + k[0..3] + k[4..5]
|
80
|
+
path.mkpath
|
87
81
|
content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{(month_alpha_long k[4..5]).upcase} #{k[0..3]}</emph>'</h1>\n<hr class=\"subtleSep\" />"
|
88
82
|
@monthly_archives[k].each { |the_post| content << the_post.formatted_output }
|
89
|
-
write_file "
|
83
|
+
write_file path+"index.html", content
|
90
84
|
end
|
91
85
|
end
|
92
86
|
|
@@ -94,31 +88,35 @@ module CarmineContraption
|
|
94
88
|
content = %Q(<?xml version="1.0" encoding="utf-8" ?>\n<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">\n<channel>\n<title>Rampant Monkey</title>\n<link>http://rampantmonkey.com</link>\n<description>Rampant Monkey - Kitchen Sink</description>\n<language>en-us</language>\n<atom:link href="http://rampantmonkey.com/rss.xml" rel="self" type="application/rss+xml" /><lastBuildDate>#{Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")}</lastBuildDate>\n)
|
95
89
|
@all_posts.reverse.each { |the_post| content += the_post.formatted_output 'rss' }
|
96
90
|
content += %Q(</channel>\n</rss>)
|
97
|
-
|
98
|
-
|
91
|
+
feed_path = @destination + "rss.xml"
|
92
|
+
feed_path.open('w:UTF-8') { |f| f.write(content) }
|
93
|
+
rss_base_path = @destination+"rss"
|
94
|
+
rss_base_path.mkpath
|
99
95
|
@tag_archives.keys.each do |k|
|
100
96
|
content = %Q(<?xml version="1.0" encoding="utf-8" ?>\n<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">\n<channel>\n<title>Rampant Monkey</title>\n<link>http://rampantmonkey.com</link>\n<description>Rampant Monkey - #{k}</description>\n<language>en-us</language>\n<atom:link href="http://rampantmonkey.com/rss/#{k}.xml" rel="self" type="application/rss+xml" /><lastBuildDate>#{Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")}</lastBuildDate>\n)
|
101
97
|
@tag_archives[k].reverse.each { |the_post| content += the_post.formatted_output 'rss' }
|
102
98
|
content += %Q(</channel>\n</rss>)
|
103
|
-
|
99
|
+
(rss_base_path + "#{k}.xml").open('w:UTF-8') { |f| f.write(content) }
|
104
100
|
end
|
105
101
|
end
|
106
102
|
|
107
103
|
def write_tag_archives
|
108
|
-
|
104
|
+
path = @destination + "tags"
|
105
|
+
path.mkpath
|
109
106
|
@tag_archives.keys.each do |k|
|
110
107
|
content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{k.upcase.gsub('_', ' ')}</emph>' CATEGORY</h1>\n<hr class=\"subtleSep\" />"
|
111
108
|
@tag_archives[k].reverse.each { |the_post| content << the_post.formatted_output }
|
112
|
-
write_file "#{
|
109
|
+
write_file path + "#{k.gsub(' ', '_').downcase}.html", content
|
113
110
|
end
|
114
111
|
end
|
115
112
|
|
116
113
|
def write_yearly_archives
|
117
114
|
@yearly_archives.keys.each do |k|
|
118
|
-
|
115
|
+
path = @destination + k
|
116
|
+
path.mkpath
|
119
117
|
content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{k}</emph>'</h1>\n<hr class=\"subtleSep\" />"
|
120
118
|
@yearly_archives[k].each { |the_post| content << the_post.formatted_output }
|
121
|
-
write_file "
|
119
|
+
write_file path+"index.html", content
|
122
120
|
end
|
123
121
|
end
|
124
122
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'yaml'
|
3
|
+
require 'pathname'
|
3
4
|
|
4
5
|
module CarmineContraption
|
5
6
|
class Options
|
6
7
|
|
7
|
-
DEFAULT_SOURCE = "/srv/www/rampantmonkey.com/content/"
|
8
|
-
DEFAULT_DESTINATION = "/srv/www/rampantmonkey.com/public_html/"
|
8
|
+
DEFAULT_SOURCE = Pathname.new("/srv/www/rampantmonkey.com/content/")
|
9
|
+
DEFAULT_DESTINATION = Pathname.new("/srv/www/rampantmonkey.com/public_html/")
|
9
10
|
|
10
|
-
DEFAULT_CONFIG_FILE = "
|
11
|
+
DEFAULT_CONFIG_FILE = Pathname.new("~/.carmine_contraption.yaml").expand_path
|
11
12
|
|
12
13
|
attr_reader :build_targets
|
13
14
|
|
@@ -17,16 +18,11 @@ module CarmineContraption
|
|
17
18
|
new_post_check: true,
|
18
19
|
update_repository: true
|
19
20
|
}
|
20
|
-
@config.merge!(YAML.load_file(DEFAULT_CONFIG_FILE)) if
|
21
|
+
@config.merge!(YAML.load_file(DEFAULT_CONFIG_FILE)) if DEFAULT_CONFIG_FILE.file?
|
22
|
+
@config.each_pair { |k, v| @config[k] = Pathname.new(v) if v.class == String }
|
21
23
|
@build_targets = Hash.new(false)
|
22
24
|
@build_targets[:individual] = true
|
23
25
|
parse(argv)
|
24
|
-
@config[:source] += "/" unless ends_with_slash? @config[:source]
|
25
|
-
@config[:destination] += "/" unless ends_with_slash? @config[:destination]
|
26
|
-
end
|
27
|
-
|
28
|
-
def ends_with_slash? s
|
29
|
-
s[-1] == "/"
|
30
26
|
end
|
31
27
|
|
32
28
|
def method_missing(m, *args, &block)
|
@@ -50,11 +46,10 @@ module CarmineContraption
|
|
50
46
|
@build_targets[:individual] = true
|
51
47
|
end
|
52
48
|
opts.on("-d", "--destination path", String, "Path to destination") do |path|
|
53
|
-
|
54
|
-
@config[:destination] = path
|
49
|
+
@config[:destination] = Pathname.new(path).expand_path
|
55
50
|
end
|
56
51
|
opts.on("--generate-config-file", "Create YAML file containing configuration options") do
|
57
|
-
|
52
|
+
DEFAULT_CONFIG_FILE.open('w+') {|f| f.write(@config.to_yaml) }
|
58
53
|
exit
|
59
54
|
end
|
60
55
|
opts.on("-h", "--help", "Show this message") do
|
@@ -74,8 +69,7 @@ module CarmineContraption
|
|
74
69
|
@build_targets[:rss] = true
|
75
70
|
end
|
76
71
|
opts.on("-s", "--source path", String, "Path to source files") do |path|
|
77
|
-
|
78
|
-
@config[:source] = path
|
72
|
+
@config[:source] = Pathname.new(path).expand_path
|
79
73
|
end
|
80
74
|
opts.on("--skip-new-post-check", "Do not check for new posts") do
|
81
75
|
@config[:new_post_check] = false
|
@@ -13,55 +13,72 @@ module CarmineContraption
|
|
13
13
|
def self.from_file path, extension=".html"
|
14
14
|
guid = File.basename(path, extension)
|
15
15
|
guid = guid[9..-1]
|
16
|
-
|
17
|
-
|
18
|
-
content =
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
a = IO.readlines(path)
|
17
|
+
new_post = extract_header a
|
18
|
+
new_post.content = translate_markdown new_post.content if extension == ".md"
|
19
|
+
new_post.guid = guid
|
20
|
+
new_post
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.extract_header raw_content
|
24
|
+
raw_content = raw_content.split('\n') if raw_content.class == String
|
25
|
+
new_post = CarmineContraption::Post.new nil, '', '', '', '', '', '', '', []
|
26
|
+
raw_content.each_with_index do |line, i|
|
26
27
|
if line =~ /^\s/
|
27
|
-
|
28
|
-
|
28
|
+
new_post.content = raw_content[(i+1)..-1].join("\n")
|
29
|
+
break
|
29
30
|
end
|
30
31
|
l = line.split ': '
|
31
32
|
if l.length == 1
|
32
|
-
new_post.title
|
33
|
+
new_post.title ||= ignore_separator l[0]
|
33
34
|
else
|
34
35
|
case l[0].downcase
|
35
36
|
when /type/
|
36
37
|
new_post.type = l[1]
|
37
38
|
when /published/
|
38
|
-
|
39
|
-
published = l[1].split(' ')
|
40
|
-
tmp = []
|
41
|
-
tmp[0] = "#{published[3]}-#{month_arabic published[2]}-#{published[1]}"
|
42
|
-
tmp[1] = published[4]
|
43
|
-
tmp[2] = '-0400'
|
44
|
-
tmp[2] = '-0500' if published[-1] == 'EST'
|
45
|
-
new_post.published = tmp.join(' ')
|
46
|
-
else
|
47
|
-
new_post.published = l[1]
|
48
|
-
end
|
39
|
+
new_post.published = convert_date l[1]
|
49
40
|
when /tags/
|
50
|
-
tags = l[1]
|
51
|
-
new_post.tags = tags
|
41
|
+
new_post.tags = parse_tags l[1]
|
52
42
|
when /extra/
|
53
|
-
new_post.extra = l
|
43
|
+
new_post.extra = rejoin_string l
|
54
44
|
when /summary/
|
55
|
-
new_post.summary = l
|
45
|
+
new_post.summary = rejoin_string l
|
56
46
|
end
|
57
47
|
end
|
58
48
|
end
|
59
|
-
content = `echo "#{content}" | bin/markdown.pl` if extension == ".md"
|
60
|
-
new_post.content = content
|
61
|
-
new_post.guid = guid
|
62
49
|
new_post
|
63
50
|
end
|
64
51
|
|
52
|
+
def self.ignore_separator line
|
53
|
+
line unless line =~ /^=+/
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.convert_date date
|
57
|
+
if date =~ /^\D/
|
58
|
+
published = date.split(' ')
|
59
|
+
tmp = []
|
60
|
+
tmp[0] = "#{published[3]}-#{month_arabic published[2]}-#{published[1]}"
|
61
|
+
tmp[1] = published[4]
|
62
|
+
tmp[2] = '-0400'
|
63
|
+
tmp[2] = '-0500' if published[-1] == 'EST'
|
64
|
+
tmp.join(' ')
|
65
|
+
else
|
66
|
+
date
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.parse_tags unparsed
|
71
|
+
unparsed.chomp.downcase.split(', ')
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.rejoin_string array
|
75
|
+
array[1..-1].join ': '
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.translate_markdown content
|
79
|
+
`echo "#{content}" | bin/markdown.pl`
|
80
|
+
end
|
81
|
+
|
65
82
|
def initialize(title, content, extra, guid, summary, published, status, type, tags)
|
66
83
|
@title = title
|
67
84
|
@content = content
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative 'options'
|
2
2
|
require_relative 'index'
|
3
|
+
require 'pathname'
|
3
4
|
|
4
5
|
module CarmineContraption
|
5
6
|
class Runner
|
@@ -7,18 +8,17 @@ module CarmineContraption
|
|
7
8
|
@options = Options.new(argv)
|
8
9
|
@source = @options.source
|
9
10
|
@destination = @options.destination
|
10
|
-
@template =
|
11
|
+
@template = Pathname.new('lib/templates/default.html.erb')
|
11
12
|
@input_extensions = [".md", ".html"]
|
13
|
+
@drafts_path = @source + 'drafts'
|
14
|
+
@posts_path = @source + 'posts'
|
12
15
|
end
|
13
16
|
|
14
17
|
def check_for_new_posts
|
15
|
-
drafts_path = @source + '/drafts/'
|
16
|
-
posts_path = @source + '/posts/'
|
17
|
-
|
18
18
|
@input_extensions.each do |input_extension|
|
19
|
-
|
19
|
+
Pathname.glob(@drafts_path + "*#{input_extension}") do |i|
|
20
20
|
post = ''
|
21
|
-
posted_filename =
|
21
|
+
posted_filename = i.basename
|
22
22
|
move_completed_draft = false
|
23
23
|
File.foreach(i) do |line|
|
24
24
|
if line.downcase =~ /publish-now/
|
@@ -31,7 +31,7 @@ module CarmineContraption
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
if move_completed_draft
|
34
|
-
|
34
|
+
(@posts_path+posted_filename).open('w') {|f| f.write(post)}
|
35
35
|
`rm #{i}`
|
36
36
|
`lib/update_repo.sh #{@source} #{posted_filename}` if @options.update_repository
|
37
37
|
end
|
@@ -42,17 +42,18 @@ module CarmineContraption
|
|
42
42
|
def build_landing
|
43
43
|
all_paths = []
|
44
44
|
@input_extensions.each do |input_extension|
|
45
|
-
|
45
|
+
Pathname.glob(@posts_path + "*#{input_extension}") { | path | all_paths << path }
|
46
46
|
end
|
47
47
|
most_recent = all_paths.sort.last
|
48
|
-
most_recent_extension = most_recent.
|
48
|
+
most_recent_extension = most_recent.extname
|
49
49
|
the_post = Post.from_file(most_recent, ".#{most_recent_extension}")
|
50
|
-
|
50
|
+
landing_template = Pathname.new('lib/templates/landing_page.html')
|
51
|
+
output = landing_template.open('r').read
|
51
52
|
output.gsub!(/\#\{title\}/, the_post.title)
|
52
53
|
output.gsub!(/\#\{summary\}/, the_post.summary)
|
53
54
|
link = "#{the_post.year}/#{the_post.month}/#{the_post.guid}.html"
|
54
55
|
output.gsub!(/\#\{link\}/, link)
|
55
|
-
|
56
|
+
(@destination + "index.html").open('w:UTF-8') {|f| f.write(output) }
|
56
57
|
end
|
57
58
|
|
58
59
|
def build_monthly
|
@@ -88,7 +89,7 @@ module CarmineContraption
|
|
88
89
|
def run
|
89
90
|
check_for_new_posts if @options.new_post_check
|
90
91
|
if @options.build_targets.has_value?(true)
|
91
|
-
@the_index = Index.new @
|
92
|
+
@the_index = Index.new @posts_path, @destination, @template
|
92
93
|
@the_index.find_all_posts
|
93
94
|
@options.build_targets.each { |k, v| send "build_#{k}" if v }
|
94
95
|
end
|
data/test/test_index.rb
ADDED
data/test/test_options.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'shoulda'
|
3
|
-
|
3
|
+
require 'pathname'
|
4
|
+
require_relative '../lib/carmine_contraption/options'
|
4
5
|
|
5
6
|
class TestOptions < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def path_expander string
|
9
|
+
Pathname.new(string).expand_path
|
10
|
+
end
|
11
|
+
|
6
12
|
context "specifying nothing" do
|
7
13
|
setup do
|
8
|
-
@block = lambda {
|
14
|
+
@block = lambda { CarmineContraption::Options.new([]) }
|
9
15
|
end
|
10
16
|
|
11
17
|
should "throw SystemExit exception" do
|
@@ -15,148 +21,130 @@ class TestOptions < Test::Unit::TestCase
|
|
15
21
|
|
16
22
|
context "specifying a source" do
|
17
23
|
should "return it unchanged" do
|
18
|
-
opts =
|
19
|
-
assert_equal "TheSource
|
20
|
-
end
|
21
|
-
|
22
|
-
should "add a trailing slash" do
|
23
|
-
opts = Engine::Options.new(["-s", "TheSource"])
|
24
|
-
assert_equal "TheSource/", opts.source
|
24
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
25
|
+
assert_equal path_expander("TheSource"), opts.source
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
29
|
context "specifying a destination" do
|
29
30
|
should "return it unchanged" do
|
30
|
-
opts =
|
31
|
-
assert_equal "TheDestination
|
32
|
-
end
|
33
|
-
|
34
|
-
should "add a trailing slash" do
|
35
|
-
opts = Engine::Options.new(["-d", "TheDestination"])
|
36
|
-
assert_equal "TheDestination/", opts.destination
|
31
|
+
opts = CarmineContraption::Options.new(["-d", "TheDestination"])
|
32
|
+
assert_equal path_expander("TheDestination"), opts.destination
|
37
33
|
end
|
38
34
|
end
|
39
35
|
|
40
36
|
context "specifying a source and destination" do
|
41
|
-
should "return both
|
42
|
-
opts =
|
43
|
-
assert_equal "TheSource
|
44
|
-
assert_equal "TheDestination
|
45
|
-
end
|
46
|
-
|
47
|
-
should "return both unchanged" do
|
48
|
-
opts = Engine::Options.new(["-s", "TheSource/", "-d", "TheDestination/"])
|
49
|
-
assert_equal "TheSource/", opts.source
|
50
|
-
assert_equal "TheDestination/", opts.destination
|
37
|
+
should "return both" do
|
38
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource", "-d", "TheDestination"])
|
39
|
+
assert_equal path_expander("TheSource"), opts.source
|
40
|
+
assert_equal path_expander("TheDestination"), opts.destination
|
51
41
|
end
|
52
42
|
end
|
53
43
|
|
54
44
|
context "skip new post check" do
|
55
45
|
should "return default value" do
|
56
|
-
opts =
|
46
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
57
47
|
assert_equal true, opts.new_post_check
|
58
48
|
end
|
59
49
|
|
60
50
|
should "disable new post checking" do
|
61
|
-
opts =
|
51
|
+
opts = CarmineContraption::Options.new(["--skip-new-post-check"])
|
62
52
|
assert_equal false, opts.new_post_check
|
63
53
|
end
|
64
54
|
end
|
65
55
|
|
66
56
|
context "build index" do
|
67
57
|
should "return the default value" do
|
68
|
-
opts =
|
69
|
-
assert_equal false, opts.
|
58
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
59
|
+
assert_equal false, opts.build_targets[:index]
|
70
60
|
end
|
71
61
|
|
72
62
|
should "enable build index (short)" do
|
73
|
-
opts =
|
74
|
-
assert_equal = true, opts.
|
63
|
+
opts = CarmineContraption::Options.new(["-i"])
|
64
|
+
assert_equal = true, opts.build_targets[:index]
|
75
65
|
end
|
76
66
|
|
77
67
|
should "enable build index (long)" do
|
78
|
-
opts =
|
79
|
-
assert_equal = true, opts.
|
68
|
+
opts = CarmineContraption::Options.new(["--build_index"])
|
69
|
+
assert_equal = true, opts.build_targets[:index]
|
80
70
|
end
|
81
71
|
end
|
82
72
|
|
83
73
|
context "build monthly" do
|
84
74
|
should "return the default value" do
|
85
|
-
opts =
|
86
|
-
assert_equal false, opts.
|
75
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
76
|
+
assert_equal false, opts.build_targets[:monthly]
|
87
77
|
end
|
88
78
|
|
89
|
-
should "enable build
|
90
|
-
opts =
|
91
|
-
assert_equal = true, opts.
|
79
|
+
should "enable build monthly (short)" do
|
80
|
+
opts = CarmineContraption::Options.new(["-m"])
|
81
|
+
assert_equal = true, opts.build_targets[:monthly]
|
92
82
|
end
|
93
83
|
|
94
|
-
should "enable build
|
95
|
-
opts =
|
96
|
-
assert_equal = true, opts.
|
84
|
+
should "enable build monthly (long)" do
|
85
|
+
opts = CarmineContraption::Options.new(["--build_monthly"])
|
86
|
+
assert_equal = true, opts.build_targets[:monthly]
|
97
87
|
end
|
98
88
|
end
|
99
89
|
|
100
90
|
context "build tags" do
|
101
91
|
should "return the default value" do
|
102
|
-
opts =
|
103
|
-
assert_equal false, opts.
|
92
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
93
|
+
assert_equal false, opts.build_targets[:tags]
|
104
94
|
end
|
105
95
|
|
106
|
-
should "enable build
|
107
|
-
opts =
|
108
|
-
assert_equal = true, opts.
|
96
|
+
should "enable build tags (short)" do
|
97
|
+
opts = CarmineContraption::Options.new(["-t"])
|
98
|
+
assert_equal = true, opts.build_targets[:tags]
|
109
99
|
end
|
110
100
|
|
111
|
-
should "enable build
|
112
|
-
opts =
|
113
|
-
assert_equal = true, opts.
|
101
|
+
should "enable build tags (long)" do
|
102
|
+
opts = CarmineContraption::Options.new(["--build_tags"])
|
103
|
+
assert_equal = true, opts.build_targets[:tags]
|
114
104
|
end
|
115
105
|
end
|
116
106
|
|
117
107
|
context "build yearly" do
|
118
108
|
should "return the default value" do
|
119
|
-
opts =
|
120
|
-
assert_equal false, opts.
|
109
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
110
|
+
assert_equal false, opts.build_targets[:yearly]
|
121
111
|
end
|
122
112
|
|
123
113
|
should "enable build yearly (short)" do
|
124
|
-
opts =
|
125
|
-
assert_equal = true, opts.
|
114
|
+
opts = CarmineContraption::Options.new(["-y"])
|
115
|
+
assert_equal = true, opts.build_targets[:yearly]
|
126
116
|
end
|
127
117
|
|
128
118
|
should "enable build yearly (long)" do
|
129
|
-
opts =
|
130
|
-
assert_equal = true, opts.
|
119
|
+
opts = CarmineContraption::Options.new(["--build_yearly"])
|
120
|
+
assert_equal = true, opts.build_targets[:yearly]
|
131
121
|
end
|
132
122
|
end
|
133
123
|
|
134
124
|
context "build everything" do
|
135
125
|
should "return the default value" do
|
136
|
-
opts =
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
126
|
+
opts = CarmineContraption::Options.new(["-s", "TheSource"])
|
127
|
+
opts.build_targets.each do |k, v|
|
128
|
+
if k == :individual
|
129
|
+
assert_equal true, v, "Individual build target incorrect"
|
130
|
+
else
|
131
|
+
assert_equal false, v, "Build #{k} incorrect"
|
132
|
+
end
|
133
|
+
end
|
142
134
|
end
|
143
135
|
|
144
136
|
should "enable build everything (short)" do
|
145
|
-
opts =
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
assert_equal true, opts.build_tags
|
150
|
-
assert_equal true, opts.build_yearly
|
137
|
+
opts = CarmineContraption::Options.new(["-a"])
|
138
|
+
opts.build_targets.each do |k, v|
|
139
|
+
assert_equal true, v, "Build #{k} not set"
|
140
|
+
end
|
151
141
|
end
|
152
142
|
|
153
143
|
should "enable build everything (long)" do
|
154
|
-
opts =
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
assert_equal true, opts.build_tags
|
159
|
-
assert_equal true, opts.build_yearly
|
144
|
+
opts = CarmineContraption::Options.new(["--build_all"])
|
145
|
+
opts.build_targets.each do |k, v|
|
146
|
+
assert_equal true, v, "Build #{k} not set"
|
147
|
+
end
|
160
148
|
end
|
161
149
|
end
|
162
150
|
end
|
data/test/test_post.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'shoulda'
|
3
|
+
require_relative '../lib/carmine_contraption/post'
|
4
|
+
|
5
|
+
class TestPost < Test::Unit::TestCase
|
6
|
+
context "Parsing Tags" do
|
7
|
+
should "Return zero tags" do
|
8
|
+
assert_equal CarmineContraption::Post.parse_tags(""), []
|
9
|
+
end
|
10
|
+
|
11
|
+
should "Return one tag" do
|
12
|
+
assert_equal CarmineContraption::Post.parse_tags("boogers"), ["boogers"]
|
13
|
+
end
|
14
|
+
|
15
|
+
should "Return three tags" do
|
16
|
+
assert_equal CarmineContraption::Post.parse_tags("a, b, c"), ["a", "b", "c"]
|
17
|
+
end
|
18
|
+
|
19
|
+
should "Return lots of tags" do
|
20
|
+
tags = Array.new(999, "z")
|
21
|
+
assert_equal CarmineContraption::Post.parse_tags(tags.join(", ")), tags
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "Markdown" do
|
26
|
+
should "return same text" do
|
27
|
+
text = "<p></p>\n"
|
28
|
+
assert_equal text, CarmineContraption::Post.translate_markdown(text)
|
29
|
+
end
|
30
|
+
|
31
|
+
should "convert to code block" do
|
32
|
+
text = " int main(){"
|
33
|
+
assert_equal "<pre><code>int main(){\n</code></pre>\n", CarmineContraption::Post.translate_markdown(text)
|
34
|
+
end
|
35
|
+
|
36
|
+
should "convert to link" do
|
37
|
+
assert_equal %Q{<p><a href="http://rampantmonkey.com">Website</a></p>\n}, CarmineContraption::Post.translate_markdown(%q{[Website](http://rampantmonkey.com)})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "Date Importing" do
|
42
|
+
should "return date" do
|
43
|
+
d = "2010-02-21 18:42:49 -0400"
|
44
|
+
assert_equal d, CarmineContraption::Post.convert_date(d)
|
45
|
+
end
|
46
|
+
|
47
|
+
should "return formatted date" do
|
48
|
+
assert_equal "2012-11-20 08:56:32 -0500",
|
49
|
+
CarmineContraption::Post.convert_date("Tue, 20 Nov 2012 08:56:32 EST")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "Date Processing" do
|
54
|
+
setup do
|
55
|
+
@the_post = CarmineContraption::Post.new('', '', '', '', '', "2010-02-21 18:00:00 -0400", '', '', '')
|
56
|
+
end
|
57
|
+
|
58
|
+
should "return year" do
|
59
|
+
assert_respond_to @the_post, :year
|
60
|
+
assert_equal "2010", @the_post.year
|
61
|
+
end
|
62
|
+
|
63
|
+
should "return month" do
|
64
|
+
assert_respond_to @the_post, :month
|
65
|
+
assert_equal "02", @the_post.month
|
66
|
+
end
|
67
|
+
|
68
|
+
should "return day" do
|
69
|
+
assert_respond_to @the_post, :day
|
70
|
+
assert_equal "21", @the_post.day
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "Separator" do
|
75
|
+
should "ignore separator" do
|
76
|
+
assert_equal nil, CarmineContraption::Post.ignore_separator("=")
|
77
|
+
end
|
78
|
+
|
79
|
+
should "ignore longer separator" do
|
80
|
+
assert_equal nil, CarmineContraption::Post.ignore_separator("="*10)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "not ignore valid title" do
|
84
|
+
title = "This is the Title"
|
85
|
+
assert_equal title, CarmineContraption::Post.ignore_separator(title)
|
86
|
+
end
|
87
|
+
|
88
|
+
should "not ignore empty string" do
|
89
|
+
title = ""
|
90
|
+
assert_equal title, CarmineContraption::Post.ignore_separator(title)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "Rejoining Strings" do
|
95
|
+
should "return nothing" do
|
96
|
+
assert CarmineContraption::Post.rejoin_string(["word"]).empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
should "return original word" do
|
100
|
+
assert_equal "word", CarmineContraption::Post.rejoin_string(["a", "word"])
|
101
|
+
end
|
102
|
+
|
103
|
+
should "return all but the first word" do
|
104
|
+
assert_equal "word: play", CarmineContraption::Post.rejoin_string(["a", "word", "play"])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/test/test_runner.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: CarmineContraption
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,56 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-12-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: shoulda
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.3.2
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.3.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.2.2
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.2.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: colorize
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.5.8
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.5.8
|
14
62
|
description: Longer and more detailed version of summary...
|
15
63
|
email:
|
16
64
|
- kc@rampantmonkey.com
|
@@ -21,20 +69,29 @@ extensions: []
|
|
21
69
|
extra_rdoc_files: []
|
22
70
|
files:
|
23
71
|
- .gitignore
|
72
|
+
- Gemfile
|
73
|
+
- LICENSE.txt
|
74
|
+
- Rakefile
|
75
|
+
- Readme.md
|
24
76
|
- bin/carmine_contraption
|
25
77
|
- bin/markdown.pl
|
78
|
+
- carmine_contraption.gemspec
|
26
79
|
- lib/carmine_contraption/formatters.rb
|
27
80
|
- lib/carmine_contraption/index.rb
|
28
81
|
- lib/carmine_contraption/month_transform.rb
|
29
82
|
- lib/carmine_contraption/options.rb
|
30
83
|
- lib/carmine_contraption/post.rb
|
31
84
|
- lib/carmine_contraption/runner.rb
|
85
|
+
- lib/carmine_contraption/version.rb
|
32
86
|
- lib/photos.rb
|
33
|
-
- lib/templates/default.html
|
87
|
+
- lib/templates/default.html.erb
|
34
88
|
- lib/templates/landing_page.html
|
35
89
|
- lib/templates/photo.html
|
36
90
|
- lib/update_repo.sh
|
91
|
+
- test/test_index.rb
|
37
92
|
- test/test_options.rb
|
93
|
+
- test/test_post.rb
|
94
|
+
- test/test_runner.rb
|
38
95
|
homepage: ''
|
39
96
|
licenses: []
|
40
97
|
post_install_message:
|
@@ -47,12 +104,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
104
|
- - ! '>='
|
48
105
|
- !ruby/object:Gem::Version
|
49
106
|
version: '0'
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
hash: -3708087154013416350
|
50
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
111
|
none: false
|
52
112
|
requirements:
|
53
113
|
- - ! '>='
|
54
114
|
- !ruby/object:Gem::Version
|
55
115
|
version: '0'
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
hash: -3708087154013416350
|
56
119
|
requirements: []
|
57
120
|
rubyforge_project: carmine_contraption
|
58
121
|
rubygems_version: 1.8.23
|
@@ -60,4 +123,7 @@ signing_key:
|
|
60
123
|
specification_version: 3
|
61
124
|
summary: Static Website Generator
|
62
125
|
test_files:
|
126
|
+
- test/test_index.rb
|
63
127
|
- test/test_options.rb
|
128
|
+
- test/test_post.rb
|
129
|
+
- test/test_runner.rb
|