nesta 0.9.11 → 0.9.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -50,10 +50,28 @@ module Nesta
50
50
  end
51
51
  end
52
52
 
53
+ class Edit
54
+ include Command
55
+
56
+ def initialize(*args)
57
+ @filename = Nesta::Config.page_path(args.shift)
58
+ end
59
+
60
+ def execute
61
+ editor = ENV.fetch('EDITOR')
62
+ rescue IndexError
63
+ $stderr.puts "No editor: set EDITOR environment variable"
64
+ else
65
+ system(editor, @filename)
66
+ end
67
+ end
68
+
53
69
  class New
54
70
  include Command
55
71
 
56
- def initialize(path, options = {})
72
+ def initialize(*args)
73
+ path = args.shift
74
+ options = args.shift || {}
57
75
  path.nil? && (raise UsageError.new('path not specified'))
58
76
  if File.exist?(path)
59
77
  raise RuntimeError.new("#{path} already exists")
@@ -104,7 +122,7 @@ module Nesta
104
122
  class Content
105
123
  include Command
106
124
 
107
- def initialize
125
+ def initialize(*args)
108
126
  @dir = 'content-demo'
109
127
  end
110
128
 
@@ -135,7 +153,8 @@ module Nesta
135
153
 
136
154
  module Plugin
137
155
  class Create
138
- def initialize(name)
156
+ def initialize(*args)
157
+ name = args.shift
139
158
  name.nil? && (raise UsageError.new('name not specified'))
140
159
  @name = name
141
160
  @gem_name = "nesta-plugin-#{name}"
@@ -210,7 +229,9 @@ end
210
229
  class Create
211
230
  include Command
212
231
 
213
- def initialize(name, options = {})
232
+ def initialize(*args)
233
+ name = args.shift
234
+ options = args.shift || {}
214
235
  name.nil? && (raise UsageError.new('name not specified'))
215
236
  @name = name
216
237
  @theme_path = Nesta::Path.themes(@name)
@@ -234,7 +255,9 @@ end
234
255
  class Install
235
256
  include Command
236
257
 
237
- def initialize(url, options = {})
258
+ def initialize(*args)
259
+ url = args.shift
260
+ options = args.shift || {}
238
261
  url.nil? && (raise UsageError.new('URL not specified'))
239
262
  @url = url
240
263
  @name = File.basename(url, '.git').sub(/nesta-theme-/, '')
@@ -243,14 +266,20 @@ end
243
266
  def execute
244
267
  system('git', 'clone', @url, "themes/#{@name}")
245
268
  FileUtils.rm_r(File.join("themes/#{@name}", '.git'))
246
- enable(@name)
269
+ enable
270
+ end
271
+
272
+ def enable
273
+ Enable.new(@name).execute
247
274
  end
248
275
  end
249
276
 
250
277
  class Enable
251
278
  include Command
252
279
 
253
- def initialize(name, options = {})
280
+ def initialize(*args)
281
+ name = args.shift
282
+ options = args.shift || {}
254
283
  name.nil? && (raise UsageError.new('name not specified'))
255
284
  @name = name
256
285
  end
@@ -1,7 +1,4 @@
1
- require "yaml"
2
-
3
- require "rubygems"
4
- require "sinatra"
1
+ require 'yaml'
5
2
 
6
3
  module Nesta
7
4
  class Config
@@ -68,7 +65,7 @@ module Nesta
68
65
 
69
66
  def self.from_yaml(setting)
70
67
  if can_use_yaml?
71
- self.yaml_conf ||= YAML::load(IO.read(yaml_path))
68
+ self.yaml_conf ||= YAML::load(ERB.new(IO.read(yaml_path)).result)
72
69
  rack_env_conf = self.yaml_conf[Nesta::App.environment.to_s]
73
70
  (rack_env_conf && rack_env_conf[setting]) || self.yaml_conf[setting]
74
71
  end
