nesta 0.9.13 → 0.10.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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/{spec/spec.opts → .rspec} +0 -0
  3. data/.travis.yml +3 -2
  4. data/CHANGES +130 -1
  5. data/Gemfile +1 -8
  6. data/Gemfile.lock +68 -51
  7. data/LICENSE +1 -1
  8. data/README.md +38 -6
  9. data/Rakefile +2 -5
  10. data/bin/nesta +59 -3
  11. data/config.ru +3 -0
  12. data/lib/nesta.rb +1 -1
  13. data/lib/nesta/app.rb +20 -17
  14. data/lib/nesta/commands.rb +6 -3
  15. data/lib/nesta/config.rb +52 -15
  16. data/lib/nesta/helpers.rb +30 -3
  17. data/lib/nesta/models.rb +48 -30
  18. data/lib/nesta/navigation.rb +32 -12
  19. data/lib/nesta/overrides.rb +5 -5
  20. data/lib/nesta/version.rb +1 -1
  21. data/nesta.gemspec +9 -10
  22. data/smoke-test.sh +102 -0
  23. data/spec/atom_spec.rb +52 -49
  24. data/spec/commands_spec.rb +22 -16
  25. data/spec/config_spec.rb +66 -6
  26. data/spec/fixtures/nesta-plugin-test/lib/nesta-plugin-test/init.rb +1 -3
  27. data/spec/model_factory.rb +16 -16
  28. data/spec/models_spec.rb +197 -144
  29. data/spec/overrides_spec.rb +21 -21
  30. data/spec/page_spec.rb +182 -136
  31. data/spec/sitemap_spec.rb +48 -43
  32. data/spec/spec_helper.rb +32 -17
  33. data/templates/Gemfile +5 -2
  34. data/templates/config/config.yml +13 -13
  35. data/templates/config/deploy.rb +2 -2
  36. data/templates/index.haml +2 -0
  37. data/templates/themes/app.rb +1 -1
  38. data/templates/themes/views/layout.haml +7 -0
  39. data/templates/themes/views/master.sass +3 -0
  40. data/templates/themes/views/page.haml +1 -0
  41. data/views/atom.haml +3 -3
  42. data/views/comments.haml +1 -1
  43. data/views/error.haml +1 -1
  44. data/views/feed.haml +1 -1
  45. data/views/layout.haml +6 -3
  46. data/views/master.sass +143 -133
  47. data/views/mixins.sass +53 -28
  48. data/views/normalize.scss +396 -0
  49. data/views/page_meta.haml +2 -2
  50. data/views/sitemap.haml +2 -2
  51. data/views/summaries.haml +2 -2
  52. metadata +155 -202
  53. data/lib/nesta/cache.rb +0 -138
data/config.ru CHANGED
@@ -4,7 +4,10 @@ require 'bundler/setup'
4
4
  Bundler.require(:default)
5
5
 
6
6
  $LOAD_PATH.unshift(::File.expand_path('lib', ::File.dirname(__FILE__)))
7
+
7
8
  require 'nesta/env'
9
+ Nesta::Env.root = ::File.expand_path('.', ::File.dirname(__FILE__))
10
+
8
11
  require 'nesta/app'
9
12
 
10
13
  run Nesta::App
@@ -6,4 +6,4 @@ module Nesta
6
6
  end
7
7
  end
8
8
 
9
- require "nesta/plugin"
9
+ require 'nesta/plugin'
@@ -4,7 +4,6 @@ require 'sass'
4
4
 
5
5
  require File.expand_path('../nesta', File.dirname(__FILE__))
6
6
  require File.expand_path('env', File.dirname(__FILE__))
7
- require File.expand_path('cache', File.dirname(__FILE__))
8
7
  require File.expand_path('config', File.dirname(__FILE__))
9
8
  require File.expand_path('models', File.dirname(__FILE__))
10
9
  require File.expand_path('helpers', File.dirname(__FILE__))
@@ -16,17 +15,19 @@ Encoding.default_external = 'utf-8' if RUBY_VERSION =~ /^1.9/
16
15
 
17
16
  module Nesta
18
17
  class App < Sinatra::Base
19
- register Sinatra::Cache
20
-
21
18
  set :root, Nesta::Env.root
