CarmineContraption 0.0.6 → 0.1.0

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/Rakefile CHANGED
@@ -14,3 +14,7 @@ task :build_gem do
14
14
  system "gem build carmine_contraption.gemspec"
15
15
  system "mv *.gem pkg/"
16
16
  end
17
+
18
+ task :publish do
19
+ system "ruby -I lib bin/carmine_contraption -a"
20
+ end
data/Readme.md CHANGED
@@ -14,6 +14,9 @@ I created Carmine Contraption as a learning experience to explore many of the un
14
14
  ## Getting Started
15
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
16
 
17
+ ## Gem
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
+
17
20
  ### Content Folder Structure
18
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.
19
22
 
@@ -20,7 +20,9 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_development_dependency 'shoulda', '~> 3.3.2'
23
- s.add_development_dependency 'rake', '~> 0.9.2.2'
23
+ s.add_development_dependency 'rake', '~> 0.9.2.2'
24
24
 
25
- s.add_runtime_dependency 'colorize', '~> 0.5.8'
25
+ s.add_runtime_dependency 'colorize', '~> 0.5.8'
26
+ s.add_runtime_dependency 'redcarpet', '~> 2.2.2'
27
+ s.add_runtime_dependency 'aws-s3', '~> 0.6.3'
26
28
  end
@@ -5,7 +5,7 @@ module CarmineContraption
5
5
  class ArticleFormatter
6
6
  def format_post context
7
7
  s = '<article class="group">
8
- <div class="grid14 title"><a href="' + context.year + "/" + context.month + "/" + context.guid + '"><h1>' + context.title + '</h1></a></div>
8
+ <div class="grid14 thetitle"><a href="' + context.year + "/" + context.month + "/" + context.guid + '"><h1>' + context.title + '</h1></a></div>
9
9
  <div class="metadata grid2">
10
10
  <span class="date">' + context.day + ' ' + "#{context.month_alpha context.month}" + ' ' + context.year + '</span>
11
11
  '
@@ -23,7 +23,7 @@ module CarmineContraption
23
23
  class LinkFormatter
24
24
  def format_post context
25
25
  s = '<article class="group">
26
- <div class="grid14 title"><a href="' + context.extra + '"><h1>' + context.title + '<span>&rarr;</span></h1></a></div>
26
+ <div class="grid14 thetitle"><a href="' + context.extra + '"><h1>' + context.title + '<span>&rarr;</span></h1></a></div>
27
27
  <div class="metadata grid2">
28
28
  <span class="date">' + context.day + ' ' + "#{context.month_alpha context.month}" + ' ' + context.year + '</span>
29
29
  '
@@ -3,17 +3,19 @@ require_relative 'post'
3
3
  require 'pathname'
4
4
  require 'time'
5
5
  require 'erb'
6
+ require 'colorize'
6
7
 
7
8
  module CarmineContraption
8
9
  class Index
9
10
  include MonthTransform
10
11
  attr_reader :yearly_archives, :monthly_archives, :tag_archives
11
12
 
12
- def initialize source, destination, template
13
+ def initialize source, destination, template, override=false
13
14
  @source = source
14
15
  @destination = destination
15
16
  raise "Template not found." unless template.file?
16
17
  @template = template
18
+ @override = override
17
19
  end
18
20
 
19
21
  def build_monthly_archives
@@ -42,12 +44,14 @@ module CarmineContraption
42
44
  Pathname.glob(@source+"*.html") { |path| @all_paths << path}
43
45
  Pathname.glob(@source+"*.md") { |path| @all_paths << path}
44
46
  @all_posts = []
45
- @all_paths.sort.each do |path|
47
+ @all_paths.each do |path|
46
48
  @all_posts << Post.from_file(path, path.extname)
47
49
  end
50
+ @all_posts.sort!
48
51
  end
49
52
 
50
- def write_file file, content=''
53
+ def write_file file, content='', supress_message=false
54
+ puts "Rendering #{file}".blue unless supress_message
51
55
  renderer = ERB.new(@template.read)
52
56
  file.open('w:UTF-8') { |f| f.write renderer.result(binding) }
53
57
  end
@@ -57,7 +61,7 @@ module CarmineContraption
57
61
  path = @destination + the_post.year + the_post.month
58
62
  path.mkpath
59
63
  path += "#{the_post.guid}.html"
60
- write_file path, the_post.formatted_output unless path.exist?
64
+ write_file path, the_post.formatted_output unless ( path.exist? and !@override )
61
65
  end
62
66
  end
63
67
 
