nanoc-filesystem-i18n 0.1.0.pre1
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/LICENSE +25 -0
- data/README.rdoc +109 -0
- data/Rakefile +59 -0
- data/lib/nanoc3/data_sources/filesystem_i18n.rb +399 -0
- data/lib/nanoc3/data_sources/filesystem_i18n/version.rb +12 -0
- data/lib/nanoc3/extra/i18n.rb +50 -0
- data/test/helper.rb +90 -0
- data/test/test_filesystem.rb +347 -0
- data/test/test_filesystem_i18n.rb +61 -0
- data/test/test_filesystem_unified.rb +461 -0
- data/test/test_filesystem_verbose.rb +260 -0
- metadata +129 -0
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2010 Yann Lugrin
|
2
|
+
|
3
|
+
The original source code is part of nanoc software with the same licence
|
4
|
+
and have the following copyright notice:
|
5
|
+
copyright (c) 2007-2010 Denis Defreyne and nanoc contributors
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
= nanoc-filesystem-i18n
|
2
|
+
|
3
|
+
The filesystem_i18n data source is a localized data source for a nanoc
|
4
|
+
site. It stores all data as files on the hard disk and is fully compatible
|
5
|
+
with {Nanoc3::DataSources::FilesystemUnified} and {Nanoc3::DataSources::FilesystemVerbose}.
|
6
|
+
|
7
|
+
== Resources
|
8
|
+
|
9
|
+
- FilesystemI18n (http://github.com/yannlugrin/nanoc-filesystem-i18n)
|
10
|
+
- Nanoc (http://nanoc.stoneship.org)
|
11
|
+
- Nanoc on Github (http://github.com/ddfreyne/nanoc)
|
12
|
+
- Ruby I18n (http://github.com/svenfuchs/i18n)
|
13
|
+
|
14
|
+
== Documentation
|
15
|
+
|
16
|
+
=== Installation
|
17
|
+
|
18
|
+
gem install nanoc-filesystem-i18n --pre
|
19
|
+
|
20
|
+
Add following require in your `lib/default.rb` file:
|
21
|
+
|
22
|
+
require 'nanoc3/data_sources/filesystem_i18n'
|
23
|
+
|
24
|
+
Edit `config.yaml` file of your nanoc site and change data_sources type
|
25
|
+
from `filesystem_unified` or `filesystem_verbose` to `filesystem_i18n`:
|
26
|
+
|
27
|
+
data_sources:
|
28
|
+
-
|
29
|
+
type: filesystem_i18n
|
30
|
+
|
31
|
+
In the data source section of `config.yaml` file, add following information:
|
32
|
+
|
33
|
+
config:
|
34
|
+
locale:
|
35
|
+
# list of availables locale, ser default to true to select default
|
36
|
+
# locale.
|
37
|
+
availables:
|
38
|
+
fr:
|
39
|
+
name: Francais
|
40
|
+
en:
|
41
|
+
name: English
|
42
|
+
default: true
|
43
|
+
# objects should be not localized
|
44
|
+
exclude:
|
45
|
+
item: ['/css*', '/js*']
|
46
|
+
layout: ['*']
|
47
|
+
|
48
|
+
See following configuration section for more information.
|
49
|
+
|
50
|
+
=== Data source specifications
|
51
|
+
|
52
|
+
The filesystem_i18n data source stores its items and layouts in nested
|
53
|
+
directories or files. Each directory represents a single item or layout,
|
54
|
+
but an item can be a simple file in a directory. The root directory for
|
55
|
+
items is the `content` directory; for layouts it is the `layouts`
|
56
|
+
directory.
|
57
|
+
|
58
|
+
Every object (item or layout) is represented by a meta file and one or
|
59
|
+
more content files with a minimum of one file. The content file contains
|
60
|
+
he actual item content or layout, while the meta file contains the item’s
|
61
|
+
or the layout’s metadata, formatted as YAML.
|
62
|
+
|
63
|
+
Both meta files and content files are named after its parent directory
|
64
|
+
(i.e. item). For example, an item/layout named `foo` will have a directory
|
65
|
+
named `foo`, with e.g. a `foo.markdown` or `index.markdown` content file
|
66
|
+
and a `foo.yaml` or `index.yaml` meta file. An item/layout named `foo/bar`
|
67
|
+
can be also created in parent directory named `foo` without dedicated
|
68
|
+
directory, with e.g. a `foo.markdown` content file and `foo.yaml` meta
|
69
|
+
file. Root item already named `index.markdown` for content file (extension
|
70
|
+
can be different) and `index.yaml` for meta file.
|
71
|
+
|
72
|
+
Content file extensions are not used for determining the filter that
|
73
|
+
should be run; the meta file or configuration file defines the list of
|
74
|
+
filters. The meta file extension must always be `.yaml`, though.
|
75
|
+
|
76
|
+
An item/layout content file named `foo.markdown` contain default content
|
77
|
+
for all locales, but you can create a content file for each locales with
|
78
|
+
e.g. a `foo.fr.markdown` for locale `fr`. If default locale for site is
|
79
|
+
`fr` and the `foo.fr.markdown` file is present but `foo.markdow` file not,
|
80
|
+
the `fr` content file is used by default.
|
81
|
+
|
82
|
+
The identifier is calculated by stripping the extension (part after last dot)
|
83
|
+
and locale code.
|
84
|
+
|
85
|
+
=== Configuration
|
86
|
+
|
87
|
+
soon...
|
88
|
+
|
89
|
+
=== Manage locale meta and content
|
90
|
+
|
91
|
+
soon...
|
92
|
+
|
93
|
+
=== Deployement
|
94
|
+
|
95
|
+
soon...
|
96
|
+
|
97
|
+
== Note on Patches/Pull Requests
|
98
|
+
|
99
|
+
* Fork the project.
|
100
|
+
* Make your feature addition or bug fix.
|
101
|
+
* Add tests for it. This is important so I don't break it in a
|
102
|
+
future version unintentionally.
|
103
|
+
* Commit, do not mess with rakefile, version, or history.
|
104
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
105
|
+
* Send me a pull request. Bonus points for topic branches.
|
106
|
+
|
107
|
+
== Copyright
|
108
|
+
|
109
|
+
Copyright (c) 2010 Yann Lugrin. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require File.dirname(__FILE__) + '/lib/nanoc3/data_sources/filesystem_i18n/version'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = 'nanoc-filesystem-i18n'
|
9
|
+
gem.version = Nanoc3::DataSources::FilesystemI18n::Version
|
10
|
+
gem.summary = %Q{I18n filesystem based data source for nanoc}
|
11
|
+
gem.description = %Q{I18n filesystem based data source for nanoc. Compatible with nanoc 3 and default filesystem based data source.}
|
12
|
+
gem.email = 'yann.lugrin@sans-savoir.net'
|
13
|
+
gem.homepage = 'http://github.com/yannlugrin/nanoc-filesystem-i18n'
|
14
|
+
gem.authors = ['Yann Lugrin']
|
15
|
+
gem.add_dependency 'nanoc', '>= 3.1.2'
|
16
|
+
gem.add_dependency 'i18n', '>= 0'
|
17
|
+
gem.add_development_dependency 'minitest', '>= 0'
|
18
|
+
gem.add_development_dependency 'yard', '>= 0'
|
19
|
+
gem.files.exclude '.gitignore', '.document', 'nanoc-filesystem-i18n.gemspec'
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/**/test_*.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
task :rcov do
|
43
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
task :test => :check_dependencies
|
48
|
+
|
49
|
+
task :default => :test
|
50
|
+
|
51
|
+
begin
|
52
|
+
require 'nanoc3'
|
53
|
+
require 'yard'
|
54
|
+
YARD::Rake::YardocTask.new
|
55
|
+
rescue LoadError
|
56
|
+
task :yardoc do
|
57
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,399 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'nanoc3'
|
4
|
+
require 'nanoc3/extra/i18n'
|
5
|
+
|
6
|
+
module Nanoc3::DataSources
|
7
|
+
|
8
|
+
# The filesystem_i18n data source is a localized data source for a nanoc
|
9
|
+
# site. It stores all data as files on the hard disk and is fully compatible
|
10
|
+
# with {Nanoc3::DataSources::FilesystemUnified} and {Nanoc3::DataSources::FilesystemVerbose}.
|
11
|
+
#
|
12
|
+
# None of the public api methods are documented in this file. See
|
13
|
+
# {Nanoc3::DataSource} for documentation on the overridden methods instead.
|
14
|
+
#
|
15
|
+
# For more information about this data source specifications and configuration,
|
16
|
+
# please read the Readme file.
|
17
|
+
class FilesystemI18n < Nanoc3::DataSource
|
18
|
+
identifier :filesystem_i18n
|
19
|
+
|
20
|
+
# The VCS that will be called when adding, deleting and moving files. If
|
21
|
+
# no VCS has been set, or if the VCS has been set to `nil`, a dummy VCS
|
22
|
+
# will be returned.
|
23
|
+
#
|
24
|
+
# @return [Nanoc3::Extra::VCS, nil] The VCS that will be used.
|
25
|
+
def vcs
|
26
|
+
@vcs ||= Nanoc3::Extra::VCSes::Dummy.new
|
27
|
+
end
|
28
|
+
attr_writer :vcs
|
29
|
+
|
30
|
+
# See {Nanoc3::DataSource#up}.
|
31
|
+
def up
|
32
|
+
I18n.load_config(@config ? @config[:locale] : nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
# See {Nanoc3::DataSource#down}.
|
36
|
+
def down
|
37
|
+
end
|
38
|
+
|
39
|
+
# See {Nanoc3::DataSource#setup}.
|
40
|
+
def setup
|
41
|
+
# Create directories
|
42
|
+
%w( content layouts lib ).each do |dir|
|
43
|
+
FileUtils.mkdir_p(dir)
|
44
|
+
vcs.add(dir)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# See {Nanoc3::DataSource#items}.
|
49
|
+
def items
|
50
|
+
load_objects('content', 'item', Nanoc3::Item)
|
51
|
+
end
|
52
|
+
|
53
|
+
# See {Nanoc3::DataSource#layouts}.
|
54
|
+
def layouts
|
55
|
+
load_objects('layouts', 'layout', Nanoc3::Layout)
|
56
|
+
end
|
57
|
+
|
58
|
+
# See {Nanoc3::DataSource#create_item}.
|
59
|
+
def create_item(content, attributes, identifier, params={})
|
60
|
+
create_object('content', content, attributes, identifier, params)
|
61
|
+
end
|
62
|
+
|
63
|
+
# See {Nanoc3::DataSource#create_layout}.
|
64
|
+
def create_layout(content, attributes, identifier, params={})
|
65
|
+
create_object('layouts', content, attributes, identifier, params)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Creates a new object (item or layout) on disk in dir_name according to
|
71
|
+
# the given identifier. The file will have its attributes taken from the
|
72
|
+
# attributes hash argument and its content from the content argument.
|
73
|
+
def create_object(dir_name, content, attributes, identifier, params={})
|
74
|
+
# Check for periods
|
75
|
+
if (@config.nil? || !@config[:allow_periods_in_identifiers]) && identifier.include?('.')
|
76
|
+
raise RuntimeError,
|
77
|
+
"Attempted to create an object in #{dir_name} with identifier #{identifier} containing a period, but allow_periods_in_identifiers is not enabled in the site configuration. (Enabling allow_periods_in_identifiers may cause the site to break, though.)"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get filenames
|
81
|
+
ext = params[:extension] || '.html'
|
82
|
+
meta_filename = dir_name + (identifier == '/' ? '/index.yaml' : identifier[0..-2] + '.yaml')
|
83
|
+
content_filename = dir_name + (identifier == '/' ? '/index.html' : identifier[0..-2] + ext)
|
84
|
+
parent_path = File.dirname(meta_filename)
|
85
|
+
|
86
|
+
# Notify
|
87
|
+
Nanoc3::NotificationCenter.post(:file_created, meta_filename)
|
88
|
+
Nanoc3::NotificationCenter.post(:file_created, content_filename)
|
89
|
+
|
90
|
+
# Create files
|
91
|
+
FileUtils.mkdir_p(parent_path)
|
92
|
+
File.open(meta_filename, 'w') { |io| io.write(YAML.dump(attributes.stringify_keys)) }
|
93
|
+
File.open(content_filename, 'w') { |io| io.write(content) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates instances of klass corresponding to the files in dir_name. The
|
97
|
+
# kind attribute indicates the kind of object that is being loaded and is
|
98
|
+
# used solely for debugging purposes.
|
99
|
+
#
|
100
|
+
# This particular implementation loads objects from a filesystem-based
|
101
|
+
# data source where content and attributes can be spread over two separate
|
102
|
+
# files (one for metadata, with `yaml` extension, and one or more for content
|
103
|
+
# with same extension and locale code before extension, separated by dot. A
|
104
|
+
# content for default locale is used by default if file without locale code
|
105
|
+
# is not present.
|
106
|
+
#
|
107
|
+
# The contents and meta-file are optional (but at least one of them needs
|
108
|
+
# to be present, obviously. If a content file is present, file without locale
|
109
|
+
# code or with default locale code is needed) and the content file can start
|
110
|
+
# with a metadata section (if no metadata file is present).
|
111
|
+
def load_objects(dir_name, kind, klass)
|
112
|
+
all_split_files_in(dir_name).map do |base_filename, (meta_ext, content_ext, locales)|
|
113
|
+
I18n.locale = I18n.default_locale # Set current locale to default
|
114
|
+
|
115
|
+
# Get filenames
|
116
|
+
meta_filename = filename_for(base_filename, meta_ext)
|
117
|
+
content_filename = filename_for(base_filename, content_ext)
|
118
|
+
|
119
|
+
# is binary content?
|
120
|
+
is_binary = !!(content_filename && !@site.config[:text_extensions].include?(File.extname(content_filename)[1..-1]))
|
121
|
+
|
122
|
+
# Read content and metadata
|
123
|
+
meta, content_or_filename = parse(content_filename, meta_filename, kind, (is_binary && klass == Nanoc3::Item))
|
124
|
+
|
125
|
+
# Is locale content?
|
126
|
+
# - excluded content with locale meta IS a locale content
|
127
|
+
# - excluded content without locale meta IS NOT locale content
|
128
|
+
# - included content with or without locale meta IS locale content
|
129
|
+
# - included content with locale meta set to `false` IS NOT locale
|
130
|
+
# content
|
131
|
+
is_locale = !!(meta['locale'] || (meta['locale'] != false && locale_content?(content_filename || meta_filename, kind)))
|
132
|
+
|
133
|
+
# Create one item by locale, if content don't need a localized version,
|
134
|
+
# use default locale
|
135
|
+
(is_locale ? I18n.available_locales : [I18n.default_locale]).map do |locale|
|
136
|
+
I18n.locale = locale # Set current locale
|
137
|
+
|
138
|
+
# Read content and metadata (only if is localized, default is already
|
139
|
+
# loaded)
|
140
|
+
meta, content_or_filename = parse(content_filename, meta_filename, kind, (is_binary && klass == Nanoc3::Item)) if is_locale
|
141
|
+
|
142
|
+
# merge meta for current locale, default locale meta used by
|
143
|
+
# default is meta don't have key
|
144
|
+
if is_locale
|
145
|
+
meta_locale = meta.delete('locale') {|el| Hash.new }
|
146
|
+
meta = (meta_locale[I18n.default_locale] || Hash.new).merge(meta)
|
147
|
+
meta.merge!(meta_locale[locale.to_s] || Hash.new)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get attributes
|
151
|
+
attributes = {
|
152
|
+
:filename => content_filename,
|
153
|
+
:content_filename => content_filename,
|
154
|
+
:meta_filename => meta_filename,
|
155
|
+
:extension => content_filename ? ext_of(content_filename)[1..-1] : nil,
|
156
|
+
:locale => locale,
|
157
|
+
# WARNING :file is deprecated; please create a File object manually
|
158
|
+
# using the :content_filename or :meta_filename attributes.
|
159
|
+
# TODO [in nanoc 4.0] remove me
|
160
|
+
:file => content_filename ? Nanoc3::Extra::FileProxy.new(content_filename) : nil
|
161
|
+
}.merge(meta)
|
162
|
+
|
163
|
+
# Get identifier
|
164
|
+
if meta_filename
|
165
|
+
identifier = identifier_for_filename(meta_filename[(dir_name.length+1)..-1])
|
166
|
+
elsif content_filename
|
167
|
+
identifier = identifier_for_filename(content_filename[(dir_name.length+1)..-1])
|
168
|
+
else
|
169
|
+
raise RuntimeError, "meta_filename and content_filename are both nil"
|
170
|
+
end
|
171
|
+
# Prepend locale code to identifier if content is localized
|
172
|
+
identifier = "/#{locale}#{identifier}" if is_locale
|
173
|
+
|
174
|
+
# Get modification times
|
175
|
+
meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil
|
176
|
+
content_mtime = content_filename ? File.stat(content_filename).mtime : nil
|
177
|
+
if meta_mtime && content_mtime
|
178
|
+
mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
|
179
|
+
elsif meta_mtime
|
180
|
+
mtime = meta_mtime
|
181
|
+
elsif content_mtime
|
182
|
+
mtime = content_mtime
|
183
|
+
else
|
184
|
+
raise RuntimeError, "meta_mtime and content_mtime are both nil"
|
185
|
+
end
|
186
|
+
|
187
|
+
# Create layout object
|
188
|
+
klass.new(
|
189
|
+
content_or_filename, attributes, identifier,
|
190
|
+
:binary => is_binary, :mtime => mtime
|
191
|
+
)
|
192
|
+
end
|
193
|
+
end.flatten # elements is an array with all locale item, flatten in to one items list
|
194
|
+
end
|
195
|
+
|
196
|
+
# Finds all items/layouts/... in the given base directory. Returns a hash
|
197
|
+
# in which the keys are the file's dirname + basenames, and the values is
|
198
|
+
# an array with three elements: the metafile extension, the content file
|
199
|
+
# extension and an array with locales of content file. The meta file
|
200
|
+
# extension or the content file extension can be, but not both. Backup
|
201
|
+
# files are ignored. For example:
|
202
|
+
#
|
203
|
+
# {
|
204
|
+
# 'content/foo' => [ 'yaml', 'html', ['en', 'fr'] ],
|
205
|
+
# 'content/bar' => [ 'yaml', nil , [] ],
|
206
|
+
# 'content/qux' => [ nil, 'html', ['en'] ]
|
207
|
+
# }
|
208
|
+
def all_split_files_in(dir_name)
|
209
|
+
# Get all good file names
|
210
|
+
filenames = Dir[dir_name + '/**/*'].select { |i| File.file?(i) }
|
211
|
+
filenames.reject! { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }
|
212
|
+
|
213
|
+
# Group by identifier
|
214
|
+
grouped_filenames = filenames.group_by { |fn| basename_of(fn).gsub('/index', '') }
|
215
|
+
|
216
|
+
# Convert values into metafile/content file extension tuple
|
217
|
+
grouped_filenames.each_pair do |key, filenames|
|
218
|
+
# Divide
|
219
|
+
meta_filenames = filenames.select { |fn| ext_of(fn) == '.yaml' }
|
220
|
+
content_filenames = filenames.select { |fn| ext_of(fn) != '.yaml' }
|
221
|
+
|
222
|
+
# Check number of files per type
|
223
|
+
if ![ 0, 1 ].include?(meta_filenames.size)
|
224
|
+
raise RuntimeError, "Found #{meta_filenames.size} meta files for #{key}; expected 0 or 1"
|
225
|
+
end
|
226
|
+
if !( 0 .. (I18n.available_locales.empty? ? 1 : I18n.available_locales.size) ).include?(content_filenames.size)
|
227
|
+
raise RuntimeError, "Found #{content_filenames.size} content files for #{key}; expected 0 to #{I18n.available_locales.size}"
|
228
|
+
end
|
229
|
+
|
230
|
+
# Check content file extensions and default file
|
231
|
+
if fn = content_filenames.find {|fn| ext_of(fn) != ext_of(content_filenames[0]) }
|
232
|
+
raise RuntimeError, "Found multiple content extensions for `#{basename_of(fn)}.???`"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Reorder elements and convert to extnames
|
236
|
+
filenames[0] = meta_filenames[0] ? ext_of(meta_filenames[0])[1..-1] : nil
|
237
|
+
filenames[1] = content_filenames[0] ? ext_of(content_filenames[0])[1..-1] : nil
|
238
|
+
filenames[2] = []
|
239
|
+
content_filenames.each do |content_filename|
|
240
|
+
filenames[2] << locale_of(content_filename) if locale_of(content_filename)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Done
|
245
|
+
grouped_filenames
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns the filename for the given base filename, current locale (or
|
249
|
+
# default locale) and the extension.
|
250
|
+
#
|
251
|
+
# If the extension is nil, this function should return nil as well.
|
252
|
+
#
|
253
|
+
# This implementation is compatible with simple file item and directory
|
254
|
+
# item (with index or named file), find order is directory with named
|
255
|
+
# file, directory with index file and simple file. For locale, find
|
256
|
+
# order is current locale file, without locale file and default locale
|
257
|
+
# file.
|
258
|
+
#
|
259
|
+
# Item priority order:
|
260
|
+
# /foo/foo.html
|
261
|
+
# /foo/index.html
|
262
|
+
# /foo.html
|
263
|
+
#
|
264
|
+
# Locale priority order:
|
265
|
+
# /foo/foo.{current locale}.html
|
266
|
+
# /foo/foo.html
|
267
|
+
# /foo/foo.{default locale}.html
|
268
|
+
#
|
269
|
+
def filename_for(base_filename, ext)
|
270
|
+
last_part = base_filename.split('/')[-1]
|
271
|
+
lang_part = "{.#{I18n.locale},,.#{I18n.default_locale}}"
|
272
|
+
base_glob = base_filename.split('/')[0..-2].join('/') + "{/,}#{last_part}{/index,}#{lang_part}."
|
273
|
+
|
274
|
+
ext ? Dir[base_glob + ext][0] : nil
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns the identifier that corresponds with the given filename, which
|
278
|
+
# can be the content filename or the meta filename.
|
279
|
+
def identifier_for_filename(filename)
|
280
|
+
# Item is a directory with an index file
|
281
|
+
if filename =~ /index(\.[a-z]{2})?\.[^\/]+$/
|
282
|
+
regex = ((@config && @config[:allow_periods_in_identifiers]) ? /index(\.[a-z]{2})?(\.[^\/\.]+)$/ : /index(\.[a-z]{2})?(\.[^\/]+)$/)
|
283
|
+
# Item is a directory with a named file
|
284
|
+
elsif basename_of(filename).split(/\//)[-1] == basename_of(filename).split(/\//)[-2]
|
285
|
+
regex = ((@config && @config[:allow_periods_in_identifiers]) ? /(\/[^\/]+)?(\.[a-z]{2})?(\.[^\/\.]+)$/ : /(\/[^\/]+)?(\.[a-z]{2})?(\.[^\/]+)$/)
|
286
|
+
# Item is a simple file
|
287
|
+
else
|
288
|
+
regex = ((@config && @config[:allow_periods_in_identifiers]) ? /(\.[a-z]{2})?(\.[^\/\.]+)$/ : /(\.[a-z]{2})?(\.[^\/]+)$/)
|
289
|
+
end
|
290
|
+
|
291
|
+
filename.sub(regex, '').cleaned_identifier
|
292
|
+
end
|
293
|
+
|
294
|
+
# Returns the base name of filename, i.e. filename with the first or all
|
295
|
+
# extensions stripped off. By default, all extensions are stripped off,
|
296
|
+
# but when allow_periods_in_identifiers is set to true in the site
|
297
|
+
# configuration, only the last extension will be stripped .
|
298
|
+
def basename_of(filename)
|
299
|
+
filename.sub(extension_regex, '')
|
300
|
+
end
|
301
|
+
|
302
|
+
# Returns the extension(s) of filename. Supports multiple extensions.
|
303
|
+
# Includes the leading period. Return empty string if don't found
|
304
|
+
# extension.
|
305
|
+
def ext_of(filename)
|
306
|
+
filename =~ extension_regex ? $2 : ''
|
307
|
+
end
|
308
|
+
|
309
|
+
# Returns a regex that is used for determining the extension of a file
|
310
|
+
# name. The first match group will be the locale code (with leading
|
311
|
+
# period) if exist and the second match group is entire extension,
|
312
|
+
# including the leading period.
|
313
|
+
def extension_regex
|
314
|
+
if @config && @config[:allow_periods_in_identifiers]
|
315
|
+
/(\.[a-z]{2})?(\.[^\/\.]+$)/
|
316
|
+
else
|
317
|
+
/(\.[a-z]{2})?(\.[^\/]+$)/
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Returnes the locale code of filename or nil if filename is the default
|
322
|
+
# file (without locale code).
|
323
|
+
def locale_of(filename)
|
324
|
+
locale = (filename =~ extension_regex ? $1 : nil)
|
325
|
+
locale ? locale.gsub(/^\./, '').to_sym : nil
|
326
|
+
end
|
327
|
+
|
328
|
+
# Returnes true if this content is localized (based on data source config)
|
329
|
+
def locale_content?(base_filename_or_identifier, kind)
|
330
|
+
base_filename_or_identifier =~ locale_exclude_regex(kind) ? false : true
|
331
|
+
end
|
332
|
+
|
333
|
+
# Returnes regex that is used for determing if content is excluded not
|
334
|
+
# localized (layouts, css, js, ...).
|
335
|
+
#
|
336
|
+
# Add to locale config follwing keys (exemple):
|
337
|
+
# exclude:
|
338
|
+
# item: ['/css*', '/js*']
|
339
|
+
# layout: ['*']
|
340
|
+
#
|
341
|
+
def locale_exclude_regex(kind)
|
342
|
+
Regexp.union(I18n.exclude_list(kind.to_sym).map do |identifier|
|
343
|
+
if identifier.is_a? String
|
344
|
+
# Add leading/trailing slashes if necessary
|
345
|
+
new_identifier = identifier.dup
|
346
|
+
new_identifier[/^/] = '/' if identifier[0,1] != '/'
|
347
|
+
new_identifier[/$/] = '/' unless [ '*', '/' ].include?(identifier[-1,1])
|
348
|
+
|
349
|
+
/^[^\/]*#{new_identifier.gsub('*', '(.*?)').gsub('+', '(.+?)')}$/
|
350
|
+
else
|
351
|
+
identifier
|
352
|
+
end
|
353
|
+
end)
|
354
|
+
end
|
355
|
+
|
356
|
+
# Parses the file named `filename` and returns an array with its first
|
357
|
+
# element a hash with the file's metadata, and with its second element the
|
358
|
+
# file content itself.
|
359
|
+
def parse(content_filename, meta_filename, kind, is_binary)
|
360
|
+
# Read content and metadata from separate files
|
361
|
+
if meta_filename || is_binary
|
362
|
+
meta = (meta_filename && YAML.load_file(meta_filename)) || {}
|
363
|
+
|
364
|
+
if is_binary
|
365
|
+
content = content_filename
|
366
|
+
else
|
367
|
+
content = content_filename ? File.read(content_filename) : ''
|
368
|
+
end
|
369
|
+
|
370
|
+
return [ meta, content ]
|
371
|
+
end
|
372
|
+
|
373
|
+
# Read data
|
374
|
+
data = File.read(content_filename)
|
375
|
+
|
376
|
+
# Check presence of metadata section
|
377
|
+
if data !~ /^(-{5}|-{3})/
|
378
|
+
return [ {}, data ]
|
379
|
+
end
|
380
|
+
|
381
|
+
# Split data
|
382
|
+
pieces = data.split(/^(-{5}|-{3})/)
|
383
|
+
if pieces.size < 4
|
384
|
+
raise RuntimeError.new(
|
385
|
+
"The file '#{content_filename}' does not seem to be a nanoc #{kind}"
|
386
|
+
)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Parse
|
390
|
+
meta = YAML.load(pieces[2]) || {}
|
391
|
+
content = pieces[4..-1].join.strip
|
392
|
+
|
393
|
+
# Done
|
394
|
+
[ meta, content ]
|
395
|
+
end
|
396
|
+
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|