22
19
  set :views, File.expand_path('../../views', File.dirname(__FILE__))
23
- set :cache_enabled, Config.cache
24
- set :haml, { :format => :html5 }
20
+ set :haml, { format: :html5 }
25
21
 
26
22
  helpers Overrides::Renderers
27
23
  helpers Navigation::Renderers
28
24
  helpers View::Helpers
29
25
 
26
+ def cache(content)
27
+ Nesta.deprecated('cache', "it's no longer required - remove it from app.rb")
28
+ content
29
+ end
30
+
30
31
  before do
31
32
  if request.path_info =~ Regexp.new('./$')
32
33
  redirect to(request.path_info.sub(Regexp.new('/$'), ''))
@@ -47,7 +48,7 @@ module Nesta
47
48
  Overrides.load_theme_app
48
49
 
49
50
  get '/robots.txt' do
50
- content_type 'text/plain', :charset => 'utf-8'
51
+ content_type 'text/plain', charset: 'utf-8'
51
52
  <<-EOF
52
53
  # robots.txt
53
54
  # See http://en.wikipedia.org/wiki/Robots_exclusion_standard
@@ -55,33 +56,35 @@ module Nesta
55
56
  end
56
57
 
57
58
  get '/css/:sheet.css' do
58
- content_type 'text/css', :charset => 'utf-8'
59
- cache stylesheet(params[:sheet].to_sym)
59
+ content_type 'text/css', charset: 'utf-8'
60
+ stylesheet(params[:sheet].to_sym)
60
61
  end
61
62
 
62
- get %r{/attachments/([\w/.-]+)} do |file|
63
+ get %r{/attachments/([\w/.@-]+)} do |file|
63
64
  file = File.join(Nesta::Config.attachment_path, params[:captures].first)
64
65
  if file =~ /\.\.\//
65
66
  not_found
66
- else
67
- send_file(file, :disposition => nil)
67
+ else
68
+ send_file(file, disposition: nil)
68
69
  end
69
70
  end
70
71
 
71
72
  get '/articles.xml' do
72
- content_type :xml, :charset => 'utf-8'
73
+ content_type :xml, charset: 'utf-8'
73
74
  set_from_config(:title, :subtitle, :author)
74
75
  @articles = Page.find_articles.select { |a| a.date }[0..9]
75
- cache haml(:atom, :format => :xhtml, :layout => false)
76
+ haml(:atom, format: :xhtml, layout: false)
76
77
  end
77
78
 
78
79
  get '/sitemap.xml' do
79
- content_type :xml, :charset => 'utf-8'
80
- @pages = Page.find_all
80
+ content_type :xml, charset: 'utf-8'
81
+ @pages = Page.find_all.reject do |page|
82
+ page.draft? or page.flagged_as?('skip-sitemap')
83
+ end
81
84
  @last = @pages.map { |page| page.last_modified }.inject do |latest, page|
82
85
  (page > latest) ? page : latest
83
86
  end
84
- cache haml(:sitemap, :format => :xhtml, :layout => false)
87
+ haml(:sitemap, format: :xhtml, layout: false)
85
88
  end
86
89
 
87
90
  get '*' do
@@ -91,7 +94,7 @@ module Nesta
91
94
  raise Sinatra::NotFound if @page.nil?
92
95
  @title = @page.title
93
96
  set_from_page(:description, :keywords)
94
- cache haml(@page.template, :layout => @page.layout)
97
+ haml(@page.template, layout: @page.layout)
95
98
  end
96
99
  end
97
100
  end
@@ -204,8 +204,8 @@ end
204
204
  output = ''
205
205
  file.each_line do |line|
206
206
  if line =~ /^end/
207
- output << ' s.add_dependency("nesta", ">= 0.9.11")' + "\n"
208
- output << ' s.add_development_dependency("rake")' + "\n"
207
+ output << ' gem.add_dependency("nesta", ">= 0.9.11")' + "\n"
208
+ output << ' gem.add_development_dependency("rake")' + "\n"
209
209
  end
210
210
  output << line
211
211
  end
@@ -247,7 +247,10 @@ end
247
247
  make_directories