@@ -80,7 +84,7 @@ module CarmineContraption
80
84
  path.mkpath
81
85
  content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{(month_alpha_long k[4..5]).upcase} #{k[0..3]}</emph>'</h1>\n<hr class=\"subtleSep\" />"
82
86
  @monthly_archives[k].each { |the_post| content << the_post.formatted_output }
83
- write_file path+"index.html", content
87
+ write_file path+"index.html", content, true
84
88
  end
85
89
  end
86
90
 
@@ -98,6 +102,7 @@ module CarmineContraption
98
102
  content += %Q(</channel>\n</rss>)
99
103
  (rss_base_path + "#{k}.xml").open('w:UTF-8') { |f| f.write(content) }
100
104
  end
105
+ puts "Updating RSS feed".blue
101
106
  end
102
107
 
103
108
  def write_tag_archives
@@ -106,17 +111,30 @@ module CarmineContraption
106
111
  @tag_archives.keys.each do |k|
107
112
  content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{k.upcase.gsub('_', ' ')}</emph>' CATEGORY</h1>\n<hr class=\"subtleSep\" />"
108
113
  @tag_archives[k].reverse.each { |the_post| content << the_post.formatted_output }
109
- write_file path + "#{k.gsub(' ', '_').downcase}.html", content
114
+ write_file path + "#{k.gsub(' ', '_').downcase}.html", content, true
110
115
  end
111
116
  end
112
117
 
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"
126
+ end
127
+ content += "</section>\n"
128
+ write_file path, content
129
+ end
130
+
113
131
  def write_yearly_archives
114
132
  @yearly_archives.keys.each do |k|
115
133
  path = @destination + k
116
134
  path.mkpath
117
135
  content = "<h1 class=\"archive\">ARCHIVE FOR '<emph>#{k}</emph>'</h1>\n<hr class=\"subtleSep\" />"
118
136
  @yearly_archives[k].each { |the_post| content << the_post.formatted_output }
119
- write_file path+"index.html", content
137
+ write_file path+"index.html", content, true
120
138
  end
121
139
  end
122
140
  end
@@ -0,0 +1,30 @@
1
+ module CarmineContraption
2
+ class Media
3
+ def initialize args={}
4
+ @values = { name: '',
5
+ local_path: '',
6
+ remote_path: ''}.merge args
7
+ end
8
+
9
+ def name
10
+ values[:name]
11
+ end
12
+
13
+ def local_path
14
+ values[:local_path]
15
+ end
16
+
17
+ def remote_path
18
+ values[:remote_path]
19
+ end
20
+
21
+ def update args={}
22
+ Media.new values.merge args
23
+ end
24
+
25
+ private
26
+ def values
27
+ @values
28
+ end
29
+ end
30
+ end
@@ -16,10 +16,11 @@ module CarmineContraption
16
16
  @config = {source: DEFAULT_SOURCE,
17
17
  destination: DEFAULT_DESTINATION,
18
18
  new_post_check: true,
19
- update_repository: true
19
+ update_repository: true,
20
+ override: false
20
21
  }
21
22
  @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 }
23
+ @config.each_pair { |k, v| @config[k] = Pathname.new(v) if v.class == String and !k.to_s.include? 's3' }
23
24
  @build_targets = Hash.new(false)
24
25
  @build_targets[:individual] = true
25
26
  parse(argv)
@@ -65,6 +66,9 @@ module CarmineContraption
65
66
  opts.on("-m", "--build_monthly", "Build monthly indexes of posts") do
66
67
  @build_targets[:monthly] = true
67
68
  end
69
+ opts.on("-o", "--override", "Write over existing files in DESTINATION") do
70
+ @config[:override] = true
71
+ end
68
72
  opts.on("-r", "--build_rss", "Build rss feed") do
69
73
  @build_targets[:rss] = true
70
74
  end
@@ -1,94 +1,138 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'redcarpet'
4
+ require 'colorize'
5
+
3
6
  require_relative 'formatters'
4
7
  require_relative 'month_transform'
8
+ require_relative 'media'
5
9
 
6
10
  module CarmineContraption
7
11
  class Post
8
- attr_accessor :title, :content, :extra, :guid, :summary, :published, :status, :type, :tags
12
+ attr_reader :title, :content, :extra, :guid, :summary, :published, :status, :type, :tags
9
13
 
10
- extend MonthTransform
11
14
  include MonthTransform
12
15
 