@@ -0,0 +1,74 @@
1
+ module Nesta
2
+ module View
3
+ module Helpers
4
+ def set_from_config(*variables)
5
+ variables.each do |var|
6
+ instance_variable_set("@#{var}", Nesta::Config.send(var))
7
+ end
8
+ end
9
+
10
+ def set_from_page(*variables)
11
+ variables.each do |var|
12
+ instance_variable_set("@#{var}", @page.send(var))
13
+ end
14
+ end
15
+
16
+ def no_widow(text)
17
+ text.split[0...-1].join(" ") + " #{text.split[-1]}"
18
+ end
19
+
20
+ def set_common_variables
21
+ @menu_items = Nesta::Menu.for_path('/')
22
+ @site_title = Nesta::Config.title
23
+ set_from_config(:title, :subtitle, :google_analytics_code)
24
+ @heading = @title
25
+ end
26
+
27
+ def absolute_urls(text)
28
+ text.gsub!(/(<a href=['"])\//, '\1' + url('/'))
29
+ text
30
+ end
31
+
32
+ def nesta_atom_id_for_page(page)
33
+ published = page.date.strftime('%Y-%m-%d')
34
+ "tag:#{request.host},#{published}:#{page.abspath}"
35
+ end
36
+
37
+ def atom_id(page = nil)
38
+ if page
39
+ page.atom_id || nesta_atom_id_for_page(page)
40
+ else
41
+ "tag:#{request.host},2009:/"
42
+ end
43
+ end
44
+
45
+ def format_date(date)
46
+ date.strftime("%d %B %Y")
47
+ end
48
+
49
+ def local_stylesheet?
50
+ Nesta.deprecated('local_stylesheet?', 'use local_stylesheet_link_tag')
51
+ File.exist?(File.expand_path('views/local.sass', Nesta::App.root))
52
+ end
53
+
54
+ def local_stylesheet_link_tag(name)
55
+ pattern = File.expand_path("views/#{name}.s{a,c}ss", Nesta::App.root)
56
+ if Dir.glob(pattern).size > 0
57
+ haml_tag :link, :href => url("/css/#{name}.css"), :rel => "stylesheet"
58
+ end
59
+ end
60
+
61
+ def latest_articles(count = 8)
62
+ Nesta::Page.find_articles[0..count - 1]
63
+ end
64
+
65
+ def article_summaries(articles)
66
+ haml(:summaries, :layout => false, :locals => { :pages => articles })
67
+ end
68
+
69
+ def articles_heading
70
+ @page.metadata('articles heading') || "Articles on #{@page.heading}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -7,6 +7,8 @@ Tilt.register Tilt::RDiscountTemplate, 'mdown'
7
7
  Tilt.register Tilt::RedcarpetTemplate, 'mdown'
8
8
 
9
9
  module Nesta
10
+ class MetadataParseError < RuntimeError; end
11
+
10
12
  class FileModel
11
13
  FORMATS = [:mdown, :haml, :textile]
12
14
  @@cache = {}
@@ -123,33 +125,49 @@ module Nesta
123
125
  flags && flags.split(',').map { |name| name.strip }.include?(flag)
124
126
  end
125
127
 
128
+ def parse_metadata(first_paragraph)
129
+ is_metadata = first_paragraph.split("\n").first =~ /^[\w ]+:/
130
+ raise MetadataParseError unless is_metadata
131
+ metadata = CaseInsensitiveHash.new
132
+ first_paragraph.split("\n").each do |line|
133
+ key, value = line.split(/\s*:\s*/, 2)
134
+ next if value.nil?
135
+ metadata[key.downcase] = value.chomp
136
+ end
137
+ metadata
138
+ end
139
+
126
140
  private
127
141
  def markup
128
142
  @markup
129
143
  end
130
144
 
131
- def metadata?(text)
132
- text.split("\n").first =~ /^[\w ]+:/
133
- end
134
-
135
145
  def parse_file
136
146
  contents = File.open(@filename).read
137
147
  rescue Errno::ENOENT
138
148
  raise Sinatra::NotFound
139
149
  else
140
150
  first_paragraph, remaining = contents.split(/\r?\n\r?\n/, 2)
141
- metadata = CaseInsensitiveHash.new
142
- if metadata?(first_paragraph)
143
- first_paragraph.split("\n").each do |line|
144
- key, value = line.split(/\s*:\s*/, 2)
145
- metadata[key.downcase] = value.chomp
151
+ begin
152
+ return parse_metadata(first_paragraph), remaining
153
+ rescue MetadataParseError
154
+ return {}, contents
155
+ end
156
+ end
157
+
158
+ def tag_lines_of_haml(text)
159
+ tagged = (text =~ /^\s*%/)
160
+ if tagged
161
+ text
162
+ else
163
+ text.split(/\r?\n/).inject("") do |accumulator, line|
164
+ accumulator << "%p #{line}\n"
146
165
  end
147
166
  end
148
- markup = metadata?(first_paragraph) ? remaining : contents
149
- return metadata, markup
150
167
  end
151
168
 
152
169
  def convert_to_html(format, scope, text)
170
+ text = tag_lines_of_haml(text) if @format == :haml
153
171
  template = Tilt[format].new { text }
154
172
  template.render(scope)
155
173
  end
@@ -237,16 +255,19 @@ module Nesta
237
255
  end
238
256
  end
239
257
 
240
- def body(scope = nil)
241
- body_text = case @format
258
+ def body_markup
259
+ case @format
242
260
  when :mdown
243
261
  markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, '')
244
262
  when :haml
245
263
  markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, '')
246
264
  when :textile
247
265
  markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, '')
