nanoc3 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +3 -0
- data/LICENSE +19 -0
- data/NEWS.rdoc +262 -0
- data/README.rdoc +80 -0
- data/Rakefile +11 -0
- data/bin/nanoc3 +16 -0
- data/lib/nanoc3/base/code_snippet.rb +42 -0
- data/lib/nanoc3/base/compiler.rb +225 -0
- data/lib/nanoc3/base/compiler_dsl.rb +110 -0
- data/lib/nanoc3/base/core_ext/array.rb +21 -0
- data/lib/nanoc3/base/core_ext/hash.rb +23 -0
- data/lib/nanoc3/base/core_ext/string.rb +14 -0
- data/lib/nanoc3/base/core_ext.rb +5 -0
- data/lib/nanoc3/base/data_source.rb +197 -0
- data/lib/nanoc3/base/dependency_tracker.rb +291 -0
- data/lib/nanoc3/base/errors.rb +95 -0
- data/lib/nanoc3/base/filter.rb +60 -0
- data/lib/nanoc3/base/item.rb +87 -0
- data/lib/nanoc3/base/item_rep.rb +236 -0
- data/lib/nanoc3/base/layout.rb +53 -0
- data/lib/nanoc3/base/notification_center.rb +68 -0
- data/lib/nanoc3/base/plugin.rb +88 -0
- data/lib/nanoc3/base/preprocessor_context.rb +37 -0
- data/lib/nanoc3/base/rule.rb +37 -0
- data/lib/nanoc3/base/rule_context.rb +68 -0
- data/lib/nanoc3/base/site.rb +334 -0
- data/lib/nanoc3/base.rb +25 -0
- data/lib/nanoc3/cli/base.rb +151 -0
- data/lib/nanoc3/cli/commands/autocompile.rb +89 -0
- data/lib/nanoc3/cli/commands/compile.rb +279 -0
- data/lib/nanoc3/cli/commands/create_item.rb +79 -0
- data/lib/nanoc3/cli/commands/create_layout.rb +94 -0
- data/lib/nanoc3/cli/commands/create_site.rb +320 -0
- data/lib/nanoc3/cli/commands/help.rb +71 -0
- data/lib/nanoc3/cli/commands/info.rb +114 -0
- data/lib/nanoc3/cli/commands/update.rb +96 -0
- data/lib/nanoc3/cli/commands.rb +13 -0
- data/lib/nanoc3/cli/logger.rb +73 -0
- data/lib/nanoc3/cli.rb +16 -0
- data/lib/nanoc3/data_sources/delicious.rb +66 -0
- data/lib/nanoc3/data_sources/filesystem.rb +231 -0
- data/lib/nanoc3/data_sources/filesystem_combined.rb +202 -0
- data/lib/nanoc3/data_sources/filesystem_common.rb +22 -0
- data/lib/nanoc3/data_sources/filesystem_compact.rb +232 -0
- data/lib/nanoc3/data_sources/last_fm.rb +103 -0
- data/lib/nanoc3/data_sources/twitter.rb +53 -0
- data/lib/nanoc3/data_sources.rb +20 -0
- data/lib/nanoc3/extra/auto_compiler.rb +97 -0
- data/lib/nanoc3/extra/chick.rb +119 -0
- data/lib/nanoc3/extra/context.rb +24 -0
- data/lib/nanoc3/extra/core_ext/time.rb +19 -0
- data/lib/nanoc3/extra/core_ext.rb +3 -0
- data/lib/nanoc3/extra/deployers/rsync.rb +64 -0
- data/lib/nanoc3/extra/deployers.rb +12 -0
- data/lib/nanoc3/extra/file_proxy.rb +31 -0
- data/lib/nanoc3/extra/validators/links.rb +0 -0
- data/lib/nanoc3/extra/validators/w3c.rb +71 -0
- data/lib/nanoc3/extra/validators.rb +12 -0
- data/lib/nanoc3/extra/vcs.rb +65 -0
- data/lib/nanoc3/extra/vcses/bazaar.rb +21 -0
- data/lib/nanoc3/extra/vcses/dummy.rb +20 -0
- data/lib/nanoc3/extra/vcses/git.rb +21 -0
- data/lib/nanoc3/extra/vcses/mercurial.rb +21 -0
- data/lib/nanoc3/extra/vcses/subversion.rb +21 -0
- data/lib/nanoc3/extra/vcses.rb +17 -0
- data/lib/nanoc3/extra.rb +16 -0
- data/lib/nanoc3/filters/bluecloth.rb +13 -0
- data/lib/nanoc3/filters/coderay.rb +17 -0
- data/lib/nanoc3/filters/erb.rb +19 -0
- data/lib/nanoc3/filters/erubis.rb +17 -0
- data/lib/nanoc3/filters/haml.rb +20 -0
- data/lib/nanoc3/filters/less.rb +13 -0
- data/lib/nanoc3/filters/markaby.rb +14 -0
- data/lib/nanoc3/filters/maruku.rb +14 -0
- data/lib/nanoc3/filters/rainpress.rb +13 -0
- data/lib/nanoc3/filters/rdiscount.rb +13 -0
- data/lib/nanoc3/filters/rdoc.rb +23 -0
- data/lib/nanoc3/filters/redcloth.rb +14 -0
- data/lib/nanoc3/filters/relativize_paths.rb +32 -0
- data/lib/nanoc3/filters/rubypants.rb +14 -0
- data/lib/nanoc3/filters/sass.rb +17 -0
- data/lib/nanoc3/filters.rb +37 -0
- data/lib/nanoc3/helpers/blogging.rb +226 -0
- data/lib/nanoc3/helpers/breadcrumbs.rb +25 -0
- data/lib/nanoc3/helpers/capturing.rb +71 -0
- data/lib/nanoc3/helpers/filtering.rb +46 -0
- data/lib/nanoc3/helpers/html_escape.rb +22 -0
- data/lib/nanoc3/helpers/link_to.rb +120 -0
- data/lib/nanoc3/helpers/rendering.rb +76 -0
- data/lib/nanoc3/helpers/tagging.rb +58 -0
- data/lib/nanoc3/helpers/text.rb +40 -0
- data/lib/nanoc3/helpers/xml_sitemap.rb +69 -0
- data/lib/nanoc3/helpers.rb +16 -0
- data/lib/nanoc3/package.rb +106 -0
- data/lib/nanoc3/tasks/clean.rake +16 -0
- data/lib/nanoc3/tasks/clean.rb +33 -0
- data/lib/nanoc3/tasks/deploy/rsync.rake +11 -0
- data/lib/nanoc3/tasks/validate.rake +35 -0
- data/lib/nanoc3/tasks.rb +9 -0
- data/lib/nanoc3.rb +19 -0
- data/vendor/cri/ChangeLog +0 -0
- data/vendor/cri/LICENSE +19 -0
- data/vendor/cri/NEWS +0 -0
- data/vendor/cri/README +4 -0
- data/vendor/cri/Rakefile +25 -0
- data/vendor/cri/lib/cri/base.rb +153 -0
- data/vendor/cri/lib/cri/command.rb +105 -0
- data/vendor/cri/lib/cri/core_ext/string.rb +41 -0
- data/vendor/cri/lib/cri/core_ext.rb +8 -0
- data/vendor/cri/lib/cri/option_parser.rb +186 -0
- data/vendor/cri/lib/cri.rb +12 -0
- data/vendor/cri/test/test_base.rb +6 -0
- data/vendor/cri/test/test_command.rb +6 -0
- data/vendor/cri/test/test_core_ext.rb +21 -0
- data/vendor/cri/test/test_option_parser.rb +279 -0
- metadata +225 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::DataSources
|
4
|
+
|
5
|
+
# The filesystem_combined data source is the default data source for a new
|
6
|
+
# nanoc site. It stores all data as files on the hard disk.
|
7
|
+
#
|
8
|
+
# None of the methods are documented in this file. See Nanoc3::DataSource
|
9
|
+
# for documentation on the overridden methods instead.
|
10
|
+
#
|
11
|
+
# = Items
|
12
|
+
#
|
13
|
+
# Items are stored as pairs of two files: a content file, containing the
|
14
|
+
# actual item content, and a meta file, containing the item's attributes,
|
15
|
+
# formatted as YAML. The content file and the corresponding meta file have
|
16
|
+
# the same filename but not the same extension; the meta file's extension is
|
17
|
+
# .yaml.
|
18
|
+
#
|
19
|
+
# Items are stored in the "content" directory of the nanoc site.
|
20
|
+
#
|
21
|
+
# The home page item, located at /, is represented by an index.yaml meta
|
22
|
+
# file, along with its corresponding content file.
|
23
|
+
#
|
24
|
+
# Subitems of other pages can be achieved in two ways: they can either be
|
25
|
+
# nested in directories and named "index" such as the home page item, or
|
26
|
+
# they can simply be given a non-"index" name.
|
27
|
+
#
|
28
|
+
# For example, this directory structure:
|
29
|
+
#
|
30
|
+
# content/
|
31
|
+
# index.html
|
32
|
+
# index.yaml
|
33
|
+
# about.html
|
34
|
+
# about.yaml
|
35
|
+
# journal.html
|
36
|
+
# journal.yaml
|
37
|
+
# journal/
|
38
|
+
# 2005.html
|
39
|
+
# 2005.yaml
|
40
|
+
# 2005/
|
41
|
+
# a-very-old-post.html
|
42
|
+
# a-very-old-post.yaml
|
43
|
+
# another-very-old-post.html
|
44
|
+
# another-very-old-post.yaml
|
45
|
+
# myst/
|
46
|
+
# index.html
|
47
|
+
# index.yaml
|
48
|
+
#
|
49
|
+
# … corresponds with the following items:
|
50
|
+
#
|
51
|
+
# /
|
52
|
+
# /about/
|
53
|
+
# /journal/
|
54
|
+
# /journal/2005/
|
55
|
+
# /journal/2005/a-very-old-post/
|
56
|
+
# /journal/2005/another-very-old-post/
|
57
|
+
# /myst/
|
58
|
+
#
|
59
|
+
# = Layouts
|
60
|
+
#
|
61
|
+
# Layouts are stored the same way as items, except that they are stored in
|
62
|
+
# the "layouts" directory instead of the "content" directory.
|
63
|
+
#
|
64
|
+
# = Code Snippets
|
65
|
+
#
|
66
|
+
# Code snippets are stored in '.rb' files in the 'lib' directory. Code
|
67
|
+
# snippets can reside in sub-directories.
|
68
|
+
class FilesystemCompact < Nanoc3::DataSource
|
69
|
+
|
70
|
+
include Nanoc3::DataSources::FilesystemCommon
|
71
|
+
|
72
|
+
########## VCSes ##########
|
73
|
+
|
74
|
+
attr_accessor :vcs
|
75
|
+
|
76
|
+
def vcs
|
77
|
+
@vcs ||= Nanoc3::Extra::VCSes::Dummy.new
|
78
|
+
end
|
79
|
+
|
80
|
+
########## Preparation ##########
|
81
|
+
|
82
|
+
def setup
|
83
|
+
# Create directories
|
84
|
+
%w( content layouts lib ).each do |dir|
|
85
|
+
FileUtils.mkdir_p(dir)
|
86
|
+
vcs.add(dir)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
########## Loading data ##########
|
91
|
+
|
92
|
+
def items
|
93
|
+
meta_filenames('content').map do |meta_filename|
|
94
|
+
# Read metadata
|
95
|
+
meta = YAML.load_file(meta_filename) || {}
|
96
|
+
|
97
|
+
# Get content
|
98
|
+
content_filename = content_filename_for_meta_filename(meta_filename)
|
99
|
+
content = File.read(content_filename)
|
100
|
+
|
101
|
+
# Get attributes
|
102
|
+
attributes = meta.merge(:file => Nanoc3::Extra::FileProxy.new(content_filename))
|
103
|
+
|
104
|
+
# Get identifier
|
105
|
+
identifier = identifier_for_meta_filename(meta_filename.sub(/^content/, ''))
|
106
|
+
|
107
|
+
# Get modification times
|
108
|
+
meta_mtime = File.stat(meta_filename).mtime
|
109
|
+
content_mtime = File.stat(content_filename).mtime
|
110
|
+
mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
|
111
|
+
|
112
|
+
# Create item object
|
113
|
+
Nanoc3::Item.new(content, attributes, identifier, mtime)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def layouts
|
118
|
+
meta_filenames('layouts').map do |meta_filename|
|
119
|
+
# Get content
|
120
|
+
content_filename = content_filename_for_meta_filename(meta_filename)
|
121
|
+
content = File.read(content_filename)
|
122
|
+
|
123
|
+
# Get attributes
|
124
|
+
attributes = YAML.load_file(meta_filename) || {}
|
125
|
+
|
126
|
+
# Get identifier
|
127
|
+
identifier = identifier_for_meta_filename(meta_filename.sub(/^layouts\//, ''))
|
128
|
+
|
129
|
+
# Get modification times
|
130
|
+
meta_mtime = File.stat(meta_filename).mtime
|
131
|
+
content_mtime = File.stat(content_filename).mtime
|
132
|
+
mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
|
133
|
+
|
134
|
+
# Create layout object
|
135
|
+
Nanoc3::Layout.new(content, attributes, identifier, mtime)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
########## Creating data ##########
|
140
|
+
|
141
|
+
# Creates a new item with the given content, attributes and identifier.
|
142
|
+
def create_item(content, attributes, identifier)
|
143
|
+
# Get filenames
|
144
|
+
base_path = 'content' + (identifier == '/' ? '/index' : identifier[0..-2])
|
145
|
+
meta_filename = base_path + '.yaml'
|
146
|
+
content_filename = base_path + '.html'
|
147
|
+
|
148
|
+
# Notify
|
149
|
+
Nanoc3::NotificationCenter.post(:file_created, meta_filename)
|
150
|
+
Nanoc3::NotificationCenter.post(:file_created, content_filename)
|
151
|
+
|
152
|
+
# Create files
|
153
|
+
FileUtils.mkdir_p(File.dirname(meta_filename))
|
154
|
+
File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
|
155
|
+
File.open(content_filename, 'w') { |io| io.write(content) }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Creates a new layout with the given content, attributes and identifier.
|
159
|
+
def create_layout(content, attributes, identifier)
|
160
|
+
# Get filenames
|
161
|
+
base_path = 'layouts' + identifier[0..-2]
|
162
|
+
meta_filename = base_path + '.yaml'
|
163
|
+
content_filename = base_path + '.html'
|
164
|
+
|
165
|
+
# Notify
|
166
|
+
Nanoc3::NotificationCenter.post(:file_created, meta_filename)
|
167
|
+
Nanoc3::NotificationCenter.post(:file_created, content_filename)
|
168
|
+
|
169
|
+
# Create files
|
170
|
+
FileUtils.mkdir_p(File.dirname(meta_filename))
|
171
|
+
File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
|
172
|
+
File.open(content_filename, 'w') { |io| io.write(content) }
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
########## Custom functions ##########
|
178
|
+
|
179
|
+
# Returns the identifier for the given meta filename. This method assumes
|
180
|
+
# that the base is already stripped.
|
181
|
+
#
|
182
|
+
# For example:
|
183
|
+
#
|
184
|
+
# /foo.yaml -> /foo/
|
185
|
+
# /foo/index.yaml -> /foo/
|
186
|
+
# /foo/foo.yaml -> /foo/foo/
|
187
|
+
# /foo/bar.yaml -> /foo/bar/
|
188
|
+
def identifier_for_meta_filename(meta_filename)
|
189
|
+
# Split into components
|
190
|
+
components = meta_filename.gsub(%r{(^/|/$)}, '').split('/')
|
191
|
+
components[-1].sub!(/\.yaml$/, '')
|
192
|
+
|
193
|
+
if components[-1] == 'index'
|
194
|
+
components[0..-2].join('/').cleaned_identifier
|
195
|
+
else
|
196
|
+
components.join('/').cleaned_identifier
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns the list of all meta files in the given base directory as well
|
201
|
+
# as its subdirectories.
|
202
|
+
def meta_filenames(base)
|
203
|
+
Dir[base + '/**/*.yaml']
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns the filename of the content file corresponding to the given meta
|
207
|
+
# file, ignoring any unwanted files (files that end with '~', '.orig',
|
208
|
+
# '.rej' or '.bak')
|
209
|
+
def content_filename_for_meta_filename(meta_filename)
|
210
|
+
# Find all files
|
211
|
+
filenames = Dir[meta_filename.sub(/\.yaml$/, '.*')]
|
212
|
+
|
213
|
+
# Reject meta files
|
214
|
+
filenames.reject! { |f| f =~ /\.yaml$/ }
|
215
|
+
|
216
|
+
# Reject backups
|
217
|
+
filenames.reject! { |f| f =~ /(~|\.orig|\.rej|\.bak)$/ }
|
218
|
+
|
219
|
+
# Make sure there is only one content file
|
220
|
+
if filenames.size != 1
|
221
|
+
raise RuntimeError.new(
|
222
|
+
"Expected 1 content file for the metafile #{meta_filename} but found #{filenames.size}"
|
223
|
+
)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Return content filename
|
227
|
+
filenames.first
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::DataSources
|
4
|
+
|
5
|
+
# Nanoc3::DataSources::LastFM provides data about recently played tracks
|
6
|
+
# from from a single Last.fm user as items (Nanoc3::Item instances).
|
7
|
+
#
|
8
|
+
# The configuration must have a "username" attribute containing the username
|
9
|
+
# of the account from which to fetch the data, and an "api_key" attribute
|
10
|
+
# containing the API key (which can be obtained from the Last.fm site).
|
11
|
+
#
|
12
|
+
# The items returned by this data source will be mounted at {root}/{id},
|
13
|
+
# where +id+ is a sequence number that is not necessarily unique for this
|
14
|
+
# bookmark (because delicious.com unfortunately does not provide unique IDs
|
15
|
+
# for each track).
|
16
|
+
#
|
17
|
+
# The items returned by this data source will have the following attributes:
|
18
|
+
#
|
19
|
+
# +:name+:: The name of the track.
|
20
|
+
#
|
21
|
+
# +played_at+:: The timestamp when the track was played (a Time instance).
|
22
|
+
#
|
23
|
+
# +url+:: The Last.fm URL corresponding to the track (a String instance).
|
24
|
+
#
|
25
|
+
# +artist+:: A hash containing information about the track's artist.
|
26
|
+
#
|
27
|
+
# The +artist+ hash consists of the following keys:
|
28
|
+
#
|
29
|
+
# +name+:: The name of the artist.
|
30
|
+
#
|
31
|
+
# +url+:: The Last.fm URL corresponding to the artist (a String instance).
|
32
|
+
class LastFM < Nanoc3::DataSource
|
33
|
+
|
34
|
+
def items
|
35
|
+
@items ||= begin
|
36
|
+
require 'json'
|
37
|
+
require 'time'
|
38
|
+
require 'uri'
|
39
|
+
|
40
|
+
# Check configuration
|
41
|
+
if self.config[:username].nil?
|
42
|
+
raise RuntimeError, "LastFM data source requires a username in the configuration"
|
43
|
+
end
|
44
|
+
if self.config[:api_key].nil?
|
45
|
+
raise RuntimeError, "LastFM data source requires an API key in the configuration"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get data
|
49
|
+
@http_client ||= Nanoc3::Extra::CHiCk::Client.new
|
50
|
+
status, headers, data = *@http_client.get(
|
51
|
+
'http://ws.audioscrobbler.com/2.0/' +
|
52
|
+
'?method=user.getRecentTracks' +
|
53
|
+
'&format=json' +
|
54
|
+
'&user=' + URI.escape(self.config[:username]) +
|
55
|
+
'&api_key=' + URI.escape(self.config[:api_key])
|
56
|
+
)
|
57
|
+
|
58
|
+
# Parse as JSON
|
59
|
+
parsed_data = JSON.parse(data)
|
60
|
+
raw_items = parsed_data['recenttracks']['track']
|
61
|
+
|
62
|
+
# Convert to items
|
63
|
+
raw_items.enum_with_index.map do |raw_item, i|
|
64
|
+
# Get artist data
|
65
|
+
artist_status, artist_headers, artist_data = *@http_client.get(
|
66
|
+
'http://ws.audioscrobbler.com/2.0/' +
|
67
|
+
'?method=artist.getInfo' +
|
68
|
+
'&format=json' +
|
69
|
+
(
|
70
|
+
raw_item['artist']['mbid'].empty? ?
|
71
|
+
'&artist=' + URI.escape(raw_item['artist']['#text']) :
|
72
|
+
'&mbid=' + URI.escape(raw_item['artist']['mbid'])
|
73
|
+
) +
|
74
|
+
'&api_key=' + URI.escape(self.config[:api_key])
|
75
|
+
)
|
76
|
+
|
77
|
+
# Parse as JSON
|
78
|
+
parsed_artist_data = JSON.parse(artist_data)
|
79
|
+
raw_artist_info = parsed_artist_data['artist']
|
80
|
+
|
81
|
+
# Build data
|
82
|
+
content = ''
|
83
|
+
attributes = {
|
84
|
+
:name => raw_item['name'],
|
85
|
+
:artist => {
|
86
|
+
:name => raw_artist_info['name'],
|
87
|
+
:url => raw_artist_info['url']
|
88
|
+
},
|
89
|
+
:url => raw_item['url'],
|
90
|
+
:played_at => Time.parse(raw_item['date']['#text'])
|
91
|
+
}
|
92
|
+
identifier = "/#{i}/"
|
93
|
+
mtime = nil
|
94
|
+
|
95
|
+
# Build item
|
96
|
+
Nanoc3::Item.new(content, attributes, identifier, mtime)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::DataSources
|
4
|
+
|
5
|
+
# Nanoc3::DataSources::Twitter provides tweets from a single user as items
|
6
|
+
# (Nanoc3::Item instances).
|
7
|
+
#
|
8
|
+
# The configuration must have a "username" attribute containing the username
|
9
|
+
# of the account from which to fetch the tweets.
|
10
|
+
#
|
11
|
+
# The items returned by this data source will be mounted at {root}/{id},
|
12
|
+
# where +id+ is the unique identifier of the tweet.
|
13
|
+
#
|
14
|
+
# The items returned by this data source will have the following attributes:
|
15
|
+
#
|
16
|
+
# +:created_at+:: The timestamp when this tweet was posted (a string).
|
17
|
+
#
|
18
|
+
# +source+:: The client used to tweet this message (HTML-encoded).
|
19
|
+
class Twitter < Nanoc3::DataSource
|
20
|
+
|
21
|
+
def items
|
22
|
+
@item ||= begin
|
23
|
+
require 'json'
|
24
|
+
require 'time'
|
25
|
+
|
26
|
+
# Get data
|
27
|
+
@http_client ||= Nanoc3::Extra::CHiCk::Client.new
|
28
|
+
status, headers, data = *@http_client.get("http://twitter.com/statuses/user_timeline/#{self.config[:username]}.json")
|
29
|
+
|
30
|
+
# Parse as JSON
|
31
|
+
raw_items = JSON.parse(data)
|
32
|
+
|
33
|
+
# Convert to items
|
34
|
+
raw_items.enum_with_index.map do |raw_item, i|
|
35
|
+
# Get data
|
36
|
+
content = raw_item['text']
|
37
|
+
attributes = {
|
38
|
+
:created_at => raw_item['created_at'],
|
39
|
+
:source => raw_item['source']
|
40
|
+
# TODO add more
|
41
|
+
}
|
42
|
+
identifier = "/#{raw_item['id']}/"
|
43
|
+
mtime = Time.parse(raw_item['created_at'])
|
44
|
+
|
45
|
+
# Build item
|
46
|
+
Nanoc3::Item.new(content, attributes, identifier, mtime)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::DataSources
|
4
|
+
|
5
|
+
autoload 'Delicious', 'nanoc3/data_sources/delicious'
|
6
|
+
autoload 'Filesystem', 'nanoc3/data_sources/filesystem'
|
7
|
+
autoload 'FilesystemCombined', 'nanoc3/data_sources/filesystem_combined'
|
8
|
+
autoload 'FilesystemCommon', 'nanoc3/data_sources/filesystem_common'
|
9
|
+
autoload 'FilesystemCompact', 'nanoc3/data_sources/filesystem_compact'
|
10
|
+
autoload 'LastFM', 'nanoc3/data_sources/last_fm'
|
11
|
+
autoload 'Twitter', 'nanoc3/data_sources/twitter'
|
12
|
+
|
13
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::Delicious', :delicious
|
14
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::Filesystem', :filesystem
|
15
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemCombined', :filesystem_combined
|
16
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::FilesystemCompact', :filesystem_compact
|
17
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::LastFM', :last_fm
|
18
|
+
Nanoc3::DataSource.register '::Nanoc3::DataSources::Twitter', :twitter
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::Extra
|
4
|
+
|
5
|
+
# Nanoc3::Extra::AutoCompiler is a web server that will automatically compile
|
6
|
+
# items as they are requested. It also serves static files such as
|
7
|
+
# stylesheets and images.
|
8
|
+
class AutoCompiler
|
9
|
+
|
10
|
+
attr_reader :site
|
11
|
+
|
12
|
+
# Creates a new autocompiler for the given site.
|
13
|
+
def initialize(site_path)
|
14
|
+
require 'rack'
|
15
|
+
require 'mime/types'
|
16
|
+
|
17
|
+
# Set site
|
18
|
+
@site_path = site_path
|
19
|
+
|
20
|
+
# Create mutex to prevent parallel requests
|
21
|
+
@mutex = Mutex.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
@mutex.synchronize do
|
26
|
+
# Start with a new site
|
27
|
+
build_site
|
28
|
+
|
29
|
+
# Find rep
|
30
|
+
path = env['PATH_INFO']
|
31
|
+
reps = site.items.map { |i| i.reps }.flatten
|
32
|
+
rep = reps.find { |r| r.path == path }
|
33
|
+
|
34
|
+
if rep
|
35
|
+
serve(rep)
|
36
|
+
else
|
37
|
+
# Get paths by appending index filenames
|
38
|
+
if path =~ /\/$/
|
39
|
+
possible_paths = site.config[:index_filenames].map { |f| path + f }
|
40
|
+
else
|
41
|
+
possible_paths = [ path ]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Find matching file
|
45
|
+
modified_path = possible_paths.find { |f| File.file?(site.config[:output_dir] + f) }
|
46
|
+
modified_path ||= path
|
47
|
+
|
48
|
+
# Serve using Rack::File
|
49
|
+
file_server.call(env.merge('PATH_INFO' => modified_path))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rescue StandardError, ScriptError => e
|
53
|
+
# Add compilation stack to env
|
54
|
+
env['nanoc.stack'] = []
|
55
|
+
site.compiler.stack.reverse.each do |obj|
|
56
|
+
if obj.is_a?(Nanoc3::ItemRep) # item rep
|
57
|
+
env['nanoc.stack'] << "[item] #{obj.item.identifier} (rep #{obj.name})"
|
58
|
+
else # layout
|
59
|
+
env['nanoc.stack'] << "[layout] #{obj.identifier}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Re-raise error
|
64
|
+
raise e
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def build_site
|
70
|
+
@site = Nanoc3::Site.new(@site_path)
|
71
|
+
@site.load_data
|
72
|
+
end
|
73
|
+
|
74
|
+
def mime_type_of(path, fallback)
|
75
|
+
mime_type = MIME::Types.of(path).first
|
76
|
+
mime_type = mime_type.nil? ? fallback : mime_type.simplified
|
77
|
+
end
|
78
|
+
|
79
|
+
def file_server
|
80
|
+
@file_server ||= ::Rack::File.new(site.config[:output_dir])
|
81
|
+
end
|
82
|
+
|
83
|
+
def serve(rep)
|
84
|
+
# Recompile rep
|
85
|
+
site.compiler.run(rep.item, :force => true)
|
86
|
+
|
87
|
+
# Build response
|
88
|
+
[
|
89
|
+
200,
|
90
|
+
{ 'Content-Type' => mime_type_of(rep.raw_path, 'text/html') },
|
91
|
+
[ rep.content_at_snapshot(:last) ]
|
92
|
+
]
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rack'
|
3
|
+
require 'rack/cache'
|
4
|
+
|
5
|
+
module Nanoc3::Extra
|
6
|
+
|
7
|
+
# CHiCk is a caching HTTP client that uses Rack::Cache.
|
8
|
+
module CHiCk
|
9
|
+
|
10
|
+
# CHiCk::Client provides a simple API for issuing HTTP requests.
|
11
|
+
class Client
|
12
|
+
|
13
|
+
DEFAULT_OPTIONS = {
|
14
|
+
:cache => {
|
15
|
+
:metastore => 'file:tmp/rack/cache.meta',
|
16
|
+
:entitystore => 'file:tmp/rack/cache.body'
|
17
|
+
},
|
18
|
+
:cache_controller => {
|
19
|
+
:max_age => 60
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
def initialize(options={})
|
24
|
+
# Get options
|
25
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
26
|
+
@options[:cache] = DEFAULT_OPTIONS[:cache].merge(@options[:cache])
|
27
|
+
@options[:cache_controller] = DEFAULT_OPTIONS[:cache_controller].merge(@options[:cache_controller])
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(url)
|
31
|
+
# Build app
|
32
|
+
options = @options
|
33
|
+
@app ||= Rack::Builder.new {
|
34
|
+
use Rack::Cache, options[:cache].merge(:verbose => true)
|
35
|
+
use Nanoc3::Extra::CHiCk::CacheController, options[:cache_controller]
|
36
|
+
run Nanoc3::Extra::CHiCk::RackClient
|
37
|
+
}
|
38
|
+
|
39
|
+
# Build environment for request
|
40
|
+
env = Rack::MockRequest.env_for(url, :method => 'GET')
|
41
|
+
|
42
|
+
# Fetch
|
43
|
+
puts "[CHiCk] Fetching #{url}..." if $DEBUG
|
44
|
+
status, headers, body_parts = @app.call(env)
|
45
|
+
puts "[CHiCk] #{url}: #{headers['X-Rack-Cache']}" if $DEBUG
|
46
|
+
|
47
|
+
# Join body
|
48
|
+
body = ''
|
49
|
+
body_parts.each { |part| body << part }
|
50
|
+
|
51
|
+
# Done
|
52
|
+
[ status, headers, body ]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
# CHiCk::CacheController sets the Cache-Control header (and more
|
58
|
+
# specifically, max-age) to limit the number of necessary requests.
|
59
|
+
class CacheController
|
60
|
+
|
61
|
+
def initialize(app, options={})
|
62
|
+
@app = app
|
63
|
+
@options = options
|
64
|
+
end
|
65
|
+
|
66
|
+
def call(env)
|
67
|
+
res = @app.call(env)
|
68
|
+
unless res[1].has_key?('Cache-Control') || res[1].has_key?('Expires')
|
69
|
+
res[1]['Cache-Control'] = "max-age=#{@options[:max_age]}"
|
70
|
+
end
|
71
|
+
res
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
# CHiCk::RackClient performs the actual HTTP requests. It does not perform
|
77
|
+
# any caching.
|
78
|
+
class RackClient
|
79
|
+
|
80
|
+
METHOD_TO_CLASS_MAPPING = {
|
81
|
+
'DELETE' => Net::HTTP::Delete,
|
82
|
+
'GET' => Net::HTTP::Get,
|
83
|
+
'HEAD' => Net::HTTP::Head,
|
84
|
+
'POST' => Net::HTTP::Post,
|
85
|
+
'PUT' => Net::HTTP::Put
|
86
|
+
}
|
87
|
+
|
88
|
+
def self.call(env)
|
89
|
+
# Build request
|
90
|
+
request = Rack::Request.new(env)
|
91
|
+
|
92
|
+
# Build headers and strip HTTP_
|
93
|
+
request_headers = env.inject({}) do |m,(k,v)|
|
94
|
+
k =~ /^HTTP_(.*)$/ && v ? m.merge($1.gsub(/_/, '-') => v) : m
|
95
|
+
end
|
96
|
+
|
97
|
+
# Build Net::HTTP request
|
98
|
+
http = Net::HTTP.new(request.host, request.port)
|
99
|
+
net_http_request_class = METHOD_TO_CLASS_MAPPING[request.request_method]
|
100
|
+
raise ArgumentError, "Unsupported method: #{request.request_method}" if net_http_request_class.nil?
|
101
|
+
net_http_request = net_http_request_class.new(request.fullpath, request_headers)
|
102
|
+
net_http_request.body = env['rack.input'].read if [ 'POST', 'PUT' ].include?(request.request_method)
|
103
|
+
|
104
|
+
# Perform request
|
105
|
+
http.request(net_http_request) do |response|
|
106
|
+
# Build Rack response triplet
|
107
|
+
return [
|
108
|
+
response.code.to_i,
|
109
|
+
response.to_hash.inject({}) { |m,(k,v)| m.merge(k => v[0]) },
|
110
|
+
[ response.body ]
|
111
|
+
]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::Extra
|
4
|
+
|
5
|
+
# Nanoc3::Extra::Context provides a context and a Binding for use in various
|
6
|
+
# filters, such as the ERB and Haml one.
|
7
|
+
class Context
|
8
|
+
|
9
|
+
# Creates a new context based off the contents of the hash. Each pair in
|
10
|
+
# the hash will be converted to an instance variable. For example, passing
|
11
|
+
# the hash { :foo => 'bar' } will cause @foo to have the value "bar".
|
12
|
+
def initialize(hash)
|
13
|
+
hash.each_pair do |key, value|
|
14
|
+
instance_variable_set('@' + key.to_s, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a binding for this context.
|
19
|
+
def get_binding
|
20
|
+
binding
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3::Extra::TimeExtensions
|
4
|
+
|
5
|
+
# Returns a string with the time in an ISO-8601 date format.
|
6
|
+
def to_iso8601_date
|
7
|
+
self.strftime("%Y-%m-%d")
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a string with the time in an ISO-8601 time format.
|
11
|
+
def to_iso8601_time
|
12
|
+
self.gmtime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class Time
|
18
|
+
include Nanoc3::Extra::TimeExtensions
|
19
|
+
end
|