248
248
  copy_templates(
249
249
  'themes/README.md' => "#{@theme_path}/README.md",
250
- 'themes/app.rb' => "#{@theme_path}/app.rb"
250
+ 'themes/app.rb' => "#{@theme_path}/app.rb",
251
+ 'themes/views/layout.haml' => "#{@theme_path}/views/layout.haml",
252
+ 'themes/views/page.haml' => "#{@theme_path}/views/page.haml",
253
+ 'themes/views/master.sass' => "#{@theme_path}/views/master.sass"
251
254
  )
252
255
  end
253
256
  end
@@ -2,8 +2,17 @@ require 'yaml'
2
2
 
3
3
  module Nesta
4
4
  class Config
5
+ class NotDefined < KeyError; end
6
+
5
7
  @settings = %w[
6
- title subtitle theme disqus_short_name cache content google_analytics_code
8
+ cache
9
+ content
10
+ disqus_short_name
11
+ google_analytics_code
12
+ read_more
13
+ subtitle
14
+ theme
15
+ title
7
16
  ]
8
17
  @author_settings = %w[name uri email]
9
18
  @yaml = nil
@@ -11,11 +20,20 @@ module Nesta
11
20
  class << self
12
21
  attr_accessor :settings, :author_settings, :yaml_conf
13
22
  end
14
-
23
+
24
+ def self.fetch(key, *default)
25
+ from_environment(key.to_s)
26
+ rescue NotDefined
27
+ begin
28
+ from_yaml(key.to_s)
29
+ rescue NotDefined
30
+ default.empty? && raise || (return default.first)
31
+ end
32
+ end
33
+
15
34
  def self.method_missing(method, *args)
16
- setting = method.to_s
17
- if settings.include?(setting)
18
- from_environment(setting) || from_yaml(setting)
35
+ if settings.include?(method.to_s)
36
+ fetch(method, nil)
19
37
  else
20
38
  super
21
39
  end
@@ -27,7 +45,14 @@ module Nesta
27
45
  variable = "NESTA_AUTHOR__#{setting.upcase}"
28
46
  ENV[variable] && environment_config[setting] = ENV[variable]
29
47
  end
30
- environment_config.empty? ? from_yaml("author") : environment_config
48
+ environment_config.empty? ? from_yaml('author') : environment_config
49
+ rescue NotDefined
50
+ nil
51
+ end
52
+
53
+ def self.cache
54
+ Nesta.deprecated('Nesta::Config.cache',
55
+ 'see http://nestacms.com/docs/deployment/page-caching')
31
56
  end
32
57
 
33
58
  def self.content_path(basename = nil)
@@ -45,9 +70,16 @@ module Nesta
45
70
  def self.yaml_path
46
71
  File.expand_path('config/config.yml', Nesta::App.root)
47
72
  end
48
-
73
+
74
+ def self.read_more
75
+ fetch('read_more', 'Continue reading')
76
+ end
77
+
49
78
  def self.from_environment(setting)
50
- value = ENV["NESTA_#{setting.upcase}"]
79
+ value = ENV.fetch("NESTA_#{setting.upcase}")
80
+ rescue KeyError
81
+ raise NotDefined.new(setting)
82
+ else
51
83
  overrides = { "true" => true, "false" => false }
52
84
  overrides.has_key?(value) ? overrides[value] : value
53
85
  end
@@ -63,15 +95,20 @@ module Nesta
63
95
  end
64
96
  private_class_method :can_use_yaml?
65
97
 
98
+ def self.from_hash(hash, setting)
99
+ hash.fetch(setting) { raise NotDefined.new(setting) }
100
+ end
101
+ private_class_method :from_hash
102
+
66
103
  def self.from_yaml(setting)
67
- if can_use_yaml?
68
- self.yaml_conf ||= YAML::load(ERB.new(IO.read(yaml_path)).result)
69
- rack_env_conf = self.yaml_conf[Nesta::App.environment.to_s]
70
- (rack_env_conf && rack_env_conf[setting]) || self.yaml_conf[setting]
104
+ raise NotDefined.new(setting) unless can_use_yaml?
105
+ self.yaml_conf ||= YAML::load(ERB.new(IO.read(yaml_path)).result)
106
+ env_config = self.yaml_conf.fetch(Nesta::App.environment.to_s, {})
107
+ begin
108
+ from_hash(env_config, setting)
109
+ rescue NotDefined
110
+ from_hash(self.yaml_conf, setting)
71
111
  end
