nanoc 3.2.4 → 3.3.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/.gemtest +0 -0
- data/ChangeLog +3 -0
- data/Gemfile +32 -0
- data/LICENSE +19 -0
- data/NEWS.md +470 -0
- data/README.md +114 -0
- data/Rakefile +14 -0
- data/bin/nanoc +7 -27
- data/bin/nanoc3 +3 -0
- data/doc/yardoc_templates/default/layout/html/footer.erb +10 -0
- data/lib/nanoc.rb +41 -0
- data/lib/nanoc/base.rb +49 -0
- data/lib/nanoc/base/compilation/checksum_store.rb +57 -0
- data/lib/nanoc/base/compilation/compiled_content_cache.rb +62 -0
- data/lib/nanoc/base/compilation/compiler.rb +458 -0
- data/lib/nanoc/base/compilation/compiler_dsl.rb +214 -0
- data/lib/nanoc/base/compilation/dependency_tracker.rb +200 -0
- data/lib/nanoc/base/compilation/filter.rb +165 -0
- data/lib/nanoc/base/compilation/item_rep_proxy.rb +103 -0
- data/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb +102 -0
- data/lib/nanoc/base/compilation/outdatedness_checker.rb +223 -0
- data/lib/nanoc/base/compilation/outdatedness_reasons.rb +46 -0
- data/lib/nanoc/base/compilation/rule.rb +73 -0
- data/lib/nanoc/base/compilation/rule_context.rb +84 -0
- data/lib/nanoc/base/compilation/rule_memory_calculator.rb +40 -0
- data/lib/nanoc/base/compilation/rule_memory_store.rb +53 -0
- data/lib/nanoc/base/compilation/rules_collection.rb +243 -0
- data/lib/nanoc/base/context.rb +47 -0
- data/lib/nanoc/base/core_ext.rb +6 -0
- data/lib/nanoc/base/core_ext/array.rb +62 -0
- data/lib/nanoc/base/core_ext/hash.rb +63 -0
- data/lib/nanoc/base/core_ext/pathname.rb +26 -0
- data/lib/nanoc/base/core_ext/string.rb +46 -0
- data/lib/nanoc/base/directed_graph.rb +275 -0
- data/lib/nanoc/base/errors.rb +211 -0
- data/lib/nanoc/base/memoization.rb +67 -0
- data/lib/nanoc/base/notification_center.rb +84 -0
- data/lib/nanoc/base/ordered_hash.rb +200 -0
- data/lib/nanoc/base/plugin_registry.rb +181 -0
- data/lib/nanoc/base/result_data/item_rep.rb +492 -0
- data/lib/nanoc/base/source_data/code_snippet.rb +58 -0
- data/lib/nanoc/base/source_data/configuration.rb +24 -0
- data/lib/nanoc/base/source_data/data_source.rb +234 -0
- data/lib/nanoc/base/source_data/item.rb +301 -0
- data/lib/nanoc/base/source_data/layout.rb +130 -0
- data/lib/nanoc/base/source_data/site.rb +361 -0
- data/lib/nanoc/base/store.rb +135 -0
- data/lib/nanoc/cli.rb +137 -0
- data/lib/nanoc/cli/command_runner.rb +104 -0
- data/lib/nanoc/cli/commands/autocompile.rb +58 -0
- data/lib/nanoc/cli/commands/compile.rb +297 -0
- data/lib/nanoc/cli/commands/create_item.rb +60 -0
- data/lib/nanoc/cli/commands/create_layout.rb +73 -0
- data/lib/nanoc/cli/commands/create_site.rb +411 -0
- data/lib/nanoc/cli/commands/debug.rb +117 -0
- data/lib/nanoc/cli/commands/deploy.rb +79 -0
- data/lib/nanoc/cli/commands/info.rb +98 -0
- data/lib/nanoc/cli/commands/nanoc.rb +38 -0
- data/lib/nanoc/cli/commands/prune.rb +50 -0
- data/lib/nanoc/cli/commands/update.rb +70 -0
- data/lib/nanoc/cli/commands/view.rb +82 -0
- data/lib/nanoc/cli/commands/watch.rb +124 -0
- data/lib/nanoc/cli/error_handler.rb +199 -0
- data/lib/nanoc/cli/logger.rb +92 -0
- data/lib/nanoc/data_sources.rb +29 -0
- data/lib/nanoc/data_sources/deprecated/delicious.rb +42 -0
- data/lib/nanoc/data_sources/deprecated/last_fm.rb +87 -0
- data/lib/nanoc/data_sources/deprecated/twitter.rb +38 -0
- data/lib/nanoc/data_sources/filesystem.rb +299 -0
- data/lib/nanoc/data_sources/filesystem_unified.rb +121 -0
- data/lib/nanoc/data_sources/filesystem_verbose.rb +91 -0
- data/lib/nanoc/extra.rb +24 -0
- data/lib/nanoc/extra/auto_compiler.rb +103 -0
- data/lib/nanoc/extra/chick.rb +125 -0
- data/lib/nanoc/extra/core_ext.rb +6 -0
- data/lib/nanoc/extra/core_ext/enumerable.rb +33 -0
- data/lib/nanoc/extra/core_ext/pathname.rb +30 -0
- data/lib/nanoc/extra/core_ext/time.rb +19 -0
- data/lib/nanoc/extra/deployer.rb +47 -0
- data/lib/nanoc/extra/deployers.rb +15 -0
- data/lib/nanoc/extra/deployers/fog.rb +98 -0
- data/lib/nanoc/extra/deployers/rsync.rb +70 -0
- data/lib/nanoc/extra/file_proxy.rb +40 -0
- data/lib/nanoc/extra/pruner.rb +86 -0
- data/lib/nanoc/extra/validators.rb +12 -0
- data/lib/nanoc/extra/validators/links.rb +268 -0
- data/lib/nanoc/extra/validators/w3c.rb +95 -0
- data/lib/nanoc/extra/vcs.rb +66 -0
- data/lib/nanoc/extra/vcses.rb +17 -0
- data/lib/nanoc/extra/vcses/bazaar.rb +25 -0
- data/lib/nanoc/extra/vcses/dummy.rb +24 -0
- data/lib/nanoc/extra/vcses/git.rb +25 -0
- data/lib/nanoc/extra/vcses/mercurial.rb +25 -0
- data/lib/nanoc/extra/vcses/subversion.rb +25 -0
- data/lib/nanoc/filters.rb +59 -0
- data/lib/nanoc/filters/asciidoc.rb +38 -0
- data/lib/nanoc/filters/bluecloth.rb +19 -0
- data/lib/nanoc/filters/coderay.rb +21 -0
- data/lib/nanoc/filters/coffeescript.rb +20 -0
- data/lib/nanoc/filters/colorize_syntax.rb +298 -0
- data/lib/nanoc/filters/erb.rb +38 -0
- data/lib/nanoc/filters/erubis.rb +34 -0
- data/lib/nanoc/filters/haml.rb +27 -0
- data/lib/nanoc/filters/kramdown.rb +20 -0
- data/lib/nanoc/filters/less.rb +53 -0
- data/lib/nanoc/filters/markaby.rb +20 -0
- data/lib/nanoc/filters/maruku.rb +20 -0
- data/lib/nanoc/filters/mustache.rb +24 -0
- data/lib/nanoc/filters/rainpress.rb +19 -0
- data/lib/nanoc/filters/rdiscount.rb +22 -0
- data/lib/nanoc/filters/rdoc.rb +33 -0
- data/lib/nanoc/filters/redcarpet.rb +62 -0
- data/lib/nanoc/filters/redcloth.rb +47 -0
- data/lib/nanoc/filters/relativize_paths.rb +94 -0
- data/lib/nanoc/filters/rubypants.rb +20 -0
- data/lib/nanoc/filters/sass.rb +74 -0
- data/lib/nanoc/filters/slim.rb +25 -0
- data/lib/nanoc/filters/typogruby.rb +23 -0
- data/lib/nanoc/filters/uglify_js.rb +42 -0
- data/lib/nanoc/filters/xsl.rb +46 -0
- data/lib/nanoc/filters/yui_compressor.rb +23 -0
- data/lib/nanoc/helpers.rb +16 -0
- data/lib/nanoc/helpers/blogging.rb +319 -0
- data/lib/nanoc/helpers/breadcrumbs.rb +40 -0
- data/lib/nanoc/helpers/capturing.rb +138 -0
- data/lib/nanoc/helpers/filtering.rb +50 -0
- data/lib/nanoc/helpers/html_escape.rb +55 -0
- data/lib/nanoc/helpers/link_to.rb +151 -0
- data/lib/nanoc/helpers/rendering.rb +140 -0
- data/lib/nanoc/helpers/tagging.rb +71 -0
- data/lib/nanoc/helpers/text.rb +44 -0
- data/lib/nanoc/helpers/xml_sitemap.rb +76 -0
- data/lib/nanoc/tasks.rb +10 -0
- data/lib/nanoc/tasks/clean.rake +16 -0
- data/lib/nanoc/tasks/clean.rb +29 -0
- data/lib/nanoc/tasks/deploy/rsync.rake +16 -0
- data/lib/nanoc/tasks/validate.rake +92 -0
- data/nanoc.gemspec +49 -0
- data/tasks/doc.rake +16 -0
- data/tasks/test.rake +46 -0
- data/test/base/core_ext/array_spec.rb +73 -0
- data/test/base/core_ext/hash_spec.rb +98 -0
- data/test/base/core_ext/pathname_spec.rb +27 -0
- data/test/base/core_ext/string_spec.rb +37 -0
- data/test/base/test_checksum_store.rb +35 -0
- data/test/base/test_code_snippet.rb +31 -0
- data/test/base/test_compiler.rb +403 -0
- data/test/base/test_compiler_dsl.rb +161 -0
- data/test/base/test_context.rb +31 -0
- data/test/base/test_data_source.rb +46 -0
- data/test/base/test_dependency_tracker.rb +262 -0
- data/test/base/test_directed_graph.rb +288 -0
- data/test/base/test_filter.rb +83 -0
- data/test/base/test_item.rb +179 -0
- data/test/base/test_item_rep.rb +579 -0
- data/test/base/test_layout.rb +59 -0
- data/test/base/test_memoization.rb +90 -0
- data/test/base/test_notification_center.rb +34 -0
- data/test/base/test_outdatedness_checker.rb +394 -0
- data/test/base/test_plugin.rb +30 -0
- data/test/base/test_rule.rb +19 -0
- data/test/base/test_rule_context.rb +65 -0
- data/test/base/test_site.rb +190 -0
- data/test/cli/commands/test_compile.rb +33 -0
- data/test/cli/commands/test_create_item.rb +14 -0
- data/test/cli/commands/test_create_layout.rb +28 -0
- data/test/cli/commands/test_create_site.rb +24 -0
- data/test/cli/commands/test_deploy.rb +74 -0
- data/test/cli/commands/test_help.rb +12 -0
- data/test/cli/commands/test_info.rb +11 -0
- data/test/cli/commands/test_prune.rb +98 -0
- data/test/cli/commands/test_update.rb +10 -0
- data/test/cli/test_cli.rb +102 -0
- data/test/cli/test_error_handler.rb +29 -0
- data/test/cli/test_logger.rb +10 -0
- data/test/data_sources/test_filesystem.rb +433 -0
- data/test/data_sources/test_filesystem_unified.rb +536 -0
- data/test/data_sources/test_filesystem_verbose.rb +357 -0
- data/test/extra/core_ext/test_enumerable.rb +30 -0
- data/test/extra/core_ext/test_pathname.rb +17 -0
- data/test/extra/core_ext/test_time.rb +15 -0
- data/test/extra/deployers/test_fog.rb +67 -0
- data/test/extra/deployers/test_rsync.rb +100 -0
- data/test/extra/test_auto_compiler.rb +417 -0
- data/test/extra/test_file_proxy.rb +19 -0
- data/test/extra/test_vcs.rb +22 -0
- data/test/extra/validators/test_links.rb +62 -0
- data/test/extra/validators/test_w3c.rb +47 -0
- data/test/filters/test_asciidoc.rb +22 -0
- data/test/filters/test_bluecloth.rb +18 -0
- data/test/filters/test_coderay.rb +44 -0
- data/test/filters/test_coffeescript.rb +18 -0
- data/test/filters/test_colorize_syntax.rb +379 -0
- data/test/filters/test_erb.rb +105 -0
- data/test/filters/test_erubis.rb +78 -0
- data/test/filters/test_haml.rb +96 -0
- data/test/filters/test_kramdown.rb +18 -0
- data/test/filters/test_less.rb +113 -0
- data/test/filters/test_markaby.rb +24 -0
- data/test/filters/test_maruku.rb +18 -0
- data/test/filters/test_mustache.rb +25 -0
- data/test/filters/test_rainpress.rb +29 -0
- data/test/filters/test_rdiscount.rb +31 -0
- data/test/filters/test_rdoc.rb +18 -0
- data/test/filters/test_redcarpet.rb +73 -0
- data/test/filters/test_redcloth.rb +33 -0
- data/test/filters/test_relativize_paths.rb +533 -0
- data/test/filters/test_rubypants.rb +18 -0
- data/test/filters/test_sass.rb +229 -0
- data/test/filters/test_slim.rb +35 -0
- data/test/filters/test_typogruby.rb +21 -0
- data/test/filters/test_uglify_js.rb +30 -0
- data/test/filters/test_xsl.rb +105 -0
- data/test/filters/test_yui_compressor.rb +44 -0
- data/test/gem_loader.rb +11 -0
- data/test/helper.rb +207 -0
- data/test/helpers/test_blogging.rb +754 -0
- data/test/helpers/test_breadcrumbs.rb +81 -0
- data/test/helpers/test_capturing.rb +41 -0
- data/test/helpers/test_filtering.rb +106 -0
- data/test/helpers/test_html_escape.rb +32 -0
- data/test/helpers/test_link_to.rb +249 -0
- data/test/helpers/test_rendering.rb +89 -0
- data/test/helpers/test_tagging.rb +87 -0
- data/test/helpers/test_text.rb +24 -0
- data/test/helpers/test_xml_sitemap.rb +103 -0
- data/test/tasks/test_clean.rb +67 -0
- metadata +327 -15
- data/bin/nanoc-select +0 -86
- data/lib/nanoc-select.rb +0 -11
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Nanoc::DataSources
|
|
4
|
+
|
|
5
|
+
autoload 'Filesystem', 'nanoc/data_sources/filesystem'
|
|
6
|
+
autoload 'FilesystemUnified', 'nanoc/data_sources/filesystem_unified'
|
|
7
|
+
autoload 'FilesystemVerbose', 'nanoc/data_sources/filesystem_verbose'
|
|
8
|
+
|
|
9
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemVerbose', :filesystem_verbose
|
|
10
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified', :filesystem_unified
|
|
11
|
+
|
|
12
|
+
# Deprecated; fetch data from online data sources manually instead
|
|
13
|
+
# TODO [in nanoc 4.0] remove me
|
|
14
|
+
autoload 'Delicious', 'nanoc/data_sources/deprecated/delicious'
|
|
15
|
+
autoload 'LastFM', 'nanoc/data_sources/deprecated/last_fm'
|
|
16
|
+
autoload 'Twitter', 'nanoc/data_sources/deprecated/twitter'
|
|
17
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::Delicious', :delicious
|
|
18
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::LastFM', :last_fm
|
|
19
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::Twitter', :twitter
|
|
20
|
+
|
|
21
|
+
# Deprecated; use `filesystem_verbose` or `filesystem_unified` instead
|
|
22
|
+
# TODO [in nanoc 4.0] remove me
|
|
23
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemVerbose', :filesystem
|
|
24
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified', :filesystem_combined
|
|
25
|
+
Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified', :filesystem_compact
|
|
26
|
+
FilesystemCombined = FilesystemUnified
|
|
27
|
+
FilesystemCompact = FilesystemUnified
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Nanoc::DataSources
|
|
4
|
+
|
|
5
|
+
# @deprecated Fetch data from online data sources manually instead
|
|
6
|
+
class Delicious < Nanoc::DataSource
|
|
7
|
+
|
|
8
|
+
def items
|
|
9
|
+
@items ||= begin
|
|
10
|
+
require 'json'
|
|
11
|
+
|
|
12
|
+
# Get data
|
|
13
|
+
@http_client ||= Nanoc::Extra::CHiCk::Client.new
|
|
14
|
+
status, headers, data = *@http_client.get("http://feeds.delicious.com/v2/json/#{self.config[:username]}")
|
|
15
|
+
|
|
16
|
+
# Parse as JSON
|
|
17
|
+
raw_items = JSON.parse(data)
|
|
18
|
+
|
|
19
|
+
# Convert to items
|
|
20
|
+
raw_items.enum_with_index.map do |raw_item, i|
|
|
21
|
+
# Get data
|
|
22
|
+
content = raw_item['n']
|
|
23
|
+
attributes = {
|
|
24
|
+
:url => raw_item['u'],
|
|
25
|
+
:description => raw_item['d'],
|
|
26
|
+
:tags => raw_item['t'],
|
|
27
|
+
:date => Time.parse(raw_item['dt']),
|
|
28
|
+
:note => raw_item['n'],
|
|
29
|
+
:author => raw_item['a']
|
|
30
|
+
}
|
|
31
|
+
identifier = "/#{i}/"
|
|
32
|
+
mtime = nil
|
|
33
|
+
|
|
34
|
+
# Build item
|
|
35
|
+
Nanoc::Item.new(content, attributes, identifier, mtime)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Nanoc::DataSources
|
|
4
|
+
|
|
5
|
+
# @deprecated Fetch data from online data sources manually instead
|
|
6
|
+
class LastFM < Nanoc::DataSource
|
|
7
|
+
|
|
8
|
+
def items
|
|
9
|
+
@items ||= begin
|
|
10
|
+
require 'json'
|
|
11
|
+
require 'uri'
|
|
12
|
+
|
|
13
|
+
# Check configuration
|
|
14
|
+
if self.config[:username].nil?
|
|
15
|
+
raise RuntimeError, "LastFM data source requires a username in the configuration"
|
|
16
|
+
end
|
|
17
|
+
if self.config[:api_key].nil?
|
|
18
|
+
raise RuntimeError, "LastFM data source requires an API key in the configuration"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Get data
|
|
22
|
+
@http_client ||= Nanoc::Extra::CHiCk::Client.new
|
|
23
|
+
status, headers, data = *@http_client.get(
|
|
24
|
+
'http://ws.audioscrobbler.com/2.0/' +
|
|
25
|
+
'?method=user.getRecentTracks' +
|
|
26
|
+
'&format=json' +
|
|
27
|
+
'&user=' + URI.escape(self.config[:username]) +
|
|
28
|
+
'&api_key=' + URI.escape(self.config[:api_key])
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Parse as JSON
|
|
32
|
+
parsed_data = JSON.parse(data)
|
|
33
|
+
raw_items = parsed_data['recenttracks']['track']
|
|
34
|
+
|
|
35
|
+
# Convert to items
|
|
36
|
+
raw_items.enum_with_index.map do |raw_item, i|
|
|
37
|
+
# Get artist data
|
|
38
|
+
artist_status, artist_headers, artist_data = *@http_client.get(
|
|
39
|
+
'http://ws.audioscrobbler.com/2.0/' +
|
|
40
|
+
'?method=artist.getInfo' +
|
|
41
|
+
'&format=json' +
|
|
42
|
+
(
|
|
43
|
+
raw_item['artist']['mbid'].empty? ?
|
|
44
|
+
'&artist=' + URI.escape(raw_item['artist']['#text']) :
|
|
45
|
+
'&mbid=' + URI.escape(raw_item['artist']['mbid'])
|
|
46
|
+
) +
|
|
47
|
+
'&api_key=' + URI.escape(self.config[:api_key])
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Parse as JSON
|
|
51
|
+
parsed_artist_data = JSON.parse(artist_data)
|
|
52
|
+
raw_artist_info = parsed_artist_data['artist']
|
|
53
|
+
|
|
54
|
+
# Build data
|
|
55
|
+
content = ''
|
|
56
|
+
|
|
57
|
+
# Handle track dates
|
|
58
|
+
if raw_item['@attr'] && raw_item['@attr']['nowplaying'] == 'true'
|
|
59
|
+
track_played_at = Time.now
|
|
60
|
+
now_playing = true
|
|
61
|
+
else
|
|
62
|
+
played_at = Time.parse(raw_item['date']['#text'])
|
|
63
|
+
now_playing = false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
attributes = {
|
|
67
|
+
:name => raw_item['name'],
|
|
68
|
+
:artist => {
|
|
69
|
+
:name => raw_artist_info['name'],
|
|
70
|
+
:url => raw_artist_info['url']
|
|
71
|
+
},
|
|
72
|
+
:url => raw_item['url'],
|
|
73
|
+
:played_at => track_played_at,
|
|
74
|
+
:now_playing => now_playing
|
|
75
|
+
}
|
|
76
|
+
identifier = "/#{i}/"
|
|
77
|
+
mtime = nil
|
|
78
|
+
|
|
79
|
+
# Build item
|
|
80
|
+
Nanoc::Item.new(content, attributes, identifier, mtime)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Nanoc::DataSources
|
|
4
|
+
|
|
5
|
+
# @deprecated Fetch data from online data sources manually instead
|
|
6
|
+
class Twitter < Nanoc::DataSource
|
|
7
|
+
|
|
8
|
+
def items
|
|
9
|
+
@item ||= begin
|
|
10
|
+
require 'json'
|
|
11
|
+
|
|
12
|
+
# Get data
|
|
13
|
+
@http_client ||= Nanoc::Extra::CHiCk::Client.new
|
|
14
|
+
status, headers, data = *@http_client.get("http://twitter.com/statuses/user_timeline/#{self.config[:username]}.json")
|
|
15
|
+
|
|
16
|
+
# Parse as JSON
|
|
17
|
+
raw_items = JSON.parse(data)
|
|
18
|
+
|
|
19
|
+
# Convert to items
|
|
20
|
+
raw_items.enum_with_index.map do |raw_item, i|
|
|
21
|
+
# Get data
|
|
22
|
+
content = raw_item['text']
|
|
23
|
+
attributes = {
|
|
24
|
+
:created_at => raw_item['created_at'],
|
|
25
|
+
:source => raw_item['source']
|
|
26
|
+
}
|
|
27
|
+
identifier = "/#{raw_item['id']}/"
|
|
28
|
+
mtime = Time.parse(raw_item['created_at'])
|
|
29
|
+
|
|
30
|
+
# Build item
|
|
31
|
+
Nanoc::Item.new(content, attributes, identifier, mtime)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Nanoc::DataSources
|
|
4
|
+
|
|
5
|
+
# Provides functionality common across all filesystem data sources.
|
|
6
|
+
module Filesystem
|
|
7
|
+
|
|
8
|
+
# The VCS that will be called when adding, deleting and moving files. If
|
|
9
|
+
# no VCS has been set, or if the VCS has been set to `nil`, a dummy VCS
|
|
10
|
+
# will be returned.
|
|
11
|
+
#
|
|
12
|
+
# @return [Nanoc::Extra::VCS, nil] The VCS that will be used.
|
|
13
|
+
def vcs
|
|
14
|
+
@vcs ||= Nanoc::Extra::VCSes::Dummy.new
|
|
15
|
+
end
|
|
16
|
+
attr_writer :vcs
|
|
17
|
+
|
|
18
|
+
# See {Nanoc::DataSource#up}.
|
|
19
|
+
def up
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# See {Nanoc::DataSource#down}.
|
|
23
|
+
def down
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# See {Nanoc::DataSource#setup}.
|
|
27
|
+
def setup
|
|
28
|
+
# Create directories
|
|
29
|
+
%w( content layouts ).each do |dir|
|
|
30
|
+
FileUtils.mkdir_p(dir)
|
|
31
|
+
vcs.add(dir)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# See {Nanoc::DataSource#items}.
|
|
36
|
+
def items
|
|
37
|
+
load_objects('content', 'item', Nanoc::Item)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# See {Nanoc::DataSource#layouts}.
|
|
41
|
+
def layouts
|
|
42
|
+
load_objects('layouts', 'layout', Nanoc::Layout)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# See {Nanoc::DataSource#create_item}.
|
|
46
|
+
def create_item(content, attributes, identifier, params={})
|
|
47
|
+
create_object('content', content, attributes, identifier, params)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# See {Nanoc::DataSource#create_layout}.
|
|
51
|
+
def create_layout(content, attributes, identifier, params={})
|
|
52
|
+
create_object('layouts', content, attributes, identifier, params)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Creates a new object (item or layout) on disk in dir_name according to
|
|
58
|
+
# the given identifier. The file will have its attributes taken from the
|
|
59
|
+
# attributes hash argument and its content from the content argument.
|
|
60
|
+
def create_object(dir_name, content, attributes, identifier, params={})
|
|
61
|
+
raise NotImplementedError.new(
|
|
62
|
+
"#{self.class} does not implement ##{name}"
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Creates instances of klass corresponding to the files in dir_name. The
|
|
67
|
+
# kind attribute indicates the kind of object that is being loaded and is
|
|
68
|
+
# used solely for debugging purposes.
|
|
69
|
+
#
|
|
70
|
+
# This particular implementation loads objects from a filesystem-based
|
|
71
|
+
# data source where content and attributes can be spread over two separate
|
|
72
|
+
# files. The content and meta-file are optional (but at least one of them
|
|
73
|
+
# needs to be present, obviously) and the content file can start with a
|
|
74
|
+
# metadata section.
|
|
75
|
+
#
|
|
76
|
+
# @see Nanoc::DataSources::Filesystem#load_objects
|
|
77
|
+
def load_objects(dir_name, kind, klass)
|
|
78
|
+
all_split_files_in(dir_name).map do |base_filename, (meta_ext, content_ext)|
|
|
79
|
+
# Get filenames
|
|
80
|
+
meta_filename = filename_for(base_filename, meta_ext)
|
|
81
|
+
content_filename = filename_for(base_filename, content_ext)
|
|
82
|
+
|
|
83
|
+
# Read content and metadata
|
|
84
|
+
is_binary = !!(content_filename && !@site.config[:text_extensions].include?(File.extname(content_filename)[1..-1]))
|
|
85
|
+
if is_binary && klass == Nanoc::Item
|
|
86
|
+
meta = (meta_filename && YAML.load_file(meta_filename)) || {}
|
|
87
|
+
content_or_filename = content_filename
|
|
88
|
+
else
|
|
89
|
+
meta, content_or_filename = parse(content_filename, meta_filename, kind)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get attributes
|
|
93
|
+
attributes = {
|
|
94
|
+
:filename => content_filename,
|
|
95
|
+
:content_filename => content_filename,
|
|
96
|
+
:meta_filename => meta_filename,
|
|
97
|
+
:extension => content_filename ? ext_of(content_filename)[1..-1] : nil,
|
|
98
|
+
# WARNING :file is deprecated; please create a File object manually
|
|
99
|
+
# using the :content_filename or :meta_filename attributes.
|
|
100
|
+
# TODO [in nanoc 4.0] remove me
|
|
101
|
+
:file => content_filename ? Nanoc::Extra::FileProxy.new(content_filename) : nil
|
|
102
|
+
}.merge(meta)
|
|
103
|
+
|
|
104
|
+
# Get identifier
|
|
105
|
+
if meta_filename
|
|
106
|
+
identifier = identifier_for_filename(meta_filename[(dir_name.length+1)..-1])
|
|
107
|
+
elsif content_filename
|
|
108
|
+
identifier = identifier_for_filename(content_filename[(dir_name.length+1)..-1])
|
|
109
|
+
else
|
|
110
|
+
raise RuntimeError, "meta_filename and content_filename are both nil"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get modification times
|
|
114
|
+
meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil
|
|
115
|
+
content_mtime = content_filename ? File.stat(content_filename).mtime : nil
|
|
116
|
+
if meta_mtime && content_mtime
|
|
117
|
+
mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
|
|
118
|
+
elsif meta_mtime
|
|
119
|
+
mtime = meta_mtime
|
|
120
|
+
elsif content_mtime
|
|
121
|
+
mtime = content_mtime
|
|
122
|
+
else
|
|
123
|
+
raise RuntimeError, "meta_mtime and content_mtime are both nil"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Create layout object
|
|
127
|
+
klass.new(
|
|
128
|
+
content_or_filename, attributes, identifier,
|
|
129
|
+
:binary => is_binary, :mtime => mtime
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Finds all items/layouts/... in the given base directory. Returns a hash
|
|
135
|
+
# in which the keys are the file's dirname + basenames, and the values a
|
|
136
|
+
# pair consisting of the metafile extension and the content file
|
|
137
|
+
# extension. The meta file extension or the content file extension can be
|
|
138
|
+
# nil, but not both. Backup files are ignored. For example:
|
|
139
|
+
#
|
|
140
|
+
# {
|
|
141
|
+
# 'content/foo' => [ 'yaml', 'html' ],
|
|
142
|
+
# 'content/bar' => [ 'yaml', nil ],
|
|
143
|
+
# 'content/qux' => [ nil, 'html' ]
|
|
144
|
+
# }
|
|
145
|
+
def all_split_files_in(dir_name)
|
|
146
|
+
# Get all good file names
|
|
147
|
+
filenames = Dir[dir_name + '/**/*'].select { |i| File.file?(i) }
|
|
148
|
+
filenames.reject! { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }
|
|
149
|
+
|
|
150
|
+
# Group by identifier
|
|
151
|
+
grouped_filenames = filenames.group_by { |fn| basename_of(fn) }
|
|
152
|
+
|
|
153
|
+
# Convert values into metafile/content file extension tuple
|
|
154
|
+
grouped_filenames.each_pair do |key, filenames|
|
|
155
|
+
# Divide
|
|
156
|
+
meta_filenames = filenames.select { |fn| ext_of(fn) == '.yaml' }
|
|
157
|
+
content_filenames = filenames.select { |fn| ext_of(fn) != '.yaml' }
|
|
158
|
+
|
|
159
|
+
# Check number of files per type
|
|
160
|
+
if ![ 0, 1 ].include?(meta_filenames.size)
|
|
161
|
+
raise RuntimeError, "Found #{meta_filenames.size} meta files for #{key}; expected 0 or 1"
|
|
162
|
+
end
|
|
163
|
+
if ![ 0, 1 ].include?(content_filenames.size)
|
|
164
|
+
raise RuntimeError, "Found #{content_filenames.size} content files for #{key}; expected 0 or 1"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Reorder elements and convert to extnames
|
|
168
|
+
filenames[0] = meta_filenames[0] ? 'yaml' : nil
|
|
169
|
+
filenames[1] = content_filenames[0] ? ext_of(content_filenames[0])[1..-1] || '': nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Done
|
|
173
|
+
grouped_filenames
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Returns the filename for the given base filename and the extension.
|
|
177
|
+
#
|
|
178
|
+
# If the extension is nil, this function should return nil as well.
|
|
179
|
+
#
|
|
180
|
+
# A simple implementation would simply concatenate the base filename, a
|
|
181
|
+
# period and an extension (which is what the
|
|
182
|
+
# {Nanoc::DataSources::FilesystemCompact} data source does), but other
|
|
183
|
+
# data sources may prefer to implement this differently (for example,
|
|
184
|
+
# {Nanoc::DataSources::FilesystemVerbose} doubles the last part of the
|
|
185
|
+
# basename before concatenating it with a period and the extension).
|
|
186
|
+
def filename_for(base_filename, ext)
|
|
187
|
+
raise NotImplementedError.new(
|
|
188
|
+
"#{self.class} does not implement #filename_for"
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns the identifier that corresponds with the given filename, which
|
|
193
|
+
# can be the content filename or the meta filename.
|
|
194
|
+
def identifier_for_filename(filename)
|
|
195
|
+
raise NotImplementedError.new(
|
|
196
|
+
"#{self.class} does not implement #identifier_for_filename"
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Returns the base name of filename, i.e. filename with the first or all
|
|
201
|
+
# extensions stripped off. By default, all extensions are stripped off,
|
|
202
|
+
# but when allow_periods_in_identifiers is set to true in the site
|
|
203
|
+
# configuration, only the last extension will be stripped .
|
|
204
|
+
def basename_of(filename)
|
|
205
|
+
filename.sub(extension_regex, '')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Returns the extension(s) of filename. Supports multiple extensions.
|
|
209
|
+
# Includes the leading period.
|
|
210
|
+
def ext_of(filename)
|
|
211
|
+
filename =~ extension_regex ? $1 : ''
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Returns a regex that is used for determining the extension of a file
|
|
215
|
+
# name. The first match group will be the entire extension, including the
|
|
216
|
+
# leading period.
|
|
217
|
+
def extension_regex
|
|
218
|
+
if @config && @config[:allow_periods_in_identifiers]
|
|
219
|
+
/(\.[^\/\.]+$)/
|
|
220
|
+
else
|
|
221
|
+
/(\.[^\/]+$)/
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Parses the file named `filename` and returns an array with its first
|
|
226
|
+
# element a hash with the file's metadata, and with its second element the
|
|
227
|
+
# file content itself.
|
|
228
|
+
def parse(content_filename, meta_filename, kind)
|
|
229
|
+
# Read content and metadata from separate files
|
|
230
|
+
if meta_filename
|
|
231
|
+
content = content_filename ? read(content_filename) : ''
|
|
232
|
+
meta = YAML.load(read(meta_filename)) || {}
|
|
233
|
+
|
|
234
|
+
return [ meta, content ]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Read data
|
|
238
|
+
data = read(content_filename)
|
|
239
|
+
|
|
240
|
+
# Check presence of metadata section
|
|
241
|
+
if data !~ /\A-{3,5}\s*$/
|
|
242
|
+
return [ {}, data ]
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Split data
|
|
246
|
+
pieces = data.split(/^(-{5}|-{3})\s*$/)
|
|
247
|
+
if pieces.size < 4
|
|
248
|
+
raise RuntimeError.new(
|
|
249
|
+
"The file '#{content_filename}' appears to start with a metadata section (three or five dashes at the top) but it does not seem to be in the correct format."
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Parse
|
|
254
|
+
meta = YAML.load(pieces[2]) || {}
|
|
255
|
+
content = pieces[4..-1].join.strip
|
|
256
|
+
|
|
257
|
+
# Done
|
|
258
|
+
[ meta, content ]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Reads the content of the file with the given name and returns a string
|
|
262
|
+
# in UTF-8 encoding. The original encoding of the string is derived from
|
|
263
|
+
# the default external encoding, but this can be overridden by the
|
|
264
|
+
# “encoding” configuration attribute in the data source configuration.
|
|
265
|
+
def read(filename)
|
|
266
|
+
# Read
|
|
267
|
+
begin
|
|
268
|
+
data = File.read(filename)
|
|
269
|
+
rescue => e
|
|
270
|
+
raise RuntimeError.new("Could not read #{filename}: #{e.inspect}")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Fix
|
|
274
|
+
if data.respond_to?(:encode!)
|
|
275
|
+
if @config && @config[:encoding]
|
|
276
|
+
original_encoding = Encoding.find(@config[:encoding])
|
|
277
|
+
data.force_encoding(@config[:encoding])
|
|
278
|
+
else
|
|
279
|
+
original_encoding = data.encoding
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
data.encode!('UTF-8') rescue raise_encoding_error(filename, original_encoding)
|
|
283
|
+
raise_encoding_error(filename, original_encoding) if !data.valid_encoding?
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Remove UTF-8 BOM (ugly)
|
|
287
|
+
data.gsub!("\xEF\xBB\xBF", '')
|
|
288
|
+
|
|
289
|
+
data
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Raises an invalid encoding error for the given filename and encoding.
|
|
293
|
+
def raise_encoding_error(filename, encoding)
|
|
294
|
+
raise RuntimeError.new("Could not read #{filename} because the file is not valid #{encoding}.")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
end
|