woody 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/woody +14 -9
- data/lib/woody/compiler.rb +76 -68
- data/lib/woody/deployer.rb +17 -15
- data/lib/woody/episode.rb +20 -44
- data/lib/woody/generator.rb +9 -8
- data/lib/woody/post.rb +63 -0
- data/lib/woody/version.rb +2 -2
- data/lib/woody.rb +41 -15
- data/templates/feed.xml +16 -15
- data/templates/index.html +3 -3
- data/templates/layout.html +4 -3
- data/templates/post.html +19 -0
- data/woody.gemspec +2 -0
- metadata +36 -3
- data/templates/episode.html +0 -16
data/bin/woody
CHANGED
@@ -10,12 +10,17 @@ program :description, 'Podcast static site generator'
|
|
10
10
|
|
11
11
|
default_command :compile
|
12
12
|
|
13
|
+
$dir = "."
|
14
|
+
global_option '--dir', '--dir path/to/site', "Specifies path to Woody site directory, if not the current directory" do |dir|
|
15
|
+
$dir = dir
|
16
|
+
end
|
17
|
+
|
13
18
|
command :compile do |c|
|
14
19
|
c.description = "Compiles the site"
|
15
20
|
c.option "--no-add", "Don't ask to add new metadata"
|
16
21
|
c.action do |args, options|
|
17
|
-
Woody.
|
18
|
-
|
22
|
+
site = Woody.new($dir)
|
23
|
+
site.compile(options)
|
19
24
|
end
|
20
25
|
end
|
21
26
|
alias_command :c, :compile
|
@@ -23,8 +28,8 @@ alias_command :c, :compile
|
|
23
28
|
command :deploy do |c|
|
24
29
|
c.description = "Deploys the site"
|
25
30
|
c.action do |args, options|
|
26
|
-
Woody.
|
27
|
-
|
31
|
+
site = Woody.new($dir)
|
32
|
+
site.deploy
|
28
33
|
end
|
29
34
|
end
|
30
35
|
alias_command :d, :deploy
|
@@ -33,9 +38,9 @@ command :cd do |c|
|
|
33
38
|
c.description = "Compiles then deploys the site"
|
34
39
|
c.option "--no-add", "Don't ask to add new metadata"
|
35
40
|
c.action do |args, options|
|
36
|
-
Woody.
|
37
|
-
|
38
|
-
|
41
|
+
site = Woody.new($dir)
|
42
|
+
site.compile(options)
|
43
|
+
site.deploy
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
@@ -54,8 +59,8 @@ end
|
|
54
59
|
command :'update templates' do |c|
|
55
60
|
c.description = "Sets template files to current default. DESTRUCTIVE!"
|
56
61
|
c.action do |args, options|
|
57
|
-
Woody.
|
58
|
-
|
62
|
+
site = Woody.new($dir)
|
63
|
+
site.update_templates
|
59
64
|
end
|
60
65
|
end
|
61
66
|
alias_command :'templates update', :'update templates'
|
data/lib/woody/compiler.rb
CHANGED
@@ -1,38 +1,48 @@
|
|
1
|
-
|
1
|
+
require 'preamble'
|
2
|
+
|
3
|
+
class Woody
|
2
4
|
# Handles functions related to compiling the Woody site
|
3
5
|
module Compiler
|
4
|
-
@@touchedfiles = []
|
5
6
|
|
6
7
|
# Compiles the Woody site
|
7
|
-
def
|
8
|
+
def compile(options = nil)
|
8
9
|
puts "Compiling..."
|
9
|
-
meta = YAML.load_file("content/metadata.yml")
|
10
|
+
meta = YAML.load_file(dir("content/metadata.yml"))
|
10
11
|
|
11
12
|
# instantiate the metadata hash so shit doesn't explode in our faces
|
12
|
-
|
13
|
-
if meta == false
|
14
|
-
meta = Hash.new
|
15
|
-
end
|
13
|
+
meta = Hash.new if meta == false
|
16
14
|
|
17
|
-
|
18
|
-
filesfound
|
19
|
-
Dir.glob('content/*.mp3') do |file|
|
20
|
-
filename = file[8..-1]
|
15
|
+
posts = Array.new
|
16
|
+
filesfound = Array.new
|
17
|
+
Dir.glob(dir('content/*.mp3')) do |file|
|
18
|
+
filename = undir(file)[8..-1]
|
21
19
|
unless meta == false or meta[filename].nil?
|
22
20
|
# Episode metadata already stored
|
23
|
-
|
21
|
+
posts << Episode.new_from_meta(self, filename, meta[filename])
|
24
22
|
filesfound << filename
|
25
23
|
else
|
26
24
|
# No episode metadata stored for this yet
|
27
|
-
unless options.no_add == false # Seemingly backwards, I know...
|
28
|
-
prompt_metadata(meta,
|
25
|
+
unless options.nil? or options.no_add == false # Seemingly backwards, I know...
|
26
|
+
prompt_metadata(meta, posts, filesfound, filename)
|
29
27
|
else
|
30
28
|
puts "Warning: found media file #{filename} but no metadata. Will not be published."
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
35
|
-
|
33
|
+
# Process blog posts
|
34
|
+
Dir.glob(dir('content/posts/*')) do |file|
|
35
|
+
filename = undir(file)[8..-1]
|
36
|
+
data = Preamble.load(file)
|
37
|
+
m = data[0]
|
38
|
+
body = data[1]
|
39
|
+
|
40
|
+
posts << Post.new(self, filename, m['title'], m['subtitle'], body, Date.parse(m['date'].to_s), m['tags'])
|
41
|
+
end
|
42
|
+
|
43
|
+
posts.sort_by! do |post|
|
44
|
+
post.date
|
45
|
+
end.reverse!
|
36
46
|
|
37
47
|
# Check for files in meta but not found
|
38
48
|
unless meta.empty?
|
@@ -43,40 +53,41 @@ module Woody
|
|
43
53
|
end
|
44
54
|
|
45
55
|
# Make sure necessary directories exist
|
46
|
-
FileUtils.mkdir_p('output/assets') unless File.directory?('output/assets')
|
47
|
-
FileUtils.mkdir_p('output/assets/mp3') unless File.directory?('output/assets/mp3')
|
48
|
-
FileUtils.mkdir_p('output/
|
56
|
+
FileUtils.mkdir_p(dir 'output/assets') unless File.directory?(dir 'output/assets')
|
57
|
+
FileUtils.mkdir_p(dir 'output/assets/mp3') unless File.directory?(dir 'output/assets/mp3')
|
58
|
+
FileUtils.mkdir_p(dir 'output/post') unless File.directory?(dir 'output/post')
|
49
59
|
|
50
60
|
# Copy over (TODO: and process) MP3 files
|
51
|
-
|
52
|
-
|
61
|
+
posts.each do |post|
|
62
|
+
next unless post.has_file?
|
63
|
+
copy_file_to_output dir(File.join("content", post.filename)), File.join("assets/mp3", post.compiledname)
|
53
64
|
end
|
54
65
|
|
55
66
|
# Copy over assets
|
56
67
|
copy_assets
|
57
68
|
|
58
69
|
# Update index.html
|
59
|
-
layout = File.read('templates/layout.html')
|
70
|
+
layout = File.read(dir 'templates/layout.html')
|
60
71
|
layout = Erubis::Eruby.new(layout)
|
61
72
|
|
62
|
-
index_compiled = layout.result(config:
|
63
|
-
index = Erubis::Eruby.new(File.read('templates/index.html'))
|
64
|
-
index.result(config:
|
65
|
-
ep = Erubis::Eruby.new(File.read('templates/
|
66
|
-
ep.result(config:
|
73
|
+
index_compiled = layout.result(config: @config) do
|
74
|
+
index = Erubis::Eruby.new(File.read(dir 'templates/index.html'))
|
75
|
+
index.result(config: @config, posts: posts) do |post|
|
76
|
+
ep = Erubis::Eruby.new(File.read(dir 'templates/post.html'))
|
77
|
+
ep.result(config: @config, posts: posts, post: post)
|
67
78
|
end
|
68
79
|
end
|
69
80
|
write_output_file('index.html') {|f| f.write(index_compiled) }
|
70
81
|
|
71
|
-
# Update
|
72
|
-
|
73
|
-
layout = File.read('templates/layout.html')
|
82
|
+
# Update post pages
|
83
|
+
posts.each do |post|
|
84
|
+
layout = File.read(dir 'templates/layout.html')
|
74
85
|
layout = Erubis::Eruby.new(layout)
|
75
|
-
|
76
|
-
ep = Erubis::Eruby.new(File.read('templates/
|
77
|
-
ep.result(config:
|
86
|
+
post_compiled = layout.result(config: @config) do
|
87
|
+
ep = Erubis::Eruby.new(File.read(dir 'templates/post.html'))
|
88
|
+
ep.result(config: @config, posts: posts, post: post)
|
78
89
|
end
|
79
|
-
write_output_file(
|
90
|
+
write_output_file(post.path!) {|f| f.write(post_compiled) }
|
80
91
|
end
|
81
92
|
|
82
93
|
# Copy over iTunes.png
|
@@ -87,44 +98,24 @@ module Woody
|
|
87
98
|
end
|
88
99
|
|
89
100
|
# Update (iTunes) RSS
|
90
|
-
|
101
|
+
@config['itunes']['explicit'] = "no" if @config['itunes']['explicit'].nil?
|
91
102
|
feedxml = File.read File.join($source_root, "feed.xml") # Use feed.xml template in gem, not in site's template folder.
|
92
103
|
feed = Erubis::Eruby.new(feedxml)
|
93
|
-
feed_compiled = feed.result(config:
|
104
|
+
feed_compiled = feed.result(config: @config, posts: posts)
|
94
105
|
write_output_file("feed.xml") {|f| f.write(feed_compiled) }
|
95
106
|
|
96
107
|
|
97
|
-
|
98
|
-
# TODO: Update General RSS
|
99
|
-
|
100
|
-
|
101
108
|
# Purge left over files
|
102
109
|
purge_output
|
103
110
|
|
104
111
|
# Remove all empty directories from the output
|
105
|
-
Dir['output/**/*'].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w[ . .. ]).empty? }.each { |d| Dir.rmdir d }
|
112
|
+
Dir[dir 'output/**/*'].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w[ . .. ]).empty? }.each { |d| Dir.rmdir d }
|
106
113
|
end
|
107
114
|
|
108
115
|
private
|
109
116
|
|
110
|
-
# Safely copies files to the output directory
|
111
|
-
def self.copy_file_to_output(source, destination)
|
112
|
-
d = File.join("output", destination)
|
113
|
-
FileUtils.copy source, d
|
114
|
-
@@touchedfiles << d
|
115
|
-
end
|
116
|
-
|
117
|
-
# Safely writes files to the output directory
|
118
|
-
def self.write_output_file(filename, &block)
|
119
|
-
file = File.join("output", filename)
|
120
|
-
File.open(file, 'w') do |f|
|
121
|
-
yield f
|
122
|
-
end
|
123
|
-
@@touchedfiles << file
|
124
|
-
end
|
125
|
-
|
126
117
|
# Prompts for metadata for new media files
|
127
|
-
def
|
118
|
+
def prompt_metadata(meta, posts, filesfound, filename)
|
128
119
|
puts "Found new media file: #{filename}"
|
129
120
|
if agree("Add metadata for this file? ")
|
130
121
|
m = Hash.new
|
@@ -136,7 +127,7 @@ module Woody
|
|
136
127
|
m['explicit'] = agree "Explicit content?: "
|
137
128
|
|
138
129
|
meta[filename] = m
|
139
|
-
|
130
|
+
posts << Episode.new(self, filename, m['title'], Date.parse(m['date'].to_s), m['synopsis'], m['subtitle'], m['tags'], m['explicit'])
|
140
131
|
filesfound << filename
|
141
132
|
|
142
133
|
write_meta meta
|
@@ -146,27 +137,44 @@ module Woody
|
|
146
137
|
end
|
147
138
|
|
148
139
|
# Writes the metadata file
|
149
|
-
def
|
150
|
-
File.open(
|
140
|
+
def write_meta(meta)
|
141
|
+
File.open(dir('content/metadata.yml'), 'w' ) do |out|
|
151
142
|
YAML.dump meta, out
|
152
143
|
end
|
153
144
|
end
|
154
145
|
|
146
|
+
# Safely copies files to the output directory
|
147
|
+
def copy_file_to_output(source, destination)
|
148
|
+
d = dir(File.join("output", destination))
|
149
|
+
FileUtils.copy source, d
|
150
|
+
@touchedfiles << undir(d)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Safely writes files to the output directory
|
154
|
+
def write_output_file(filename, &block)
|
155
|
+
file = dir(File.join("output", filename))
|
156
|
+
File.open(file, 'w') do |f|
|
157
|
+
yield f
|
158
|
+
end
|
159
|
+
@touchedfiles << undir(file)
|
160
|
+
end
|
161
|
+
|
162
|
+
|
155
163
|
# Copies custom assets to output
|
156
|
-
def
|
157
|
-
Dir.glob "templates/assets/**/*" do |item|
|
164
|
+
def copy_assets
|
165
|
+
Dir.glob dir("templates/assets/**/*") do |item|
|
158
166
|
next if File.directory? item
|
159
|
-
d = item
|
167
|
+
d = undir item
|
168
|
+
d = d[10..-1] # Cut off "templates/" at beginning
|
160
169
|
copy_file_to_output item, d
|
161
170
|
end
|
162
171
|
end
|
163
172
|
|
164
|
-
|
165
173
|
# Deletes old files from the site's output directory
|
166
|
-
def
|
167
|
-
Dir.glob "output/**/*" do |item|
|
174
|
+
def purge_output
|
175
|
+
Dir.glob dir("output/**/*") do |item|
|
168
176
|
next if File.directory? item
|
169
|
-
File.delete item unless
|
177
|
+
File.delete item unless @touchedfiles.include? undir(item)
|
170
178
|
end
|
171
179
|
end
|
172
180
|
|
data/lib/woody/deployer.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
|
1
|
+
class Woody
|
2
2
|
# Handles functions relating to deploying the Woody site
|
3
3
|
module Deployer
|
4
|
-
@@touchedfiles = []
|
5
4
|
# Deploys the Woody site to S3
|
6
|
-
def
|
5
|
+
def deploy
|
7
6
|
puts "Deploying..."
|
8
7
|
|
9
|
-
Dir.glob "output/**/*" do |item|
|
8
|
+
Dir.glob dir("output/**/*") do |item|
|
10
9
|
next if File.directory? item
|
10
|
+
item = undir item
|
11
11
|
name = item[7..-1] # Remove "output/"
|
12
12
|
next if name == "index.html" # These *must* be left until last
|
13
13
|
next if name == "feed.xml"
|
@@ -26,17 +26,17 @@ module Woody
|
|
26
26
|
|
27
27
|
# Deletes old objects from the S3 bucket
|
28
28
|
# @param [Array] touchedfiles specifies the S3 objects to keep
|
29
|
-
def
|
30
|
-
bucket = AWS::S3::Bucket.find
|
31
|
-
prefix =
|
29
|
+
def purge_bucket
|
30
|
+
bucket = AWS::S3::Bucket.find @bucketname
|
31
|
+
prefix = @config['s3']['prefix']
|
32
32
|
if prefix.nil?
|
33
33
|
bucket.objects.each do |object|
|
34
|
-
object.delete unless
|
34
|
+
object.delete unless @s3touchedobjects.include? object.key
|
35
35
|
end
|
36
36
|
else
|
37
37
|
bucket.objects.each do |object|
|
38
38
|
if object.key.start_with? prefix # If using a prefix, don't delete anything outside of that 'subdirectory'
|
39
|
-
object.delete unless
|
39
|
+
object.delete unless @s3touchedobjects.include? object.key
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
@@ -49,18 +49,20 @@ module Woody
|
|
49
49
|
# Prints notices to STDOUT
|
50
50
|
# @param [String] objectname specifies the S3 object's key/name
|
51
51
|
# @param [String] filepath specifies the path to the file to upload
|
52
|
-
def
|
53
|
-
prefix =
|
52
|
+
def upload(objectname, filepath)
|
53
|
+
prefix = @config['s3']['prefix']
|
54
54
|
unless prefix.nil?
|
55
55
|
objectname = File.join(prefix, objectname)
|
56
56
|
end
|
57
57
|
|
58
|
+
filepath = dir(filepath)
|
59
|
+
|
58
60
|
# Generate hash of file
|
59
61
|
hash = filehash filepath
|
60
|
-
|
62
|
+
@s3touchedobjects << objectname
|
61
63
|
# Get hash of version already uploaded, if available.
|
62
64
|
begin
|
63
|
-
object = AWS::S3::S3Object.find objectname,
|
65
|
+
object = AWS::S3::S3Object.find objectname, @bucketname
|
64
66
|
oldhash = object.metadata['hash']
|
65
67
|
rescue AWS::S3::NoSuchKey
|
66
68
|
# File not uploaded yet
|
@@ -69,7 +71,7 @@ module Woody
|
|
69
71
|
unless hash == oldhash
|
70
72
|
# Don't reupload if file hasn't changed
|
71
73
|
puts "#{objectname}: Uploading"
|
72
|
-
AWS::S3::S3Object.store(objectname, open(filepath),
|
74
|
+
AWS::S3::S3Object.store(objectname, open(filepath), @bucketname, access: :public_read, 'x-amz-meta-hash' => hash)
|
73
75
|
else
|
74
76
|
puts "#{objectname}: Not uploading, hasn't changed since last time."
|
75
77
|
end
|
@@ -80,7 +82,7 @@ module Woody
|
|
80
82
|
# Stored in S3 object metadata (x-amz-meta-hash) and used to avoid re-uploading unchanged files
|
81
83
|
# @param [String] filepath path to file
|
82
84
|
# @return [String] hash of file
|
83
|
-
def
|
85
|
+
def filehash(filepath)
|
84
86
|
sha1 = Digest::SHA1.new
|
85
87
|
File.open(filepath) do|file|
|
86
88
|
buffer = ''
|
data/lib/woody/episode.rb
CHANGED
@@ -1,57 +1,36 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require "woody/post"
|
2
|
+
|
3
|
+
class Woody
|
4
|
+
# Represents an episode of the podcast. Inherits from Post.
|
5
|
+
class Episode < Post
|
4
6
|
# Creates a new Episode object from segment of metadata.yml
|
5
7
|
# @param [String] filename specifies the name of the MP3 file
|
6
8
|
# @param [Hash] meta is the relevant part of metadata.yml
|
7
9
|
# @return [Episode] the new Episode object
|
8
|
-
def self.new_from_meta(filename, meta)
|
9
|
-
return Episode.new(filename, meta['title'], Date.parse(meta['date'].to_s), meta['synopsis'], meta['subtitle'], meta['tags'], meta['explicit'])
|
10
|
+
def self.new_from_meta(site, filename, meta)
|
11
|
+
return Episode.new(site, filename, meta['title'], Date.parse(meta['date'].to_s), meta['synopsis'], meta['subtitle'], meta['tags'], meta['explicit'])
|
10
12
|
end
|
11
13
|
|
12
14
|
# Creates a new Episode object
|
13
15
|
# @param [String] filename specifies the name of the MP3 file
|
14
16
|
# @param [String] title specifies the episode's title
|
15
17
|
# @param [Date] date specifies the episode's published date
|
16
|
-
# @param [String]
|
18
|
+
# @param [String] body specifies the episode's synopsis/body
|
17
19
|
# @param [String] subtitle specifies the episode's subtitle
|
18
20
|
# @param [Array] tags specifies the episode's tags - each element is a String
|
19
21
|
# @return [Episode] the new Episode object
|
20
|
-
def initialize(filename, title, date,
|
21
|
-
|
22
|
-
@title = title
|
23
|
-
@date = date
|
24
|
-
@synopsis = synopsis
|
25
|
-
@subtitle = subtitle
|
26
|
-
@tags = tags
|
22
|
+
def initialize(site, filename, title, date, raw_body, subtitle = nil, tags = [], explicit = false)
|
23
|
+
super site, filename, title, subtitle, raw_body, date, tags
|
27
24
|
@explicit = explicit
|
28
|
-
@compiledname = @filename.gsub(/[^0-9A-Za-z .]/, '').gsub(' ', '_')
|
25
|
+
@compiledname = @filename.gsub(/[^0-9A-Za-z ._]/, '').gsub(' ', '_')
|
29
26
|
end
|
30
27
|
|
31
|
-
attr_accessor :
|
32
|
-
|
33
|
-
# @return the episode's page URL where possible, otherwise false
|
34
|
-
def url
|
35
|
-
return "#{$config['urlbase']}#{path!}" unless path! == false
|
36
|
-
return false
|
37
|
-
end
|
28
|
+
attr_accessor :explicit
|
38
29
|
|
39
|
-
# @return the episode's page path! where possible, otherwise false. Does not take prefix in to account.
|
40
|
-
def path!(leader=true)
|
41
|
-
return "#{leader ? "/" : ""}episode/#{@compiledname[0..-5]}.html" unless @compiledname.nil?
|
42
|
-
return false
|
43
|
-
end
|
44
|
-
|
45
|
-
# @return the episode's page path! where possible, otherwise false. Includes the site prefix if enabled.
|
46
|
-
def path(leader=true)
|
47
|
-
prefix = $config['s3']['prefix']
|
48
|
-
return "#{leader ? "/" : ""}#{prefix.nil? ? "" : prefix + "/" }episode/#{@compiledname[0..-5]}.html" unless @compiledname.nil?
|
49
|
-
return false
|
50
|
-
end
|
51
30
|
|
52
31
|
# @return the episode's media file URL where possible, otherwise false
|
53
32
|
def file_url
|
54
|
-
return "#{
|
33
|
+
return "#{@site.config['urlbase']}#{file_path!}" unless file_path! == false
|
55
34
|
return false
|
56
35
|
end
|
57
36
|
|
@@ -63,23 +42,19 @@ module Woody
|
|
63
42
|
|
64
43
|
# @return the episode's media file path! where possible, otherwise false. Includes site prefix if enabled.
|
65
44
|
def file_path(leader=true)
|
66
|
-
prefix =
|
45
|
+
prefix = @site.config['s3']['prefix']
|
67
46
|
return "#{leader ? "/" : ""}#{prefix.nil? ? "" : prefix + "/" }assets/mp3/#{@compiledname}" unless @compiledname.nil?
|
68
47
|
return false
|
69
48
|
end
|
70
49
|
|
71
50
|
|
72
|
-
# @return [String] a comma separated list of tags, or nil if no tags
|
73
|
-
def keywords
|
74
|
-
@tags.join ', ' unless @tags.nil? or @tags.empty?
|
75
|
-
end
|
76
51
|
|
77
52
|
# @return [Integer] the size of the episodes media file in bytes
|
78
53
|
def size
|
79
|
-
File.size File.join("content", filename)
|
54
|
+
File.size @site.dir(File.join("content", filename))
|
80
55
|
end
|
81
56
|
|
82
|
-
# @return [String] 'yes' if explicit content, otherwise
|
57
|
+
# @return [String] 'yes' if explicit content, otherwise 'no'
|
83
58
|
def explicit_string
|
84
59
|
@explicit ? 'yes' : 'no'
|
85
60
|
end
|
@@ -88,7 +63,7 @@ module Woody
|
|
88
63
|
def duration
|
89
64
|
return @duration unless @duration.nil?
|
90
65
|
length = 0
|
91
|
-
Mp3Info.open(File.join("content", filename)) do |mp3|
|
66
|
+
Mp3Info.open(@site.dir(File.join("content", filename))) do |mp3|
|
92
67
|
length = mp3.length
|
93
68
|
end
|
94
69
|
@duration = Time.at(length).gmtime.strftime('%R:%S') # Should work up to 24 hours
|
@@ -97,8 +72,9 @@ module Woody
|
|
97
72
|
end
|
98
73
|
end
|
99
74
|
|
100
|
-
|
101
|
-
|
75
|
+
|
76
|
+
def has_file?
|
77
|
+
true
|
102
78
|
end
|
103
79
|
|
104
80
|
end
|
data/lib/woody/generator.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
class Woody
|
2
2
|
# Handles functions related to generating Woody sites and updating them and their data stores
|
3
3
|
module Generator
|
4
4
|
# Generates a blank skeleton Woody site
|
@@ -17,7 +17,7 @@ module Woody
|
|
17
17
|
cdir_p("#{name}/templates")
|
18
18
|
cpy_t("layout.html", "#{name}/templates/layout.html")
|
19
19
|
cpy_t("index.html", "#{name}/templates/index.html")
|
20
|
-
cpy_t("
|
20
|
+
cpy_t("post.html", "#{name}/templates/post.html")
|
21
21
|
|
22
22
|
cdir_p("#{name}/templates/assets")
|
23
23
|
cpy_t("stylesheet.css", "#{name}/templates/assets/stylesheet.css")
|
@@ -25,23 +25,24 @@ module Woody
|
|
25
25
|
cdir_p("#{name}/content")
|
26
26
|
cpy_t("metadata.yml", "#{name}/content/metadata.yml")
|
27
27
|
cpy_t("iTunes.png", "#{name}/content/iTunes.png")
|
28
|
+
cdir_p("#{name}/content/posts")
|
28
29
|
|
29
30
|
cdir_p("#{name}/output")
|
30
31
|
cdir_p("#{name}/output/assets")
|
31
32
|
cdir_p("#{name}/output/assets/mp3")
|
32
|
-
cdir_p("#{name}/output/
|
33
|
+
cdir_p("#{name}/output/post")
|
33
34
|
|
34
35
|
puts "Done!"
|
35
36
|
puts "Now, do `cd #{name}` then edit the config file, woody-config.yml."
|
36
37
|
end
|
37
38
|
|
38
39
|
# Replaces the templates in the Woody site with the gem's current default ones
|
39
|
-
def self.update_templates
|
40
|
+
def self.update_templates(site)
|
40
41
|
puts "Updating templates..."
|
41
|
-
cpy_t("layout.html", "templates/layout.html")
|
42
|
-
cpy_t("index.html", "templates/index.html")
|
43
|
-
cpy_t("
|
44
|
-
cpy_t("stylesheet.css", "templates/assets/stylesheet.css")
|
42
|
+
cpy_t("layout.html", site.dir("templates/layout.html"))
|
43
|
+
cpy_t("index.html", site.dir("templates/index.html"))
|
44
|
+
cpy_t("post.html", site.dir("templates/post.html"))
|
45
|
+
cpy_t("stylesheet.css", site.dir("templates/assets/stylesheet.css"))
|
45
46
|
puts "Done! Thanks for updating :)"
|
46
47
|
end
|
47
48
|
|
data/lib/woody/post.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
|
3
|
+
class Woody
|
4
|
+
# Represents a post
|
5
|
+
class Post
|
6
|
+
# Creates a new Post object
|
7
|
+
# @param [String] filename specifies the name of the post file
|
8
|
+
# @param [String] title specifies the Post's title
|
9
|
+
# @param [String] subtitle specifies the Post's subtitle
|
10
|
+
# @param [String] body specifies the Post's body
|
11
|
+
# @param [Date] date specifies the Post's published date
|
12
|
+
# @param [Array] tags specifies the Post's tags - each element is a String
|
13
|
+
# @return [Post] the new Post object
|
14
|
+
def initialize(site, filename, title, subtitle, raw_body, date, tags = [], compiledname = nil)
|
15
|
+
@site = site
|
16
|
+
@filename = filename
|
17
|
+
@title = title
|
18
|
+
@subtitle = subtitle
|
19
|
+
@raw_body = raw_body
|
20
|
+
@date = date
|
21
|
+
@tags = tags.nil? ? [] : tags
|
22
|
+
@compiledname = @filename[6..-1].gsub(/[^0-9A-Za-z ._]/, '').gsub(' ', '_')
|
23
|
+
end
|
24
|
+
attr_accessor :filename, :title, :subtitle, :raw_body, :date, :tags, :compiledname
|
25
|
+
|
26
|
+
def body(regenerate = false)
|
27
|
+
return @body unless @body.nil? or regenerate
|
28
|
+
return @body = Kramdown::Document.new(@raw_body).to_html
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return the Page's page URL where possible, otherwise false
|
32
|
+
def url
|
33
|
+
return "#{@site.config['urlbase']}#{path!}" unless path! == false
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return the Page's page path where possible, otherwise false. Does not take prefix in to account.
|
38
|
+
def path!(leader=true)
|
39
|
+
return "#{leader ? "/" : ""}post/#{@compiledname.chomp(File.extname(@compiledname))}.html" unless @compiledname.nil?
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return the Page's page path where possible, otherwise false. Includes the site prefix if enabled.
|
44
|
+
def path(leader=true)
|
45
|
+
prefix = @site.config['s3']['prefix']
|
46
|
+
return "#{leader ? "/" : ""}#{prefix.nil? ? "" : prefix + "/" }post/#{@compiledname.chomp(File.extname(@compiledname))}.html" unless @compiledname.nil?
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String] a comma separated list of tags, or nil if no tags
|
51
|
+
def keywords
|
52
|
+
@tags.join ', ' unless @tags.nil? or @tags.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_file?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def <=> (other)
|
60
|
+
other.date <=> self.date
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/woody/version.rb
CHANGED
data/lib/woody.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "woody/version"
|
2
|
+
require "woody/post"
|
2
3
|
require "woody/episode"
|
3
4
|
require "woody/compiler"
|
4
5
|
require "woody/deployer"
|
@@ -19,34 +20,45 @@ require 'mp3info'
|
|
19
20
|
$VERBOSE = oldverbosity
|
20
21
|
|
21
22
|
# Woody podcast static site generator
|
22
|
-
|
23
|
+
class Woody
|
24
|
+
include Generator
|
25
|
+
include Compiler
|
26
|
+
include Deployer
|
27
|
+
|
28
|
+
|
23
29
|
# Path of template directory inside gem
|
24
30
|
$source_root = File.expand_path("../../templates", __FILE__)
|
25
31
|
|
26
32
|
# Load configuration and connect to S3
|
27
|
-
def
|
33
|
+
def initialize(directory = ".")
|
34
|
+
@directory = directory
|
35
|
+
@touchedfiles = []
|
36
|
+
@s3touchedobjects = []
|
37
|
+
|
38
|
+
|
39
|
+
|
28
40
|
begin
|
29
|
-
|
41
|
+
@config = YAML.load_file(dir("woody-config.yml"))
|
30
42
|
rescue Errno::ENOENT
|
31
43
|
puts "This doesn't look like a valid Woody site directory!"
|
32
44
|
exit!
|
33
45
|
end
|
34
46
|
|
35
47
|
# Strip trailing slash from urlbase, if present.
|
36
|
-
if
|
37
|
-
|
48
|
+
if @config['urlbase'].end_with? "/"
|
49
|
+
@config['urlbase'] = @config['urlbase'][0..-2]
|
38
50
|
end
|
39
51
|
|
40
|
-
if
|
41
|
-
prefix =
|
52
|
+
if @config['distributiontype'] == "s3"
|
53
|
+
prefix = @config['s3']['prefix']
|
42
54
|
unless prefix.nil?
|
43
|
-
|
55
|
+
@config['urlbase'] = @config['urlbase'] + "/" + prefix
|
44
56
|
end
|
45
57
|
end
|
46
58
|
|
47
|
-
|
48
|
-
:access_key_id =>
|
49
|
-
:secret_access_key =>
|
59
|
+
s3options = {
|
60
|
+
:access_key_id => @config['s3']['accesskey']['id'],
|
61
|
+
:secret_access_key => @config['s3']['accesskey']['secret']
|
50
62
|
}
|
51
63
|
|
52
64
|
unless ENV['http_proxy'].nil?
|
@@ -56,14 +68,28 @@ module Woody
|
|
56
68
|
p[:port] = uri.port
|
57
69
|
p[:user] = uri.user unless uri.user.nil?
|
58
70
|
p[:password] = uri.password unless uri.password.nil?
|
59
|
-
|
71
|
+
s3options[:proxy] = p
|
60
72
|
end
|
61
73
|
|
62
|
-
AWS::S3::Base.establish_connection!(
|
63
|
-
AWS::S3::DEFAULT_HOST.replace
|
64
|
-
|
74
|
+
AWS::S3::Base.establish_connection!(s3options)
|
75
|
+
AWS::S3::DEFAULT_HOST.replace @config['s3']['hostname']
|
76
|
+
@bucketname = @config['s3']['bucket']
|
65
77
|
end
|
66
78
|
|
79
|
+
attr_accessor :config
|
80
|
+
attr_reader :directory
|
81
|
+
|
82
|
+
def dir(dir="")
|
83
|
+
File.expand_path(File.join(@directory, dir))
|
84
|
+
end
|
85
|
+
|
86
|
+
def undir(string)
|
87
|
+
string[dir.length+1..-1]
|
88
|
+
end
|
89
|
+
|
90
|
+
def update_templates
|
91
|
+
Generator::update_templates(self)
|
92
|
+
end
|
67
93
|
end
|
68
94
|
|
69
95
|
|
data/templates/feed.xml
CHANGED
@@ -17,27 +17,28 @@
|
|
17
17
|
<itunes:email><%= config['itunes']['owner']['email'] %></itunes:email>
|
18
18
|
</itunes:owner>
|
19
19
|
|
20
|
-
<itunes:image href="<%=
|
21
|
-
<itunes:category text="<%=
|
20
|
+
<itunes:image href="<%= config['urlbase'] + "/assets/iTunes.png" %>"/>
|
21
|
+
<itunes:category text="<%= config['itunes']['category'] %>"/>
|
22
22
|
|
23
|
-
<atom:link href="<%=
|
23
|
+
<atom:link href="<%= config['urlbase'] %>/feed.xml" rel="self" type="application/rss+xml" />
|
24
24
|
|
25
25
|
|
26
|
-
<%-
|
26
|
+
<%- posts.each do |post| %>
|
27
|
+
<%- next unless post.has_file? %>
|
27
28
|
<item>
|
28
|
-
<title><%=
|
29
|
-
<itunes:subtitle><%=
|
30
|
-
<itunes:summary><%=
|
31
|
-
<pubDate><%=
|
32
|
-
<itunes:duration><%=
|
33
|
-
<itunes:keywords><%=
|
34
|
-
<itunes:explicit><%=
|
35
|
-
<description><%=
|
36
|
-
<enclosure url="<%=
|
37
|
-
<guid><%=
|
29
|
+
<title><%= post.title %></title>
|
30
|
+
<itunes:subtitle><%= post.subtitle %></itunes:subtitle>
|
31
|
+
<itunes:summary><%= post.body %></itunes:summary>
|
32
|
+
<pubDate><%= post.date.rfc2822 %></pubDate>
|
33
|
+
<itunes:duration><%= post.duration %></itunes:duration>
|
34
|
+
<itunes:keywords><%= post.keywords %></itunes:keywords>
|
35
|
+
<itunes:explicit><%= post.explicit_string %></itunes:explicit>
|
36
|
+
<description><%= post.body %></description>
|
37
|
+
<enclosure url="<%= post.file_url %>" length="<%= post.size %>" type="audio/mpeg"/>
|
38
|
+
<guid><%= post.url %></guid>
|
38
39
|
<category>Podcasts</category>
|
39
40
|
</item>
|
40
|
-
|
41
|
+
<% end %>
|
41
42
|
|
42
43
|
</channel>
|
43
44
|
</rss>
|
data/templates/index.html
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<h2>Episodes:</h2>
|
2
|
-
<%= link_to "RSS Feed", "#{
|
3
|
-
<%
|
2
|
+
<%= link_to "RSS Feed", "#{ config['urlbase'] }/feed.xml" %>
|
3
|
+
<% posts.each do |post| %>
|
4
4
|
<div class="episode">
|
5
|
-
<%= yield
|
5
|
+
<%= yield post %>
|
6
6
|
</div>
|
7
7
|
<% end %>
|
data/templates/layout.html
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html>
|
3
3
|
<head>
|
4
|
-
<
|
4
|
+
<meta charset="UTF-8" />
|
5
|
+
<title><%= config['title'] %></title>
|
5
6
|
<%= generator_meta_tag %>
|
6
7
|
<link rel="stylesheet" type="text/css" href="/assets/stylesheet.css" />
|
7
8
|
</head>
|
8
9
|
<body>
|
9
|
-
<h1><%= link_to
|
10
|
-
<h2><%=
|
10
|
+
<h1><%= link_to config['title'], config['urlbase'] %></h1>
|
11
|
+
<h2><%= config['subtitle'] %></h2>
|
11
12
|
<hr />
|
12
13
|
<%= yield %>
|
13
14
|
</body>
|
data/templates/post.html
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
<h3><%= link_to post.title, post.path %></h3>
|
2
|
+
<h4><%= post.subtitle %></h4>
|
3
|
+
|
4
|
+
<%- if post.has_file? %>
|
5
|
+
Player goes here
|
6
|
+
|
7
|
+
<a href="<%= post.file_path %>">Download MP3</a>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<div class="synopsis body">
|
11
|
+
<%= post.body %>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="tags">
|
15
|
+
<strong>Tags:</strong>
|
16
|
+
<% post.tags.each do |tag| %>
|
17
|
+
<span class="tag"><%= tag %> </span>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
data/woody.gemspec
CHANGED
@@ -22,6 +22,8 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.add_runtime_dependency 'aws-s3'
|
23
23
|
gem.add_runtime_dependency 'commander'
|
24
24
|
gem.add_runtime_dependency 'highline'
|
25
|
+
gem.add_runtime_dependency 'preamble'
|
26
|
+
gem.add_runtime_dependency 'kramdown'
|
25
27
|
|
26
28
|
# gem.post_install_message = "This update modifies default templates. Please run `woody update templates` in your site directory to update them. Warning: this will destroy any modifications to your templates."
|
27
29
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: woody
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: erubis
|
@@ -91,6 +91,38 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: preamble
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: kramdown
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
94
126
|
description: Woody
|
95
127
|
email:
|
96
128
|
- david@davidr.me
|
@@ -110,13 +142,14 @@ files:
|
|
110
142
|
- lib/woody/deployer.rb
|
111
143
|
- lib/woody/episode.rb
|
112
144
|
- lib/woody/generator.rb
|
145
|
+
- lib/woody/post.rb
|
113
146
|
- lib/woody/version.rb
|
114
|
-
- templates/episode.html
|
115
147
|
- templates/feed.xml
|
116
148
|
- templates/iTunes.png
|
117
149
|
- templates/index.html
|
118
150
|
- templates/layout.html
|
119
151
|
- templates/metadata.yml
|
152
|
+
- templates/post.html
|
120
153
|
- templates/stylesheet.css
|
121
154
|
- templates/woody-config.yml
|
122
155
|
- woody.gemspec
|
data/templates/episode.html
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
<h3><%= link_to episode.title, episode.path %></h3>
|
2
|
-
<h4><%= episode.subtitle %></h4>
|
3
|
-
Player goes here
|
4
|
-
|
5
|
-
<a href="<%= episode.file_path %>">Download MP3</a>
|
6
|
-
|
7
|
-
<div class="synopsis">
|
8
|
-
<%= episode.synopsis %>
|
9
|
-
</div>
|
10
|
-
|
11
|
-
<div class="tags">
|
12
|
-
<strong>Tags:</strong>
|
13
|
-
<% episode.tags.each do |tag| %>
|
14
|
-
<span class="tag"><%= tag %> </span>
|
15
|
-
<% end %>
|
16
|
-
</div>
|