248
- end
249
- convert_to_html(@format, scope, body_text)
266
+ end
267
+ end
268
+
269
+ def body(scope = nil)
270
+ convert_to_html(@format, scope, body_markup)
250
271
  end
251
272
 
252
273
  def categories
@@ -21,9 +21,9 @@ module Nesta
21
21
  end
22
22
  end
23
23
  else
24
- html_class = (request.path == item.abspath) ? "current" : nil
24
+ html_class = current_item?(item) ? "current" : nil
25
25
  haml_tag :li, :class => html_class do
26
- haml_tag :a, :<, :href => item.abspath do
26
+ haml_tag :a, :<, :href => url(item.abspath) do
27
27
  haml_concat item.heading
28
28
  end
29
29
  end
@@ -44,7 +44,7 @@ module Nesta
44
44
  haml_tag :ul, :class => options[:class] do
45
45
  breadcrumb_ancestors[0...-1].each do |page|
46
46
  haml_tag :li do
47
- haml_tag :a, :<, :href => page.abspath do
47
+ haml_tag :a, :<, :href => url(page.abspath) do
48
48
  haml_concat breadcrumb_label(page)
49
49
  end
50
50
  end
@@ -56,6 +56,10 @@ module Nesta
56
56
  def breadcrumb_label(page)
57
57
  (page.abspath == '/') ? 'Home' : page.heading
58
58
  end
59
+
60
+ def current_item?(item)
61
+ request.path == item.abspath
62
+ end
59
63
  end
60
64
  end
61
65
  end
@@ -6,6 +6,11 @@ module Nesta
6
6
  super(template, defaults.merge(options), locals)
7
7
  end
8
8
 
9
+ def erb(template, options = {}, locals = {})
10
+ defaults, engine = Overrides.render_options(template, :erb)
11
+ super(template, defaults.merge(options), locals)
12
+ end
13
+
9
14
  def scss(template, options = {}, locals = {})
10
15
  defaults, engine = Overrides.render_options(template, :scss)
11
16
  super(template, defaults.merge(options), locals)
@@ -17,17 +17,19 @@ module Nesta
17
17
  end
18
18
 
19
19
  def self.load_local_plugins
20
+ # This approach is deprecated; plugins should now be distributed
21
+ # as gems. See http://nestacms.com/docs/plugins/writing-plugins
20
22
  plugins = Dir.glob(File.expand_path('../plugins/*', File.dirname(__FILE__)))