13
- def self.from_file path, extension=".html"
14
- guid = File.basename(path, extension)
15
- guid = guid[9..-1]
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
16
+ class << self
17
+ include MonthTransform
18
+
19
+ def from_file path, extension=".html"
20
+ guid = File.basename(path, extension)
21
+ guid = guid[9..-1]
22
+ a = IO.readlines(path)
23
+ new_post = extract_header a
24
+ new_post = new_post.update(content: translate_markdown(new_post.content)) if extension == ".md"
25
+ new_post = new_post.update(original_path: Pathname.new(File.dirname(path)))
26
+ new_post = new_post.update(media: find_media(new_post.content, new_post.original_path))
27
+ new_post.update guid: guid
28
+ end
22
29
 
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|
27
- if line =~ /^\s/
28
- new_post.content = raw_content[(i+1)..-1].join("\n")
29
- break
30
+ private
31
+ def extract_header raw_content
32
+ raw_content = raw_content.split('\n') if raw_content.class == String
33
+ new_post = CarmineContraption::Post.new
34
+ raw_content.each_with_index do |line, i|
35
+ if line =~ /^\s/
36
+ new_post = new_post.update(content: raw_content[(i+1)..-1].join("\n"))
37
+ 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
+ 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
55
+ end
56
+ end
57
+ new_post
58
+ end
59
+
60
+ def ignore_separator line
61
+ line unless line =~ /^=+/
30
62
  end
31
- l = line.split ': '
32
- if l.length == 1
33
- new_post.title ||= ignore_separator l[0]
34
- else
35
- case l[0].downcase
36
- when /type/
37
- new_post.type = l[1]
38
- when /published/
39
- new_post.published = convert_date l[1]
40
- when /tags/
41
- new_post.tags = parse_tags l[1]
42
- when /extra/
43
- new_post.extra = rejoin_string l
44
- when /summary/
45
- new_post.summary = rejoin_string l
63
+
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(' ')
73
+ else
74
+ date
46
75
  end
47
76
  end
48
- end
49
- new_post
50
- end
51
77
 
52
- def self.ignore_separator line
53
- line unless line =~ /^=+/
54
- end
78
+ def find_media content, path
79
+ a = []
80
+ content.scan(/<img src="([^"]*)"/) do |match|
81
+ if !( /http:/ =~ match[0] )
82
+ local_path = Pathname.new(path)
83
+ local_path = local_path.parent + "media" + match[0]
84
+ if local_path.exist?
85
+ a << Media.new(name: local_path.basename,
86
+ local_path: local_path.expand_path)
87
+ else
88
+ puts "#{local_path.expand_path.to_s.red} not found"
89
+ end
90
+ end
91
+ end
92
+ a
93
+ end
55
94
 
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
95
+ def parse_tags unparsed
96
+ unparsed.chomp.downcase.split(', ')
97
+ end
69
98
 
70
- def self.parse_tags unparsed
71
- unparsed.chomp.downcase.split(', ')
72
- end
99
+ def rejoin_string array
100
+ array[1..-1].join ': '
101
+ end
102
+
103
+ def translate_markdown content
104
+ md = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
105
+ md.render(content)
106
+ end
73
107
 
74
- def self.rejoin_string array
75
- array[1..-1].join ': '
76
108
  end
77
109
 
78
- def self.translate_markdown content
79
- `echo "#{content}" | bin/markdown.pl`
110
+ def initialize opts={}
111
+ @defaults = { title: nil,
112
+ content: '',
113
+ extra: '',
114
+ guid: '',
115
+ summary: '',
116
+ published: '',
117
+ status: '',
118
+ type: '',
119
+ tags: [],
120
+ 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]
80
132
  end
81
133
 
82
- def initialize(title, content, extra, guid, summary, published, status, type, tags)
83
- @title = title
84
- @content = content
85
- @extra = extra
86
- @guid = guid
87
- @summary = summary
88
- @published = published
89
- @status = status
90
- @type = type
91
- @tags = tags
134
+ def update opts={}
135
+ Post.new(@defaults.merge opts)
92
136
  end
93
137
 
94
138
  def year
@@ -103,7 +147,20 @@ module CarmineContraption
103
147
  @published[8..9]
104
148
  end
105
149
 
150
+ def <=> other
151
+ published <=> other.published
152
+ end
153
+
154
+ def media
155
+ @defaults[:media]
156
+ end
157
+
158
+ def original_path
159
+ @defaults[:original_path]
160
+ end
161
+
106
162
  def formatted_output method='type'
163
+ insert_remote_media_links
107
164
  formatter = CarmineContraption::RawFormatter.new
108
165
  case method
109
166
  when 'type'
@@ -118,5 +175,12 @@ module CarmineContraption
118
175
  end
