CarmineContraption 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 20a2c70d35267d05960e810047bdef045d63b903
4
+ data.tar.gz: 553ea900ca9ae8dc4010f33173c2cca0f90b81dd
5
+ SHA512:
6
+ metadata.gz: 059a83a1e06aa97cc8ab2c0bd9e6021f5d5ccc4f6f94088bf75fe3bbe38dea738c5920aab2c1189f550c902e2e19f0e69dde5980a3e483e2a7cfc8aecfb572d7
7
+ data.tar.gz: f6e16bea89780b765c77e7702a09cdf45f75aa677c9a1b545168c2e94db3834ddeeb806b26f93f3c3d50f502952fe4af4e122e5a92e6d70e0111ae8dec0f3c32
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ script bundle exec rake
data/Rakefile CHANGED
@@ -1,13 +1,11 @@
1
- require 'pathname'
2
- require 'colorize'
1
+ require 'rake/testtask'
3
2
 
4
3
  task default: [:test]
5
4
 
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
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib/carmine_contraption'
7
+ t.test_files = FileList['test/lib/carmine_contraption/test_*.rb']
8
+ t.verbose = true
11
9
  end
12
10
 
13
11
  task :build_gem do
data/Readme.md CHANGED
@@ -18,7 +18,7 @@ Currently, Carmine Contraption requires ssh access to a web server and a Git rep
18
18
  I have published Carmine Contraption as a [gem on RubyGems.org](https://rubygems.org/gems/CarmineContraption). Just use `gem install CarmineContraption` to test it out.
19
19
 
20
20
  ### Content Folder Structure
21
- This is the tree that you write in. More specifically, the drafts folder. `content` is known as `source` within the bowels of Carmine Contraption.
21
+ This is the tree that you write in. More specifically, the drafts folder. `content` is known as `source` within the bowels of Carmine Contraption. `formats` and `templates` contain layout and styling for posts. `formats` contain html templates for displaying individual posts. `templates` contain templates for displaying collections of posts.
22
22
 
23
23
  content
24
24
  |
@@ -28,11 +28,29 @@ This is the tree that you write in. More specifically, the drafts folder. `conte
28
28
  | |
29
29
  | |-- another_draft.md
30
30
  |
31
+ |-- formats
32
+ | |
33
+ | |-- article.html.erb
34
+ | |
35
+ | |-- link.html.erb
36
+ | |
37
+ | |-- raw.md.erb
38
+ | |
39
+ | |-- rss.xml.erb
40
+ |
31
41
  |-- posts
32
- |
33
- |-- 20120721-on-metaprogramming.html
34
- |
35
- |-- 20120801-example-post.md
42
+ | |
43
+ | |-- 20120721-on-metaprogramming.html
44
+ | |
45
+ | |-- 20120801-example-post.md
46
+ |
47
+ |-- templates
48
+ |
49
+ |-- default.html.erb
50
+ |
51
+ |-- landing_page.html.erb
52
+ |
53
+ |-- rss.xml.erb
36
54
 
37
55
  ### Server Folder Structure
38
56
  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`.
@@ -79,13 +97,19 @@ The header tags can be placed in any order. Once Carmine Contraption encounters
79
97
 
80
98
  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.
81
99
 
100
+ ### Formatters and Templates
101
+ Formatters and templates are very similar. Both are [ERB](http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html) files. Both are given one variable for parsing, `context`. The main distinction is the expected use case. Formatters are meant for styling/laying out a single post, while templates represent the overall structure of the website. To handle the different purposes the contents of `context` are slightly different: formatters are given an instance of `CarmineContraption::Post`, while templates are passed a string containing a collection of formatted posts.
102
+
82
103
  ## Contributing
83
- ### Build Status
84
- All statuses provided by [Gitlab CI](https://github.com/gitlabhq/gitlab-ci).
104
+ To suggest a feature or report a bug create a [new issue](https://github.com/rampantmonkey/carmine_contraption/issues/new).
105
+
106
+ Pull requests will also be considered.
107
+
108
+ ### Status
85
109
 
86
- - Master &rarr; ![master](http://ci.rampantmonkey.com/projects/1/status?ref=master)
87
- - Dev &rarr; ![dev](http://ci.rampantmonkey.com/projects/1/status?ref=dev)
88
- - Testing &rarr; ![testing](http://ci.rampantmonkey.com/projects/1/status?ref=testing)
110
+ [![Build Status](https://travis-ci.org/rampantmonkey/carmine_contraption.png?branch=master)](https://travis-ci.org/rampantmonkey/carmine_contraption)
111
+ [![Code Climate](https://codeclimate.com/github/rampantmonkey/carmine_contraption.png)](https://codeclimate.com/github/rampantmonkey/carmine_contraption)
112
+ [![Gem Version](https://badge.fury.io/rb/CarmineContraption.png)](http://badge.fury.io/rb/CarmineContraption)
89
113
 
90
114
  ## License
91
115
  Carmine Contraption is licensed under [The MIT License](http://opensource.org/licenses/MIT).
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Casey Robinson"]
10
10
  s.email = ["kc@rampantmonkey.com"]
11
- s.homepage = ""
11
+ s.homepage = "https://github.com/rampantmonkey/carmine_contraption"
12
12
  s.summary = "Static Website Generator"
13
13
  s.description = "Longer and more detailed version of summary..."
14
14
 
@@ -0,0 +1,34 @@
1
+ require 'carmine_contraption/black_hole.rb'
2
+ require 'carmine_contraption/error.rb'
3
+ require 'carmine_contraption/formatter.rb'
4
+ require 'carmine_contraption/index.rb'
5
+ require 'carmine_contraption/media.rb'
6
+ require 'carmine_contraption/options.rb'
7
+ require 'carmine_contraption/post.rb'
8
+ require 'carmine_contraption/runner.rb'
9
+ require 'carmine_contraption/uploader.rb'
10
+ require 'carmine_contraption/version.rb'
11
+ require 'carmine_contraption/writer.rb'
12
+
13
+ require 'yaml'
14
+
15
+ module CarmineContraption
16
+ def Maybe value
17
+ value.nil? ? BlackHole.new : value
18
+ end
19
+
20
+ at_exit do
21
+ if $!
22
+ puts "Something bad happened. See `/tmp/crash.log` for details"
23
+ open('/tmp/crash.log', 'a') do |log|
24
+ error = { timestamp: Time.now,
25
+ message: $!.message,
26
+ backtrace: $!.backtrace
27
+ }
28
+ YAML.dump error, log
29
+ end
30
+ exit!
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,12 @@
1
+ require_relative '../carmine_contraption'
2
+
3
+ module CarmineContraption
4
+ class BlackHole
5
+ def method_missing *args, &block
6
+ self
7
+ end
8
+
9
+ def nil?; true; end
10
+ end
11
+ end
12
+
@@ -0,0 +1,5 @@
1
+ module CarmineContraption
2
+ class Error < StandardError; end
3
+
4
+ class MissingFormat < Error; end
5
+ end
@@ -0,0 +1,34 @@
1
+ require 'time'
2
+ require 'erb'
3
+
4
+ module CarmineContraption
5
+ class Formatter
6
+ attr_reader :directory
7
+
8
+ def initialize formatter_directory='./'
9
+ @directory = Pathname.new(formatter_directory).expand_path
10
+ find_formats
11
+ end
12
+
13
+ def format context, method
14
+ renderer = ERB.new formats_hash.fetch(method) { raise MissingFormat, "formatter #{method} not found" }.read
15
+ renderer.result binding
16
+ end
17
+
18
+ def formats
19
+ @formats.keys
20
+ end
21
+
22
+ private
23
+ def find_formats
24
+ tmp = {}
25
+ Pathname.glob(@directory+"*") { |path| tmp[path.basename.to_s.split('.')[0].to_sym] = path }
26
+ @formats = tmp
27
+ end
28
+
29
+ def formats_hash
30
+ @formats
31
+ end
32
+
33
+ end
34
+ end
@@ -1,141 +1,82 @@
1
- require_relative 'month_transform.rb'
2
- require_relative 'post'
1
+ require_relative '../carmine_contraption'
3
2
  require 'pathname'
4
3
  require 'time'
5
4
  require 'erb'
6
5
  require 'colorize'
6
+ require 'date'
7
7
 
8
8
  module CarmineContraption
9
9
  class Index
10
- include MonthTransform
11
- attr_reader :yearly_archives, :monthly_archives, :tag_archives
10
+ attr_reader :base, :destination, :all_posts
12
11
 
13
- def initialize source, destination, template, override=false
14
- @source = source
12
+ def initialize base_path, destination
13
+ @base = base_path
14
+ @source = base_path + "posts"
15
15
  @destination = destination
16
- raise "Template not found." unless template.file?
17
- @template = template
18
- @override = override
16
+ find_all_posts
19
17
  end
20
18
 
21
- def build_monthly_archives
22
- @monthly_archives = {}
23
- collect_archives { |the_post| (@monthly_archives[the_post.year + the_post.month] ||=[]) << the_post}
19
+ def yearly_archives
20
+ build_yearly_archives unless @yearly
21
+ @yearly
24
22
  end
25
23
 
26
- def build_tag_archives
27
- @tag_archives = {}
28
- collect_archives { |the_post| the_post.tags.each { |t| (@tag_archives[t.gsub(' ', '_').downcase] ||=[]) << the_post } }
24
+ def monthly_archives
25
+ build_monthly_archives unless @monthly
26
+ @monthly
29
27
  end
30
28
 
31
- def build_yearly_archives
32
- @yearly_archives = {}
33
- collect_archives { |the_post| (@yearly_archives[the_post.year] ||= []) << the_post}
29
+ def tag_archives
30
+ build_tag_archives unless @tagly
31
+ @tagly
34
32
  end
35
33
 
36
- def collect_archives
37
- @all_posts.each do |a|
38
- yield a
39
- end
40
- end
41
-
42
- def find_all_posts
43
- @all_paths = []
44
- Pathname.glob(@source+"*.html") { |path| @all_paths << path}
45
- Pathname.glob(@source+"*.md") { |path| @all_paths << path}
46
- @all_posts = []
47
- @all_paths.each do |path|
48
- @all_posts << Post.from_file(path, path.extname)
49
- end
50
- @all_posts.sort!
34
+ def build_date_from_key k
35
+ d = Date.new k[0..3].to_i, k[4..5].to_i
51
36
  end
52
37
 
53
- def write_file file, content='', supress_message=false
54
- puts "Rendering #{file}".blue unless supress_message
55
- renderer = ERB.new(@template.read)
56
- file.open('w:UTF-8') { |f| f.write renderer.result(binding) }
38
+ def longest_tag
39
+ find_length_of_most_used_tag
57
40
  end
58
41
 
59
- def write_individual_posts
60
- @all_posts.each do |the_post|
61
- path = @destination + the_post.year + the_post.month
62
- path.mkpath
63
- path += "#{the_post.guid}.html"
64
- write_file path, the_post.formatted_output unless ( path.exist? and !@override )
42
+ private
43
+ def build_monthly_archives
44
+ @monthly = {}
45
+ collect_archives { |the_post| (@monthly[the_post.published.strftime "%Y%m"] ||=[]) << the_post}
65
46
  end
66
- end
67
47
 
68
- def write_main_page
69
- content = ""
70
- @all_posts[-8..-1].reverse.each do |the_post|
71
- content << the_post.formatted_output
72
- end
73
- content << %Q{<section class="archive_title">\n<h2>Archives</h2>\n<section class="archives">\n}
74
- @monthly_archives.keys.reverse.each do |k|
75
- content << %Q{<a href="/#{k[0..3]}/#{k[4..5]}/" class="nohover">#{month_alpha_long k[4..5]} #{k[0..3]}</a>\n}
48
+ def build_tag_archives
49
+ @tagly = {}
50
+ collect_archives { |the_post| the_post.tags.each { |t| (@tagly[t.gsub(' ', '_').downcase] ||=[]) << the_post } }
76
51
  end
77
- content << %Q{</section>\n</section>\n}
78
- write_file @destination + "recent.html", content
79
- end
80
52
 
81
- def write_monthly_archives
82
- @monthly_archives.keys.each do |k|
83
- path = @destination + k[0..3] + k[4..5]
84
- path.mkpath
85
- content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{(month_alpha_long k[4..5]).upcase} #{k[0..3]}</emph>'</h1>\n<hr class=\"subtleSep\" />"
86
- @monthly_archives[k].each { |the_post| content << the_post.formatted_output }
87
- write_file path+"index.html", content, true
53
+ def build_yearly_archives
54
+ @yearly = {}
55
+ collect_archives { |the_post| (@yearly[the_post.published.strftime "%Y"] ||= []) << the_post}
88
56
  end
89
- end
90
57
 
91
- def write_rss
92
- 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)
93
- @all_posts.reverse.each { |the_post| content += the_post.formatted_output 'rss' }
94
- content += %Q(</channel>\n</rss>)
95
- feed_path = @destination + "rss.xml"
96
- feed_path.open('w:UTF-8') { |f| f.write(content) }
97
- rss_base_path = @destination+"rss"
98
- rss_base_path.mkpath
99
- @tag_archives.keys.each do |k|
100
- 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
- @tag_archives[k].reverse.each { |the_post| content += the_post.formatted_output 'rss' }
102
- content += %Q(</channel>\n</rss>)
103
- (rss_base_path + "#{k}.xml").open('w:UTF-8') { |f| f.write(content) }
104
- end
105
- puts "Updating RSS feed".blue
106
- end
107
58
 
108
- def write_tag_archives
109
- path = @destination + "tags"
110
- path.mkpath
111
- @tag_archives.keys.each do |k|
112
- content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{k.upcase.gsub('_', ' ')}</emph>' CATEGORY</h1>\n<hr class=\"subtleSep\" />"
113
- @tag_archives[k].reverse.each { |the_post| content << the_post.formatted_output }
114
- write_file path + "#{k.gsub(' ', '_').downcase}.html", content, true
59
+ def find_all_posts
60
+ @all_paths = []
61
+ Pathname.glob(@source+"*.html") { |path| @all_paths << path}
62
+ Pathname.glob(@source+"*.md") { |path| @all_paths << path}
63
+ @all_posts = []
64
+ @all_paths.each do |path|
65
+ @all_posts << Post.from_file(path, path.extname)
66
+ end
67
+ @all_posts.sort!
115
68
  end
116
- end
117
69
 
118
- def write_tag_cloud
119
- path = @destination + "tags" + "index.html"
120
- content = "<h1 class=\"archive\">TAG CLOUD</h1>\n<hr class=\"subtleSep\" />"
121
- content += "<section class=\"tag_cloud \">\n"
122
- max = @tag_archives.max_by { |k, v| v.length }[-1].length
123
- @tag_archives.sort_by{|k,v| k}.each do |k, v|
124
- size = (1+v.length.to_f/max) * 0.8
125
- content += "<span style=\"font-size: #{size}em;\"><a href=\"tags/#{k.gsub(' ', '_').downcase}.html\">#{k.gsub('_', ' ')}</a></span>\n"
70
+
71
+ def collect_archives
72
+ @all_posts.each do |a|
73
+ yield a
74
+ end
126
75
  end
127
- content += "</section>\n"
128
- write_file path, content
129
- end
130
76
 
131
- def write_yearly_archives
132
- @yearly_archives.keys.each do |k|
133
- path = @destination + k
134
- path.mkpath
135
- content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{k}</emph>'</h1>\n<hr class=\"subtleSep\" />"
136
- @yearly_archives[k].each { |the_post| content << the_post.formatted_output }
137
- write_file path+"index.html", content, true
77
+ def find_length_of_most_used_tag
78
+ tag_archives.max_by { |k,v| v.length }[-1].length
138
79
  end
139
- end
80
+
140
81
  end
141
82
  end
@@ -15,7 +15,7 @@ module CarmineContraption
15
15
  end
16
16
 
17
17
  def remote_path
18
- values[:remote_path]
18
+ values[:remote_path] + name.to_s
19
19
  end
20
20
 
21
21
  def update args={}
@@ -2,19 +2,14 @@
2
2
 
3
3
  require 'redcarpet'
4
4
  require 'colorize'
5
+ require 'date'
5
6
 
6
- require_relative 'formatters'
7
- require_relative 'month_transform'
8
- require_relative 'media'
7
+ require_relative '../carmine_contraption'
9
8
 
10
9
  module CarmineContraption
11
10
  class Post
12
- attr_reader :title, :content, :extra, :guid, :summary, :published, :status, :type, :tags
13
-
14
- include MonthTransform
15
11
 
16
12
  class << self
17
- include MonthTransform
18
13
 
19
14
  def from_file path, extension=".html"
20
15
  guid = File.basename(path, extension)
@@ -35,46 +30,39 @@ module CarmineContraption
35
30
  if line =~ /^\s/
36
31
  new_post = new_post.update(content: raw_content[(i+1)..-1].join("\n"))
37
32
  break
38
- end
39
- l = line.split ': '
40
- if l.length == 1
41
- new_post = new_post.update(title: (ignore_separator l[0])) unless new_post.title
42
33
  else
43
- case l[0].downcase
44
- when /type/
45
- new_post = new_post.update(type: l[1])
46
- when /published/
47
- new_post = new_post.update published: (convert_date l[1])
48
- when /tags/
49
- new_post = new_post.update tags: (parse_tags l[1])
50
- when /extra/
51
- new_post = new_post.update extra: (rejoin_string l)
52
- when /summary/
53
- new_post = new_post.update summary: (rejoin_string l)
54
- end
34
+ new_post = process_header_line line, new_post
55
35
  end
56
36
  end
57
37
  new_post
58
38
  end
59
39
 
60
- def ignore_separator line
61
- line unless line =~ /^=+/
40
+ def process_header_line line, new_post
41
+ l = line.split ': '
42
+ if l.length == 1
43
+ new_post = new_post.update(title: (ignore_separator l[0])) unless new_post.title
44
+ else
45
+ new_post = new_post.update(header_switch l[0].downcase.to_sym, l)
46
+ end
47
+ new_post
62
48
  end
63
49
 
64
- def convert_date date
65
- if date =~ /^\D/
66
- published = date.split(' ')
67
- tmp = []
68
- tmp[0] = "#{published[3]}-#{month_arabic published[2]}-#{published[1]}"
69
- tmp[1] = published[4]
70
- tmp[2] = '-0400'
71
- tmp[2] = '-0500' if published[-1] == 'EST'
72
- tmp.join(' ')
50
+ def header_switch sym, split_line
51
+ case sym
52
+ when :published
53
+ split_line[1] = split_line[1].gsub(/<\/p>/, '')
54
+ { published: (DateTime.parse split_line[1]) }
55
+ when :tags
56
+ { tags: (parse_tags split_line[1]) }
73
57
  else
74
- date
58
+ { sym => rejoin_string(split_line) }
75
59
  end
76
60
  end
77
61
 
62
+ def ignore_separator line
63
+ line unless line =~ /^=+/
64
+ end
65
+
78
66
  def find_media content, path
79
67
  a = []
80
68
  content.scan(/<img src="([^"]*)"/) do |match|
@@ -114,66 +102,33 @@ module CarmineContraption
114
102
  guid: '',
115
103
  summary: '',
116
104
  published: '',
105
+ path: nil,
117
106
  status: '',
118
107
  type: '',
119
108
  tags: [],
120
109
  media: [],
121
- original_path: ''}
122
- @defaults.merge!(opts)
123
- @title = @defaults[:title]
124
- @content = @defaults[:content]
125
- @extra = @defaults[:extra]
126
- @guid = @defaults[:guid]
127
- @summary = @defaults[:summary]
128
- @published = @defaults[:published]
129
- @status = @defaults[:status]
130
- @type = @defaults[:type]
131
- @tags = @defaults[:tags]
110
+ original_path: ''}.merge!(opts)
132
111
  end
133
112
 
134
- def update opts={}
135
- Post.new(@defaults.merge opts)
136
- end
137
-
138
- def year
139
- @published[0..3]
113
+ def method_missing method, *args, &block
114
+ @defaults.fetch(method) { super }
140
115
  end
141
116
 
142
- def month
143
- @published[5..6]
144
- end
145
-
146
- def day
147
- @published[8..9]
117
+ def update opts={}
118
+ Post.new(@defaults.merge opts)
148
119
  end
149
120
 
150
121
  def <=> other
151
122
  published <=> other.published
152
123
  end
153
124
 
154
- def media
155
- @defaults[:media]
156
- end
157
-
158
- def original_path
159
- @defaults[:original_path]
160
- end
161
-
162
- def formatted_output method='type'
125
+ def formatted_output formatter, method=nil
163
126
  insert_remote_media_links
164
- formatter = CarmineContraption::RawFormatter.new
165
- case method
166
- when 'type'
167
- case @type.downcase
168
- when /article/
169
- formatter = CarmineContraption::ArticleFormatter.new
170
- when /link/
171
- formatter = CarmineContraption::LinkFormatter.new
172
- end
173
- when 'rss'
174
- formatter = CarmineContraption::RssFormatter.new
127
+ if method
128
+ formatter.format self, method
129
+ else
130
+ formatter.format self, type.chomp.downcase.to_sym
175
131
  end
176
- formatter.format_post self
177
132
  end
178
133
 
179
134
  private