CarmineContraption 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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