119
176
  formatter.format_post self
120
177
  end
178
+
179
+ private
180
+ def insert_remote_media_links
181
+ media.each do |m|
182
+ content.sub!(/(<img src=")#{m.name}"/, '\1'"#{m.remote_path}\"")
183
+ end
184
+ end
121
185
  end
122
186
  end
@@ -1,6 +1,9 @@
1
1
  require_relative 'options'
2
2
  require_relative 'index'
3
+ require_relative 'uploader'
4
+
3
5
  require 'pathname'
6
+ require 'colorize'
4
7
 
5
8
  module CarmineContraption
6
9
  class Runner
@@ -8,6 +11,7 @@ module CarmineContraption
8
11
  @options = Options.new(argv)
9
12
  @source = @options.source
10
13
  @destination = @options.destination
14
+ @override = @options.override
11
15
  @template = Pathname.new('lib/templates/default.html.erb')
12
16
  @input_extensions = [".md", ".html"]
13
17
  @drafts_path = @source + 'drafts'
@@ -25,15 +29,29 @@ module CarmineContraption
25
29
  move_completed_draft = true
26
30
  t = Time.now
27
31
  post += 'Published: ' + t.strftime("%a, %d %b %Y %H:%M:%S %Z") + "\n"
28
- posted_filename = "#{t.strftime('%Y%m%d')}-#{posted_filename}#{input_extension}"
32
+ posted_filename = "#{t.strftime('%Y%m%d')}-#{posted_filename}"
29
33
  else
30
34
  post += line
31
35
  end
32
36
  end
33
37
  if move_completed_draft
34
- (@posts_path+posted_filename).open('w') {|f| f.write(post)}
38
+ puts "Publishing draft: #{posted_filename}".yellow
39
+ (@posts_path+posted_filename).open('w') {|f| f.write(post)}
35
40
  `rm #{i}`
36
41
  `lib/update_repo.sh #{@source} #{posted_filename}` if @options.update_repository
42
+ up = Uploader.new({access_key_id: @options.s3_access_key_id,
43
+ secret_access_key: @options.s3_secret_access_key},
44
+ @options.s3_target_bucket)
45
+ the_post = Post.from_file((@posts_path+posted_filename).to_s, input_extension)
46
+ tmp = []
47
+ the_post.media.each do |m|
48
+ puts "Uploading #{m.name}...".green
49
+ remote_name = "#{the_post.year}/#{the_post.month}/#{m.name}"
50
+ up.upload (m.update remote_path: remote_name)
51
+ tmp << (m.update remote_path: "http://#{@options.s3_target_bucket}/#{remote_name}")
52
+ end
53
+ the_post = the_post.update(media: tmp)
54
+ (@posts_path+posted_filename).open('w') { |f| f.write(the_post.formatted_output '') }
37
55
  end
38
56
  end
39
57
  end
@@ -46,7 +64,7 @@ module CarmineContraption
46
64
  end
47
65
  most_recent = all_paths.sort.last
48
66
  most_recent_extension = most_recent.extname
49
- the_post = Post.from_file(most_recent, ".#{most_recent_extension}")
67
+ the_post = Post.from_file most_recent, most_recent_extension
50
68
  landing_template = Pathname.new('lib/templates/landing_page.html')
51
69
  output = landing_template.open('r').read
52
70
  output.gsub!(/\#\{title\}/, the_post.title)
@@ -54,6 +72,7 @@ module CarmineContraption
54
72
  link = "#{the_post.year}/#{the_post.month}/#{the_post.guid}.html"
55
73
  output.gsub!(/\#\{link\}/, link)
56
74
  (@destination + "index.html").open('w:UTF-8') {|f| f.write(output) }
75
+ puts "Rendering #{@destination}index.html".blue
57
76
  end
58
77
 
59
78
  def build_monthly
@@ -69,6 +88,7 @@ module CarmineContraption
69
88
  def build_tag
70
89
  @the_index.build_tag_archives
71
90
  @the_index.write_tag_archives
91
+ @the_index.write_tag_cloud
72
92
  end
73
93
 
74
94
  def build_individual
@@ -89,7 +109,7 @@ module CarmineContraption
89
109
  def run
90
110
  check_for_new_posts if @options.new_post_check
91
111
  if @options.build_targets.has_value?(true)
92
- @the_index = Index.new @posts_path, @destination, @template
112
+ @the_index = Index.new @posts_path, @destination, @template, @override
93
113
  @the_index.find_all_posts
94
114
  @options.build_targets.each { |k, v| send "build_#{k}" if v }
95
115
  end