21
23
  plugins.each { |path| require_local_plugin(path) }
22
24
  end
23
25
 
24
- private
25
- def self.require_local_plugin(path)
26
- Nesta.deprecated(
27
- 'loading plugins from ./plugins', "convert #{path} to a gem")
28
- require File.join(path, 'lib', File.basename(path))
29
- rescue LoadError => e
30
- $stderr.write("Couldn't load plugins/#{File.basename(path)}: #{e}\n")
31
- end
26
+ def self.require_local_plugin(path)
27
+ Nesta.deprecated(
28
+ 'loading plugins from ./plugins', "convert #{path} to a gem")
29
+ require File.join(path, 'lib', File.basename(path))
30
+ rescue LoadError => e
31
+ $stderr.write("Couldn't load plugins/#{File.basename(path)}: #{e}\n")
32
+ end
33
+ private_class_method :require_local_plugin
32
34
  end
33
35
  end
@@ -1,3 +1,3 @@
1
1
  module Nesta
2
- VERSION = '0.9.11'
2
+ VERSION = '0.9.13'
3
3
  end
@@ -35,7 +35,8 @@ EOF
35
35
  s.add_dependency('sass', '~> 3.1')
36
36
  s.add_dependency('rdiscount', '~> 1.6')
37
37
  s.add_dependency('RedCloth', '~> 4.2')
38
- s.add_dependency('sinatra', '1.2.6')
38
+ s.add_dependency('sinatra', '~> 1.3')
39
+ s.add_dependency('rack', '~> 1.1')
39
40
 
40
41
  # Useful in development
41
42
  s.add_dependency('shotgun', '>= 0.8')
@@ -32,7 +32,7 @@ describe "atom feed" do
32
32
  end
33
33
 
34
34
  it "should have an alternate link element" do
35
- body.should have_tag("/feed/link[@rel=alternate][@href='http://example.org']")
35
+ body.should have_tag("/feed/link[@rel=alternate][@href='http://example.org/']")
36
36
  end
37
37
 
38
38
  it "should have a self link element" do
@@ -167,6 +167,29 @@ describe "nesta" do
167
167
  end
168
168
  end
169
169
 
170
+ describe "edit" do
171
+ before(:each) do
172
+ Nesta::Config.stub!(:content_path).and_return('content')
173
+ @page_path = 'path/to/page.mdown'
174
+ @command = Nesta::Commands::Edit.new(@page_path)
175
+ @command.stub!(:system)
176
+ end
177
+
178
+ it "should launch the editor" do
179
+ ENV['EDITOR'] = 'vi'
180
+ full_path = File.join('content/pages', @page_path)
181
+ @command.should_receive(:system).with(ENV['EDITOR'], full_path)
182
+ @command.execute
183
+ end
184
+
185
+ it "should not try and launch an editor if environment not setup" do
186
+ ENV.delete('EDITOR')
187
+ @command.should_not_receive(:system)
188
+ $stderr.stub!(:puts)
189
+ @command.execute
190
+ end
191
+ end
192
+
170
193
  describe "plugin:create" do
171
194
  before(:each) do
172
195
  @name = 'my-feature'
@@ -247,6 +270,7 @@ describe "nesta" do
247
270
  @theme_dir = 'themes/mine'
248
271
  FileUtils.mkdir_p(File.join(@theme_dir, '.git'))
249
272
  @command = Nesta::Commands::Theme::Install.new(@repo_url)
273
+ @command.stub!(:enable)
250
274
  @command.stub!(:system)
251
275
  end
252
276
 
@@ -267,16 +291,17 @@ describe "nesta" do
267
291
  end
268
292
 
269
293
  it "should enable the freshly installed theme" do
270
- @command.should_receive(:enable).with('mine')
294
+ @command.should_receive(:enable)
271
295
  @command.execute
272
296
  end
273
297
 
