nanoc-filesystem-i18n 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|