nesta 0.13.0 → 0.15.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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +1 -1
- data/.gitignore +1 -0
- data/{CHANGES → CHANGELOG.md} +117 -21
- data/Gemfile.lock +28 -26
- data/LICENSE +1 -1
- data/README.md +45 -31
- data/RELEASING.md +1 -1
- data/bin/nesta +4 -1
- data/lib/nesta/app.rb +1 -2
- data/lib/nesta/commands/build.rb +38 -0
- data/lib/nesta/commands/new.rb +2 -1
- data/lib/nesta/commands.rb +1 -0
- data/lib/nesta/config.rb +49 -75
- data/lib/nesta/config_file.rb +5 -1
- data/lib/nesta/helpers.rb +0 -5
- data/lib/nesta/models/file_model.rb +191 -0
- data/lib/nesta/models/menu.rb +67 -0
- data/lib/nesta/models/page.rb +167 -0
- data/lib/nesta/models.rb +3 -422
- data/lib/nesta/navigation.rb +0 -5
- data/lib/nesta/overrides.rb +32 -43
- data/lib/nesta/plugin.rb +0 -16
- data/lib/nesta/static/assets.rb +50 -0
- data/lib/nesta/static/html_file.rb +26 -0
- data/lib/nesta/static/site.rb +104 -0
- data/lib/nesta/version.rb +1 -1
- data/lib/nesta.rb +1 -3
- data/nesta.gemspec +5 -5
- data/templates/config/config.yml +28 -2
- data/templates/themes/README.md +1 -1
- data/templates/themes/views/master.sass +1 -1
- data/test/integration/atom_feed_test.rb +1 -1
- data/test/integration/commands/build_test.rb +53 -0
- data/test/integration/overrides_test.rb +1 -1
- data/test/integration/sitemap_test.rb +1 -1
- data/test/support/temporary_files.rb +1 -1
- data/test/support/test_configuration.rb +2 -4
- data/test/unit/config_test.rb +25 -94
- data/test/unit/static/assets_test.rb +56 -0
- data/test/unit/static/html_file_test.rb +41 -0
- data/test/unit/static/site_test.rb +104 -0
- data/views/atom.haml +2 -2
- data/views/comments.haml +2 -2
- data/views/footer.haml +1 -1
- data/views/header.haml +2 -3
- data/views/layout.haml +2 -2
- data/views/master.sass +1 -1
- data/views/mixins.sass +2 -2
- data/views/normalize.scss +0 -1
- data/views/sitemap.haml +1 -1
- metadata +42 -31
- /data/test/unit/{file_model_test.rb → models/file_model_test.rb} +0 -0
- /data/test/unit/{menu_test.rb → models/menu_test.rb} +0 -0
- /data/test/unit/{page_test.rb → models/page_test.rb} +0 -0
data/lib/nesta/config.rb
CHANGED
@@ -1,115 +1,89 @@
|
|
1
|
+
require 'singleton'
|
1
2
|
require 'yaml'
|
2
3
|
|
4
|
+
require_relative './config_file'
|
5
|
+
|
3
6
|
module Nesta
|
4
7
|
class Config
|
8
|
+
include Singleton
|
9
|
+
|
5
10
|
class NotDefined < KeyError; end
|
6
11
|
|
7
|
-
|
8
|
-
|
12
|
+
SETTINGS = %w[
|
13
|
+
author
|
14
|
+
build
|
9
15
|
content
|
10
16
|
disqus_short_name
|
17
|
+
domain
|
11
18
|
google_analytics_code
|
12
19
|
read_more
|
13
20
|
subtitle
|
14
21
|
theme
|
15
22
|
title
|
16
23
|
]
|
17
|
-
|
18
|
-
@yaml = nil
|
19
|
-
|
24
|
+
|
20
25
|
class << self
|
21
|
-
|
26
|
+
extend Forwardable
|
27
|
+
def_delegators *[:instance, :fetch].concat(SETTINGS.map(&:to_sym))
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
attr_accessor :config
|
31
|
+
|
32
|
+
def fetch(setting, *default)
|
33
|
+
setting = setting.to_s
|
34
|
+
self.config ||= read_config_file(setting)
|
35
|
+
env_config = config.fetch(Nesta::App.environment.to_s, {})
|
36
|
+
env_config.fetch(
|
37
|
+
setting,
|
38
|
+
config.fetch(setting) { raise NotDefined.new(setting) }
|
39
|
+
)
|
26
40
|
rescue NotDefined
|
27
|
-
|
28
|
-
from_yaml(key.to_s)
|
29
|
-
rescue NotDefined
|
30
|
-
default.empty? && raise || (return default.first)
|
31
|
-
end
|
41
|
+
default.empty? && raise || (return default.first)
|
32
42
|
end
|
33
43
|
|
34
|
-
def
|
35
|
-
if
|
36
|
-
fetch(method, nil)
|
44
|
+
def method_missing(method, *args)
|
45
|
+
if SETTINGS.include?(method.to_s)
|
46
|
+
fetch(method.to_s, nil)
|
37
47
|
else
|
38
48
|
super
|
39
49
|
end
|
40
50
|
end
|
41
|
-
|
42
|
-
def self.author
|
43
|
-
environment_config = {}
|
44
|
-
%w[name uri email].each do |setting|
|
45
|
-
variable = "NESTA_AUTHOR__#{setting.upcase}"
|
46
|
-
ENV[variable] && environment_config[setting] = ENV[variable]
|
47
|
-
end
|
48
|
-
environment_config.empty? ? from_yaml('author') : environment_config
|
49
|
-
rescue NotDefined
|
50
|
-
nil
|
51
|
-
end
|
52
51
|
|
53
|
-
def
|
54
|
-
|
55
|
-
'see http://nestacms.com/docs/deployment/page-caching')
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.content_path(basename = nil)
|
59
|
-
get_path(content, basename)
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.page_path(basename = nil)
|
63
|
-
get_path(File.join(content_path, "pages"), basename)
|
52
|
+
def respond_to_missing?(method, include_private = false)
|
53
|
+
SETTINGS.include?(method.to_s) || super
|
64
54
|
end
|
65
|
-
|
66
|
-
def
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.yaml_path
|
71
|
-
File.expand_path('config/config.yml', Nesta::App.root)
|
55
|
+
|
56
|
+
def build
|
57
|
+
fetch('build', {})
|
72
58
|
end
|
73
59
|
|
74
|
-
def
|
60
|
+
def read_more
|
75
61
|
fetch('read_more', 'Continue reading')
|
76
62
|
end
|
77
63
|
|
78
|
-
|
79
|
-
|
80
|
-
|
64
|
+
private
|
65
|
+
|
66
|
+
def read_config_file(setting)
|
67
|
+
YAML::load(ERB.new(IO.read(Nesta::ConfigFile.path)).result)
|
68
|
+
rescue Errno::ENOENT
|
81
69
|
raise NotDefined.new(setting)
|
82
|
-
else
|
83
|
-
overrides = { "true" => true, "false" => false }
|
84
|
-
overrides.has_key?(value) ? overrides[value] : value
|
85
|
-
end
|
86
|
-
private_class_method :from_environment
|
87
|
-
|
88
|
-
def self.yaml_exists?
|
89
|
-
File.exist?(yaml_path)
|
90
70
|
end
|
91
|
-
private_class_method :yaml_exists?
|
92
71
|
|
93
|
-
def self.from_hash(hash, setting)
|
94
|
-
hash.fetch(setting) { raise NotDefined.new(setting) }
|
95
|
-
end
|
96
|
-
private_class_method :from_hash
|
97
|
-
|
98
|
-
def self.from_yaml(setting)
|
99
|
-
raise NotDefined.new(setting) unless yaml_exists?
|
100
|
-
self.yaml_conf ||= YAML::load(ERB.new(IO.read(yaml_path)).result)
|
101
|
-
env_config = self.yaml_conf.fetch(Nesta::App.environment.to_s, {})
|
102
|
-
begin
|
103
|
-
from_hash(env_config, setting)
|
104
|
-
rescue NotDefined
|
105
|
-
from_hash(self.yaml_conf, setting)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
private_class_method :from_yaml
|
109
|
-
|
110
72
|
def self.get_path(dirname, basename)
|
111
73
|
basename.nil? ? dirname : File.join(dirname, basename)
|
112
74
|
end
|
113
75
|
private_class_method :get_path
|
76
|
+
|
77
|
+
def self.content_path(basename = nil)
|
78
|
+
get_path(content, basename)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.page_path(basename = nil)
|
82
|
+
get_path(File.join(content_path, "pages"), basename)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.attachment_path(basename = nil)
|
86
|
+
get_path(File.join(content_path, "attachments"), basename)
|
87
|
+
end
|
114
88
|
end
|
115
89
|
end
|
data/lib/nesta/config_file.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
module Nesta
|
2
2
|
class ConfigFile
|
3
|
+
def self.path
|
4
|
+
File.expand_path('config/config.yml', Nesta::App.root)
|
5
|
+
end
|
6
|
+
|
3
7
|
def set_value(key, value)
|
4
8
|
pattern = /^\s*#?\s*#{key}:.*/
|
5
9
|
replacement = "#{key}: #{value}"
|
6
10
|
|
7
11
|
configured = false
|
8
|
-
File.open(
|
12
|
+
File.open(self.class.path, 'r+') do |file|
|
9
13
|
output = ''
|
10
14
|
file.each_line do |line|
|
11
15
|
if configured
|
data/lib/nesta/helpers.rb
CHANGED
@@ -60,11 +60,6 @@ module Nesta
|
|
60
60
|
date.strftime("%d %B %Y")
|
61
61
|
end
|
62
62
|
|
63
|
-
def local_stylesheet?
|
64
|
-
Nesta.deprecated('local_stylesheet?', 'use local_stylesheet_link_tag')
|
65
|
-
File.exist?(File.expand_path('views/local.sass', Nesta::App.root))
|
66
|
-
end
|
67
|
-
|
68
63
|
def local_stylesheet_link_tag(name)
|
69
64
|
pattern = File.expand_path("views/#{name}.s{a,c}ss", Nesta::App.root)
|
70
65
|
if Dir.glob(pattern).size > 0
|
@@ -0,0 +1,191 @@
|
|
1
|
+
def register_template_handler(class_name, *extensions)
|
2
|
+
Tilt.register Tilt.const_get(class_name), *extensions
|
3
|
+
rescue LoadError
|
4
|
+
# Only one of the Markdown processors needs to be available, so we can
|
5
|
+
# safely ignore these load errors.
|
6
|
+
end
|
7
|
+
|
8
|
+
register_template_handler :MarukuTemplate, 'mdown', 'md'
|
9
|
+
register_template_handler :KramdownTemplate, 'mdown', 'md'
|
10
|
+
register_template_handler :RDiscountTemplate, 'mdown', 'md'
|
11
|
+
register_template_handler :RedcarpetTemplate, 'mdown', 'md'
|
12
|
+
|
13
|
+
|
14
|
+
module Nesta
|
15
|
+
class MetadataParseError < RuntimeError; end
|
16
|
+
|
17
|
+
class FileModel
|
18
|
+
FORMATS = [:mdown, :md, :haml, :textile]
|
19
|
+
@@model_cache = {}
|
20
|
+
@@filename_cache = {}
|
21
|
+
|
22
|
+
attr_reader :filename, :mtime
|
23
|
+
|
24
|
+
class CaseInsensitiveHash < Hash
|
25
|
+
def [](key)
|
26
|
+
super(key.to_s.downcase)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.model_path(basename = nil)
|
31
|
+
Nesta::Config.content_path(basename)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find_all
|
35
|
+
file_pattern = File.join(model_path, "**", "*.{#{FORMATS.join(',')}}")
|
36
|
+
Dir.glob(file_pattern).map do |path|
|
37
|
+
relative = path.sub("#{model_path}/", "")
|
38
|
+
load(relative.sub(/\.(#{FORMATS.join('|')})/, ""))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.find_file_for_path(path)
|
43
|
+
if ! @@filename_cache.has_key?(path)
|
44
|
+
FORMATS.each do |format|
|
45
|
+
[path, File.join(path, 'index')].each do |basename|
|
46
|
+
filename = model_path("#{basename}.#{format}")
|
47
|
+
if File.exist?(filename)
|
48
|
+
@@filename_cache[path] = filename
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@@filename_cache[path]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.needs_loading?(path, filename)
|
58
|
+
@@model_cache[path].nil? || File.mtime(filename) > @@model_cache[path].mtime
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.load(path)
|
62
|
+
if (filename = find_file_for_path(path)) && needs_loading?(path, filename)
|
63
|
+
@@model_cache[path] = self.new(filename)
|
64
|
+
end
|
65
|
+
@@model_cache[path]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.purge_cache
|
69
|
+
@@model_cache = {}
|
70
|
+
@@filename_cache = {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(filename)
|
74
|
+
@filename = filename
|
75
|
+
@format = filename.split('.').last.to_sym
|
76
|
+
if File.zero?(filename)
|
77
|
+
@metadata = {}
|
78
|
+
@markup = ''
|
79
|
+
else
|
80
|
+
@metadata, @markup = parse_file
|
81
|
+
end
|
82
|
+
@mtime = File.mtime(filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
def ==(other)
|
86
|
+
other.respond_to?(:path) && (self.path == other.path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def index_page?
|
90
|
+
@filename =~ /\/?index\.\w+$/
|
91
|
+
end
|
92
|
+
|
93
|
+
def abspath
|
94
|
+
file_path = @filename.sub(self.class.model_path, '')
|
95
|
+
if index_page?
|
96
|
+
File.dirname(file_path)
|
97
|
+
else
|
98
|
+
File.join(File.dirname(file_path), File.basename(file_path, '.*'))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def path
|
103
|
+
abspath.sub(/^\//, '')
|
104
|
+
end
|
105
|
+
|
106
|
+
def permalink
|
107
|
+
File.basename(path)
|
108
|
+
end
|
109
|
+
|
110
|
+
def layout
|
111
|
+
(metadata('layout') || 'layout').to_sym
|
112
|
+
end
|
113
|
+
|
114
|
+
def template
|
115
|
+
(metadata('template') || 'page').to_sym
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_html(scope = Object.new)
|
119
|
+
convert_to_html(@format, scope, markup)
|
120
|
+
end
|
121
|
+
|
122
|
+
def last_modified
|
123
|
+
@last_modified ||= File.stat(@filename).mtime
|
124
|
+
end
|
125
|
+
|
126
|
+
def description
|
127
|
+
metadata('description')
|
128
|
+
end
|
129
|
+
|
130
|
+
def keywords
|
131
|
+
metadata('keywords')
|
132
|
+
end
|
133
|
+
|
134
|
+
def metadata(key)
|
135
|
+
@metadata[key]
|
136
|
+
end
|
137
|
+
|
138
|
+
def flagged_as?(flag)
|
139
|
+
flags = metadata('flags')
|
140
|
+
flags && flags.split(',').map { |name| name.strip }.include?(flag)
|
141
|
+
end
|
142
|
+
|
143
|
+
def parse_metadata(first_paragraph)
|
144
|
+
is_metadata = first_paragraph.split("\n").first =~ /^[\w ]+:/
|
145
|
+
raise MetadataParseError unless is_metadata
|
146
|
+
metadata = CaseInsensitiveHash.new
|
147
|
+
first_paragraph.split("\n").each do |line|
|
148
|
+
key, value = line.split(/\s*:\s*/, 2)
|
149
|
+
next if value.nil?
|
150
|
+
metadata[key.downcase] = value.chomp
|
151
|
+
end
|
152
|
+
metadata
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def markup
|
158
|
+
@markup
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse_file
|
162
|
+
contents = File.open(@filename).read
|
163
|
+
rescue Errno::ENOENT
|
164
|
+
raise Sinatra::NotFound
|
165
|
+
else
|
166
|
+
first_paragraph, remaining = contents.split(/\r?\n\r?\n/, 2)
|
167
|
+
begin
|
168
|
+
return parse_metadata(first_paragraph), remaining
|
169
|
+
rescue MetadataParseError
|
170
|
+
return {}, contents
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_p_tags_to_haml(text)
|
175
|
+
contains_tags = (text =~ /^\s*%/)
|
176
|
+
if contains_tags
|
177
|
+
text
|
178
|
+
else
|
179
|
+
text.split(/\r?\n/).inject('') do |accumulator, line|
|
180
|
+
accumulator << "%p #{line}\n"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def convert_to_html(format, scope, text)
|
186
|
+
text = add_p_tags_to_haml(text) if @format == :haml
|
187
|
+
template = Tilt[format].new { text }
|
188
|
+
template.render(scope)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Nesta
|
2
|
+
class Menu
|
3
|
+
INDENT = " " * 2
|
4
|
+
|
5
|
+
def self.full_menu
|
6
|
+
menu = []
|
7
|
+
menu_file = Nesta::Config.content_path('menu.txt')
|
8
|
+
if File.exist?(menu_file)
|
9
|
+
File.open(menu_file) { |file| append_menu_item(menu, file, 0) }
|
10
|
+
end
|
11
|
+
menu
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.top_level
|
15
|
+
full_menu.reject { |item| item.is_a?(Array) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.for_path(path)
|
19
|
+
path.sub!(Regexp.new('^/'), '')
|
20
|
+
if path.empty?
|
21
|
+
full_menu
|
22
|
+
else
|
23
|
+
find_menu_item_by_path(full_menu, path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private_class_method def self.append_menu_item(menu, file, depth)
|
28
|
+
path = file.readline
|
29
|
+
rescue EOFError
|
30
|
+
else
|
31
|
+
page = Page.load(path.strip)
|
32
|
+
current_depth = path.scan(INDENT).size
|
33
|
+
if page
|
34
|
+
if current_depth > depth
|
35
|
+
sub_menu_for_depth(menu, depth) << [page]
|
36
|
+
else
|
37
|
+
sub_menu_for_depth(menu, current_depth) << page
|
38
|
+
end
|
39
|
+
end
|
40
|
+
append_menu_item(menu, file, current_depth)
|
41
|
+
end
|
42
|
+
|
43
|
+
private_class_method def self.sub_menu_for_depth(menu, depth)
|
44
|
+
sub_menu = menu
|
45
|
+
depth.times { sub_menu = sub_menu[-1] }
|
46
|
+
sub_menu
|
47
|
+
end
|
48
|
+
|
49
|
+
private_class_method def self.find_menu_item_by_path(menu, path)
|
50
|
+
item = menu.detect do |item|
|
51
|
+
item.respond_to?(:path) && (item.path == path)
|
52
|
+
end
|
53
|
+
if item
|
54
|
+
subsequent = menu[menu.index(item) + 1]
|
55
|
+
item = [item]
|
56
|
+
item << subsequent if subsequent.respond_to?(:each)
|
57
|
+
else
|
58
|
+
sub_menus = menu.select { |menu_item| menu_item.respond_to?(:each) }
|
59
|
+
sub_menus.each do |sub_menu|
|
60
|
+
item = find_menu_item_by_path(sub_menu, path)
|
61
|
+
break if item
|
62
|
+
end
|
63
|
+
end
|
64
|
+
item
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Nesta
|
2
|
+
class HeadingNotSet < RuntimeError; end
|
3
|
+
class LinkTextNotSet < RuntimeError; end
|
4
|
+
|
5
|
+
class Page < FileModel
|
6
|
+
def self.model_path(basename = nil)
|
7
|
+
Nesta::Config.page_path(basename)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find_by_path(path)
|
11
|
+
page = load(path)
|
12
|
+
page && page.hidden? ? nil : page
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find_all
|
16
|
+
super.select { |p| ! p.hidden? }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find_articles
|
20
|
+
find_all.select do |page|
|
21
|
+
page.date && page.date < DateTime.now
|
22
|
+
end.sort { |x, y| y.date <=> x.date }
|
23
|
+
end
|
24
|
+
|
25
|
+
def draft?
|
26
|
+
flagged_as?('draft')
|
27
|
+
end
|
28
|
+
|
29
|
+
def hidden?
|
30
|
+
draft? && Nesta::App.production?
|
31
|
+
end
|
32
|
+
|
33
|
+
def heading
|
34
|
+
regex = case @format
|
35
|
+
when :mdown, :md
|
36
|
+
/^#\s*(.*?)(\s*#+|$)/
|
37
|
+
when :haml
|
38
|
+
/^\s*%h1\s+(.*)/
|
39
|
+
when :textile
|
40
|
+
/^\s*h1\.\s+(.*)/
|
41
|
+
end
|
42
|
+
markup =~ regex
|
43
|
+
Regexp.last_match(1) or raise HeadingNotSet, "#{abspath} needs a heading"
|
44
|
+
end
|
45
|
+
|
46
|
+
def link_text
|
47
|
+
metadata('link text') || heading
|
48
|
+
rescue HeadingNotSet
|
49
|
+
raise LinkTextNotSet, "Need to link to '#{abspath}' but can't get link text"
|
50
|
+
end
|
51
|
+
|
52
|
+
def title
|
53
|
+
metadata('title') || link_text
|
54
|
+
rescue LinkTextNotSet
|
55
|
+
return Nesta::Config.title if abspath == '/'
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
|
59
|
+
def date(format = nil)
|
60
|
+
@date ||= if metadata("date")
|
61
|
+
if format == :xmlschema
|
62
|
+
Time.parse(metadata("date")).xmlschema
|
63
|
+
else
|
64
|
+
DateTime.parse(metadata("date"))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def atom_id
|
70
|
+
metadata('atom id')
|
71
|
+
end
|
72
|
+
|
73
|
+
def read_more
|
74
|
+
metadata('read more') || Nesta::Config.read_more
|
75
|
+
end
|
76
|
+
|
77
|
+
def summary
|
78
|
+
if summary_text = metadata("summary")
|
79
|
+
summary_text.gsub!('\n', "\n")
|
80
|
+
convert_to_html(@format, Object.new, summary_text)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def body_markup
|
85
|
+
case @format
|
86
|
+
when :mdown, :md
|
87
|
+
markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, '')
|
88
|
+
when :haml
|
89
|
+
markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, '')
|
90
|
+
when :textile
|
91
|
+
markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, '')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def body(scope = Object.new)
|
96
|
+
convert_to_html(@format, scope, body_markup)
|
97
|
+
end
|
98
|
+
|
99
|
+
def categories
|
100
|
+
paths = category_strings.map { |specifier| specifier.sub(/:-?\d+$/, '') }
|
101
|
+
valid_paths(paths).map { |p| Page.find_by_path(p) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def priority(category)
|
105
|
+
category_string = category_strings.detect do |string|
|
106
|
+
string =~ /^#{category}([,:\s]|$)/
|
107
|
+
end
|
108
|
+
category_string && category_string.split(':', 2)[-1].to_i
|
109
|
+
end
|
110
|
+
|
111
|
+
def parent
|
112
|
+
if abspath == '/'
|
113
|
+
nil
|
114
|
+
else
|
115
|
+
parent_path = File.dirname(path)
|
116
|
+
while parent_path != '.' do
|
117
|
+
parent = Page.load(parent_path)
|
118
|
+
return parent unless parent.nil?
|
119
|
+
parent_path = File.dirname(parent_path)
|
120
|
+
end
|
121
|
+
return categories.first unless categories.empty?
|
122
|
+
Page.load('index')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def pages
|
127
|
+
in_category = Page.find_all.select do |page|
|
128
|
+
page.date.nil? && page.categories.include?(self)
|
129
|
+
end
|
130
|
+
in_category.sort do |x, y|
|
131
|
+
by_priority = y.priority(path) <=> x.priority(path)
|
132
|
+
if by_priority == 0
|
133
|
+
x.link_text.downcase <=> y.link_text.downcase
|
134
|
+
else
|
135
|
+
by_priority
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def articles
|
141
|
+
Page.find_articles.select { |article| article.categories.include?(self) }
|
142
|
+
end
|
143
|
+
|
144
|
+
def receives_comments?
|
145
|
+
! date.nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def category_strings
|
151
|
+
strings = metadata('categories')
|
152
|
+
strings.nil? ? [] : strings.split(',').map { |string| string.strip }
|
153
|
+
end
|
154
|
+
|
155
|
+
def valid_paths(paths)
|
156
|
+
page_dir = Nesta::Config.page_path
|
157
|
+
paths.select do |path|
|
158
|
+
FORMATS.detect do |format|
|
159
|
+
[path, File.join(path, 'index')].detect do |candidate|
|
160
|
+
File.exist?(File.join(page_dir, "#{candidate}.#{format}"))
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|