274
- describe "when theme URL doesn't match recommendation" do
298
+ describe "when theme URL doesn't match recommended pattern" do
275
299
  before(:each) do
276
300
  @repo_url = 'git://foobar.com/path/to/mytheme.git'
277
301
  @other_theme_dir = 'themes/mytheme'
278
302
  FileUtils.mkdir_p(File.join(@other_theme_dir, '.git'))
279
303
  @command = Nesta::Commands::Theme::Install.new(@repo_url)
304
+ @command.stub!(:enable)
280
305
  end
281
306
 
282
307
  after(:each) do
@@ -58,6 +58,24 @@ describe "Page", :shared => true do
58
58
  Nesta::Page.find_by_path('banana').heading.should == 'Banana'
59
59
  end
60
60
 
61
+ it "should respond to #parse_metadata, returning hash of key/value" do
62
+ page = create_page(:heading => 'Banana', :path => 'banana')
63
+ metadata = page.parse_metadata('My key: some value')
64
+ metadata['my key'].should == 'some value'
65
+ end
66
+
67
+ it "should be parseable if metadata is invalid" do
68
+ dodgy_metadata = "Key: value\nKey without value\nAnother key: value"
69
+ create_page(:heading => 'Banana', :path => 'banana') do |path|
70
+ text = File.read(path)
71
+ File.open(path, 'w') do |file|
72
+ file.puts(dodgy_metadata)
73
+ file.write(text)
74
+ end
75
+ end
76
+ Nesta::Page.find_by_path('banana')
77
+ end
78
+
61
79
  describe "for home page" do
62
80
  it "should set title to heading and site title" do
63
81
  create_page(:heading => 'Home', :path => 'index')
@@ -396,6 +414,10 @@ describe "Page", :shared => true do
396
414
  it "should not include metadata in the HTML" do
397
415
  @article.to_html.should_not have_tag("p", /^Date/)
398
416
  end
417
+
418
+ it "should not include heading in body markup" do
419
+ @article.body_markup.should_not include("My article")
420
+ end
399
421
 
400
422
  it "should not include heading in body" do
401
423
  @article.body.should_not have_tag("h1", "My article")
@@ -501,12 +523,31 @@ describe "Haml page" do
501
523
  it_should_behave_like "Page"
502
524
 
503
525
  it "should set heading from first h1 tag" do
504
- create_page(
526
+ page = create_page(
505
527
  :path => "a-page",
506
528
  :heading => "First heading",
507
529
  :content => "%h1 Second heading"
508
530
  )
509
- Nesta::Page.find_by_path("a-page").heading.should == "First heading"
531
+ page.heading.should == "First heading"
532
+ end
533
+
534
+ it "should wrap <p> tags around one line summary text" do
535
+ page = create_page(
536
+ :path => "a-page",
537
+ :heading => "First para",
538
+ :metadata => { "Summary" => "Wrap me" }
539
+ )
540
+ page.summary.should include("<p>Wrap me</p>")
541
+ end
542
+
543
+ it "should wrap <p> tags around multiple lines of summary text" do
544
+ page = create_page(
545
+ :path => "a-page",
546
+ :heading => "First para",
547
+ :metadata => { "Summary" => 'Wrap me\nIn paragraph tags' }
548
+ )
549
+ page.summary.should include("<p>Wrap me</p>")
550
+ page.summary.should include("<p>In paragraph tags</p>")
510
551
  end
511
552
  end
512
553
 
@@ -518,12 +559,12 @@ describe "Textile page" do
518
559
  it_should_behave_like "Page"
519
560
 
520
561
  it "should set heading from first h1 tag" do
521
- create_page(
562
+ page = create_page(
522
563
  :path => "a-page",
523
564
  :heading => "First heading",
524
565
  :content => "h1. Second heading"
525
566
  )
526
- Nesta::Page.find_by_path("a-page").heading.should == "First heading"
567
+ page.heading.should == "First heading"
527
568
  end
528
569
  end
529
570