72
- rescue Errno::ENOENT # config file not found
73
- raise unless Nesta::App.environment == :test
74
- nil
75
112
  end
76
113
  private_class_method :from_yaml
77
114
 
@@ -25,7 +25,7 @@ module Nesta
25
25
  end
26
26
 
27
27
  def absolute_urls(text)
28
- text.gsub!(/(<a href=['"])\//, '\1' + url('/'))
28
+ text.gsub!(/(<a href=['"])\//, '\1' + path_to('/', uri: true))
29
29
  text
30
30
  end
31
31
 
@@ -54,7 +54,7 @@ module Nesta
54
54
  def local_stylesheet_link_tag(name)
55
55
  pattern = File.expand_path("views/#{name}.s{a,c}ss", Nesta::App.root)
56
56
  if Dir.glob(pattern).size > 0
57
- haml_tag :link, :href => url("/css/#{name}.css"), :rel => "stylesheet"
57
+ haml_tag :link, href: path_to("/css/#{name}.css"), rel: "stylesheet"
58
58
  end
59
59
  end
60
60
 
@@ -63,12 +63,39 @@ module Nesta
63
63
  end
64
64
 
65
65
  def article_summaries(articles)
66
- haml(:summaries, :layout => false, :locals => { :pages => articles })
66
+ haml(:summaries, layout: false, locals: { pages: articles })
67
67
  end
68
68
 
69
69
  def articles_heading
70
70
  @page.metadata('articles heading') || "Articles on #{@page.heading}"
71
71
  end
72
+
73
+ # Generates the full path to a given page, taking Rack routers and
74
+ # reverse proxies into account.
75
+ #
76
+ # Takes an options hash with a single option called `uri`. Set it
77
+ # to `true` if you'd like the publicly accessible URI for the
78
+ # path, rather than just the path relative to the site's root URI.
79
+ # The default is `false`.
80
+ #
81
+ # path_to(page.abspath, uri: true)
82
+ #
83
+ def path_to(page_path, options = {})
84
+ host = ''
85
+ if options[:uri]
86
+ host << "http#{'s' if request.ssl?}://"
87
+ if (request.env.include?("HTTP_X_FORWARDED_HOST") or
88
+ request.port != (request.ssl? ? 443 : 80))
89
+ host << request.host_with_port
90
+ else
91
+ host << request.host
92
+ end
93
+ end
94
+ uri_parts = [host]
95
+ uri_parts << request.script_name.to_s if request.script_name
96
+ uri_parts << page_path
97
+ File.join(uri_parts)
98
+ end
72
99
  end
73
100
  end
74
101
  end
@@ -7,11 +7,14 @@ Tilt.register Tilt::RDiscountTemplate, 'mdown'
7
7
  Tilt.register Tilt::RedcarpetTemplate, 'mdown'
8
8
 
9
9
  module Nesta
10
+ class HeadingNotSet < RuntimeError; end
11
+ class LinkTextNotSet < RuntimeError; end
10
12
  class MetadataParseError < RuntimeError; end
11
13
 
12
14
  class FileModel
13
15
  FORMATS = [:mdown, :haml, :textile]
14
- @@cache = {}
16
+ @@page_cache = {}
17
+ @@filename_cache = {}
15
18
 
16
19
  attr_reader :filename, :mtime
17
20
 
@@ -33,25 +36,35 @@ module Nesta
33
36
  end
34
37
  end
35
38
 
39
+ def self.find_file_for_path(path)
40
+ if ! @@filename_cache.has_key?(path)
41
+ FORMATS.each do |format|
42
+ [path, File.join(path, 'index')].each do |basename|
43
+ filename = model_path("#{basename}.#{format}")
44
+ if File.exist?(filename)
45
+ @@filename_cache[path] = filename
46
+ break
47
+ end
48
+ end
49
+ end
50
+ end
51
+ @@filename_cache[path]
52
+ end
53
+
36
54
  def self.needs_loading?(path, filename)
37
- @@cache[path].nil? || File.mtime(filename) > @@cache[path].mtime
55
+ @@page_cache[path].nil? || File.mtime(filename) > @@page_cache[path].mtime
38
56
  end
39
57
 
40
58
  def self.load(path)
41
- FORMATS.each do |format|
42
- [path, File.join(path, 'index')].each do |basename|
43
- filename = model_path("#{basename}.#{format}")
44
- if File.exist?(filename) && needs_loading?(path, filename)
45
- @@cache[path] = self.new(filename)
46
- break
47
- end
48
- end
59
+ if (filename = find_file_for_path(path)) && needs_loading?(path, filename)
60
+ @@page_cache[path] = self.new(filename)
49
61
  end
50
- @@cache[path]
62
+ @@page_cache[path]
51
63
  end
52
64
 
53
65
  def self.purge_cache
54
- @@cache = {}
66
+ @@page_cache = {}
67
+ @@filename_cache = {}
55
68
  end
56
69
 
57
70
  def self.menu_items
@@ -155,19 +168,19 @@ module Nesta
155
168
  end
156
169
  end
157
170
 
158
- def tag_lines_of_haml(text)
159
- tagged = (text =~ /^\s*%/)
160
- if tagged
171
+ def add_p_tags_to_haml(text)
172
+ contains_tags = (text =~ /^\s*%/)
173
+ if contains_tags
161
174
  text
162
175
  else
163
- text.split(/\r?\n/).inject("") do |accumulator, line|
176
+ text.split(/\r?\n/).inject('') do |accumulator, line|
164
177
  accumulator << "%p #{line}\n"
165
178
  end
166
179
  end
167
180
  end
168
181
 
169
182
  def convert_to_html(format, scope, text)
170
- text = tag_lines_of_haml(text) if @format == :haml
183
+ text = add_p_tags_to_haml(text) if @format == :haml
171
184
  template = Tilt[format].new { text }
172
185
  template.render(scope)
173
186
  end
@@ -215,19 +228,20 @@ module Nesta
215
228
  /^\s*h1\.\s+(.*)/
216
229
  end
217
230
  markup =~ regex
218
- Regexp.last_match(1)
231
+ Regexp.last_match(1) or raise HeadingNotSet, "#{abspath} needs a heading"
232
+ end
233
+
234
+ def link_text
235
+ metadata('link text') || heading
236
+ rescue HeadingNotSet
237
+ raise LinkTextNotSet, "Need to link to '#{abspath}' but can't get link text"
219
238
  end
220
239
 
221
240
  def title
222
- if metadata('title')
223
- metadata('title')
224
- elsif parent && (! parent.heading.nil?)
225
- "#{heading} - #{parent.heading}"
226
- elsif heading
227
- "#{heading} - #{Nesta::Config.title}"
228
- elsif abspath == '/'
229
- Nesta::Config.title
230
- end
241
+ metadata('title') || link_text
242
+ rescue LinkTextNotSet
243
+ return Nesta::Config.title if abspath == '/'
244
+ raise
231
245
  end
232
246
 
233
247
  def date(format = nil)
@@ -245,7 +259,7 @@ module Nesta
245
259
  end
246
260
 
247
261
  def read_more
248
- metadata('read more') || 'Continue reading'
262
+ metadata('read more') || Nesta::Config.read_more
249
263
  end
250
264
 
251
265
  def summary
@@ -274,7 +288,7 @@ module Nesta
274
288
  paths = category_strings.map { |specifier| specifier.sub(/:-?\d+$/, '') }
275
289
  pages = valid_paths(paths).map { |p| Page.find_by_path(p) }
276
290
  pages.sort do |x, y|
277
- x.heading.downcase <=> y.heading.downcase
291
+ x.link_text.downcase <=> y.link_text.downcase
278
292
  end
279
293
  end
280
294
 
@@ -306,7 +320,7 @@ module Nesta
306
320
  in_category.sort do |x, y|
307
321
  by_priority = y.priority(path) <=> x.priority(path)
308
322
  if by_priority == 0
309
- x.heading.downcase <=> y.heading.downcase
323
+ x.link_text.downcase <=> y.link_text.downcase
310
324
  else
311
325
  by_priority
312
326
  end
@@ -317,6 +331,10 @@ module Nesta
317
331
  Page.find_articles.select { |article| article.categories.include?(self) }
318
332
  end
319
333
 
334
+ def receives_comments?
335
+ ! date.nil?
336
+ end
337
+
320
338
  private
321
339
  def category_strings
322
340
  strings = metadata('categories')