ruhoh 2.1 → 2.2

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 (47) hide show
  1. data/Gemfile +7 -2
  2. data/README.md +50 -7
  3. data/Rakefile +1 -9
  4. data/cucumber.yml +1 -0
  5. data/features/categories.feature +38 -0
  6. data/features/conversion.feature +35 -0
  7. data/features/data.feature +32 -0
  8. data/features/drafts.feature +21 -0
  9. data/features/javascripts.feature +42 -0
  10. data/features/layouts.feature +41 -0
  11. data/features/pagination.feature +55 -0
  12. data/features/partials.feature +13 -0
  13. data/features/permalinks.feature +118 -0
  14. data/features/step_defs.rb +70 -0
  15. data/features/stylesheets.feature +42 -0
  16. data/features/summary.feature +119 -0
  17. data/features/support/env.rb +8 -0
  18. data/features/support/helpers.rb +74 -0
  19. data/features/tags.feature +39 -0
  20. data/features/themes.feature +0 -0
  21. data/features/widgets/google_prettify.feature +12 -0
  22. data/features/widgets/syntax.feature +35 -0
  23. data/features/widgets/widgets.feature +83 -0
  24. data/history.json +23 -0
  25. data/lib/ruhoh/base/model.rb +29 -9
  26. data/lib/ruhoh/base/model_view.rb +64 -32
  27. data/lib/ruhoh/programs/watch.rb +1 -0
  28. data/lib/ruhoh/resources/pages/collection.rb +2 -1
  29. data/lib/ruhoh/resources/pages/collection_view.rb +9 -7
  30. data/lib/ruhoh/resources/pages/previewer.rb +11 -10
  31. data/lib/ruhoh/resources/widgets/collection_view.rb +2 -4
  32. data/lib/ruhoh/resources/widgets/compiler.rb +19 -3
  33. data/lib/ruhoh/version.rb +1 -1
  34. data/ruhoh.gemspec +4 -0
  35. data/system/plugins/sprockets/compiler.rb +38 -0
  36. data/system/plugins/sprockets/javascripts/compiler.rb +3 -24
  37. data/system/plugins/sprockets/javascripts/previewer.rb +4 -12
  38. data/system/plugins/sprockets/previewer.rb +17 -0
  39. data/system/plugins/sprockets/stylesheets/compiler.rb +2 -25
  40. data/system/plugins/sprockets/stylesheets/previewer.rb +3 -12
  41. data/system/widgets/analytics/google.html +14 -6
  42. data/system/widgets/google_prettify/default.html +1 -1
  43. data/system/widgets/syntax/javascripts/prettify.js +30 -0
  44. data/system/widgets/syntax/prettify.html +18 -0
  45. metadata +80 -5
  46. data/spec/spec_helper.rb +0 -29
  47. data/spec/support/shared_contexts.rb +0 -25
data/history.json CHANGED
@@ -1,4 +1,27 @@
1
1
  [
2
+ {
3
+ "version" : "2.2",
4
+ "date" : "09.06.2013",
5
+ "changes" : [
6
+ "@binaryphile updates google analytics tracking script",
7
+ "@stebalien overhauls page.summary internal implementation",
8
+ ],
9
+ "features" : [
10
+ "Cache the collection#all call to improve performance.",
11
+ "@stebalien introduces more powerful controls over page.summary",
12
+ "@lynnfaraday adds page.url to the previewer page data.",
13
+ "Add cucumber integration tests",
14
+ "@caspervonb adds system level widgets.syntax to act as unified syntax highlighting widget"
15
+ ],
16
+ "bugs" : [
17
+ "@tlupfer fixes date metadata not being substituted in permalinks",
18
+ "@stebalien fixes sprockets plugin",
19
+ "@caspervonb makes widgets compiler respect model excludes",
20
+ "Avoid data parse errors by always converting to string",
21
+ "Improve error messages for a few parse error cases",
22
+ "Fix widget config not deep merging with default widget config",
23
+ ]
24
+ },
2
25
  {
3
26
  "version" : "2.1",
4
27
  "date" : "19.05.2013",
@@ -99,7 +99,13 @@ module Ruhoh::Base
99
99
 
100
100
  page = File.open(@pointer['realpath'], 'r:UTF-8') {|f| f.read }
101
101
 
102
- front_matter = page.match(FMregex)
102
+ begin
103
+ front_matter = page.match(FMregex)
104
+ rescue => e
105
+ raise "Error trying to read meta-data from #{@pointer['realpath']}." +
106
+ " Check your folder configuration. Error details: #{e}"
107
+ end
108
+
103
109
  data = front_matter ?
104
110
  (YAML.load(front_matter[0].gsub(/---\n/, "")) || {}) :
105
111
  {}
@@ -122,10 +128,6 @@ module Ruhoh::Base
122
128
  nil
123
129
  end
124
130
 
125
- def formatted_date(date)
126
- Time.parse(date.to_s).strftime('%Y-%m-%d') rescue false
127
- end
128
-
129
131
  def parse_page_filename(filename)
130
132
  data = *filename.match(DateMatcher)
131
133
  data = *filename.match(Matcher) if data.empty?
@@ -179,9 +181,27 @@ module Ruhoh::Base
179
181
  "categories" => category || '',
180
182
  }
181
183
 
182
- date = Date.parse(page_data['date']) rescue nil
183
- if date
184
- data.merge({
184
+ uses_date = false
185
+ %w{ :year :month :day :i_day :i_month }.each do |token|
186
+ if format.include?(token)
187
+ uses_date = true
188
+ break
189
+ end
190
+ end
191
+
192
+ if uses_date
193
+ begin
194
+ date = Time.parse(page_data['date'].to_s)
195
+ rescue ArgumentError, TypeError
196
+ Ruhoh.log.error(
197
+ "ArgumentError:" +
198
+ " The file '#{ @pointer["realpath"] }' has a permalink '#{ format }'" +
199
+ " which is date dependant but the date '#{page_data['date']}' could not be parsed." +
200
+ " Ensure the date's format is: 'YYYY-MM-DD'"
201
+ )
202
+ end
203
+
204
+ data.merge!({
185
205
  "year" => date.strftime("%Y"),
186
206
  "month" => date.strftime("%m"),
187
207
  "day" => date.strftime("%d"),
@@ -217,4 +237,4 @@ module Ruhoh::Base
217
237
  class Model
218
238
  include Modelable
219
239
  end
220
- end
240
+ end
@@ -1,3 +1,6 @@
1
+ require 'nokogiri'
2
+ require 'set'
3
+
1
4
  module Ruhoh::Base
2
5
  module ModelViewable
3
6
  def initialize(model)
@@ -5,7 +8,6 @@ module Ruhoh::Base
5
8
  @model = model
6
9
  @ruhoh = model.ruhoh
7
10
 
8
- # TODO: THIS AUTOMATICALLY CALLS PROCESS ON THE MODEL =XXXX
9
11
  # Define direct access to the data Hash object
10
12
  # but don't overwrite methods if already defined.
11
13
  data.keys.each do |method|
@@ -44,9 +46,9 @@ module Ruhoh::Base
44
46
  other_data = other.__send__(attribute)
45
47
  if attribute == "date"
46
48
  begin
47
- this_data = Date.parse(this_data)
48
- other_data = Date.parse(other_data)
49
- rescue ArgumentError
49
+ this_data = Time.parse(this_data.to_s)
50
+ other_data = Time.parse(other_data.to_s)
51
+ rescue ArgumentError, TypeError
50
52
  Ruhoh.log.error(
51
53
  "ArgumentError:" +
52
54
  " The '#{ @model.collection.resource_name }' collection is configured to sort based on 'date'" +
@@ -88,41 +90,71 @@ module Ruhoh::Base
88
90
  id == @model.collection.master.page_data['id']
89
91
  end
90
92
 
91
- # Truncate the page content relative to a line_count limit.
92
- # This is optimized for markdown files in which content is largely
93
- # blocked into chunks and separating by blank lines.
94
- # The line_limit truncates content based on # of content-based lines,
95
- # so blank lines don't count toward the limit.
96
- # Always break the content on a blank line only so result stays formatted nicely.
93
+ # Generate a truncated summary.
94
+ # - If a summary element (`<tag class="summary">...</tag>`) is specified
95
+ # in the content, return it.
96
+ # - If summary_lines > 0, truncate after the first complete element where
97
+ # the number of summary lines is greater than summary_lines.
98
+ # - If summary_stop_at_header is a number n, stop before the nth header.
99
+ # - If summary_stop_at_header is true, stop before the first header after
100
+ # content has been included. In other words, don't count headers at the
101
+ # top of the page.
97
102
  def summary
98
- line_limit = @model.collection.config['summary_lines']
99
- line_count = 0
100
- line_breakpoint = @model.content.lines.count
101
-
102
- content.lines.each_with_index do |line, i|
103
- if line =~ /^\s*$/ # line with only whitespace
104
- if line_count >= line_limit
105
- line_breakpoint = i
103
+ # Parse the document
104
+ full_content = @ruhoh.master_view(@model.pointer).render_content
105
+ content_doc = Nokogiri::HTML.fragment(full_content)
106
+
107
+ # Return a summary element if specified
108
+ summary_el = content_doc.at_css('.summary')
109
+ return summary_el.to_html unless summary_el.nil?
110
+
111
+ # Get the configuration parameters
112
+ # Default to the parameters provided in the page itself
113
+ model_data = @model.data
114
+ collection_config = @model.collection.config
115
+ line_limit = model_data['summary_lines'] || collection_config['summary_lines']
116
+ stop_at_header = model_data['summary_stop_at_header']
117
+ stop_at_header = collection_config['summary_stop_at_header'] if stop_at_header.nil?
118
+
119
+ # Create the summary element.
120
+ summary_doc = Nokogiri::XML::Node.new("div", Nokogiri::HTML::Document.new)
121
+ summary_doc["class"] = "summary"
122
+
123
+ # All "heading" elements.
124
+ headings = Nokogiri::HTML::ElementDescription::HEADING + ["header", "hgroup"]
125
+
126
+
127
+ content_doc.children.each do |node|
128
+
129
+ if stop_at_header == true
130
+ # Detect first header after content
131
+ if not (headings.include?(node.name) && node.content.empty?)
132
+ stop_at_header = 1
133
+ end
134
+ elsif stop_at_header.is_a?(Integer) && headings.include?(node.name)
135
+ if stop_at_header > 1
136
+ stop_at_header -= 1;
137
+ else
138
+ summary_doc["class"] += " ellipsis"
106
139
  break
107
140
  end
108
- else
109
- line_count += 1
110
141
  end
111
- end
112
-
113
- summary = content.lines.to_a[0, line_breakpoint].join
114
142
 
115
- # The summary may be missing some key items needed to render properly.
116
- # So search the rest of the content and add it to the summary.
117
- content.lines.with_index(line_breakpoint) do |line, i|
118
- # Add lines containing destination urls.
119
- if line =~ /^\[[^\]]+\]:/
120
- summary << "\n#{line}"
143
+ if line_limit > 0 && summary_doc.content.lines.to_a.length > line_limit
144
+ # Skip through leftover whitespace. Without this check, the summary
145
+ # can be marked as ellipsis even if it isn't.
146
+ unless node.text? && node.text.strip.empty?
147
+ summary_doc["class"] += " ellipsis"
148
+ break
149
+ else
150
+ next
151
+ end
121
152
  end
153
+
154
+ summary_doc << node
122
155
  end
123
156
 
124
- summary = @model.collection.master.render(summary)
125
- Ruhoh::Converter.convert(summary, id)
157
+ summary_doc.to_html
126
158
  end
127
159
 
128
160
  def next
@@ -149,4 +181,4 @@ module Ruhoh::Base
149
181
  class ModelView < SimpleDelegator
150
182
  include ModelViewable
151
183
  end
152
- end
184
+ end
@@ -33,6 +33,7 @@ class Ruhoh
33
33
  resource = path.split(separator)[0]
34
34
 
35
35
  ruhoh.cache.delete(ruhoh.collection(resource).files_cache_key)
36
+ ruhoh.cache.delete("#{ resource }-all")
36
37
 
37
38
  ruhoh.collection(resource).load_watcher.update(path)
38
39
  end
@@ -42,6 +42,7 @@ module Ruhoh::Resources::Pages
42
42
  hash['permalink'] ||= "/:path/:filename"
43
43
  hash['summary_lines'] ||= 20
44
44
  hash['summary_lines'] = hash['summary_lines'].to_i
45
+ hash['summary_stop_at_header'] ||= false
45
46
  hash['latest'] ||= 2
46
47
  hash['latest'] = hash['latest'].to_i
47
48
  hash['ext'] ||= ".md"
@@ -83,4 +84,4 @@ module Ruhoh::Resources::Pages
83
84
  hash
84
85
  end
85
86
  end
86
- end
87
+ end
@@ -9,9 +9,11 @@ module Ruhoh::Resources::Pages
9
9
  include Ruhoh::Views::Helpers::Paginator
10
10
 
11
11
  def all
12
- dictionary.each_value.find_all { |model|
13
- File.basename(File.dirname(model.id)) != "drafts"
14
- }.sort
12
+ ruhoh.cache.get("#{ resource_name }-all") ||
13
+ ruhoh.cache.set("#{ resource_name }-all", dictionary.each_value.find_all { |model|
14
+ File.basename(File.dirname(model.id)) != "drafts"
15
+ }.sort
16
+ )
15
17
  end
16
18
 
17
19
  def drafts
@@ -39,11 +41,11 @@ module Ruhoh::Resources::Pages
39
41
  collated = []
40
42
  pages = all
41
43
  pages.each_with_index do |page, i|
42
- thisYear = Time.parse(page['date']).strftime('%Y')
43
- thisMonth = Time.parse(page['date']).strftime('%B')
44
+ thisYear = Time.parse(page['date'].to_s).strftime('%Y')
45
+ thisMonth = Time.parse(page['date'].to_s).strftime('%B')
44
46
  if (i-1 >= 0)
45
- prevYear = Time.parse(pages[i-1]['date']).strftime('%Y')
46
- prevMonth = Time.parse(pages[i-1]['date']).strftime('%B')
47
+ prevYear = Time.parse(pages[i-1]['date'].to_s).strftime('%Y')
48
+ prevMonth = Time.parse(pages[i-1]['date'].to_s).strftime('%B')
47
49
  end
48
50
 
49
51
  if(prevYear == thisYear)
@@ -13,17 +13,15 @@ module Ruhoh::Resources::Pages
13
13
  env['PATH_INFO'].chomp!("/") unless env['PATH_INFO'] == "/"
14
14
 
15
15
  pointer = @ruhoh.routes.find(env['PATH_INFO'])
16
+ Ruhoh::Friend.say {
17
+ plain "- previewing page:"
18
+ plain " #{pointer.inspect}"
19
+ }
20
+
16
21
  view = pointer ? @ruhoh.master_view(pointer) : paginator_view(env)
22
+
17
23
  if view
18
- content = view.render_full
19
- Ruhoh::Friend.say {
20
- cyan "-> previewing page:"
21
- plain " - #{pointer.inspect}"
22
- plain " - sub-layout: #{ view.sub_layout }"
23
- plain " - master-layout: #{ view.master_layout }"
24
- plain " - meta-data: #{ view.page_data }"
25
- }
26
- [200, {'Content-Type' => 'text/html'}, [content]]
24
+ [200, {'Content-Type' => 'text/html'}, [view.render_full]]
27
25
  else
28
26
  message = "No generated page URL matches '#{ env['PATH_INFO'] }'" +
29
27
  " using file pointer: '#{ pointer.inspect }'."
@@ -57,10 +55,13 @@ module Ruhoh::Resources::Pages
57
55
  collection = @ruhoh.collection(resource)
58
56
  config = collection.config["paginator"] || {}
59
57
 
58
+ url = "#{config["url"]}/#{page_number}"
59
+
60
60
  view = @ruhoh.master_view({"resource" => resource})
61
61
  view.page_data = {
62
62
  "layout" => config["layout"],
63
- "current_page" => page_number
63
+ "current_page" => page_number,
64
+ "url" => @ruhoh.to_url(url)
64
65
  }
65
66
  view
66
67
  end
@@ -9,15 +9,13 @@ module Ruhoh::Resources::Widgets
9
9
  model = find("#{ name }/#{ (widget_config['use'] || "default") }")
10
10
  return '' unless model
11
11
 
12
- view = ruhoh.master_view({})
13
-
14
12
  # merge the config.yml data into the inline layout data.
15
13
  # Note this is reversing the normal hierarchy
16
14
  # in that inline should always override config level.
17
15
  # However the inline in this case is set as implementation defaults
18
16
  # and meant to be overridden by user specific data.
19
- view.render(model.content, {
20
- "this_config" => model.data.merge(widget_config),
17
+ master.render(model.content, {
18
+ "this_config" => Ruhoh::Utils.deep_merge(model.data, widget_config),
21
19
  "this_path" => ruhoh.to_url(url_endpoint, name)
22
20
  })
23
21
  end
@@ -13,9 +13,9 @@ module Ruhoh::Resources::Widgets
13
13
  compiled_path = Ruhoh::Utils.url_to_path(@ruhoh.to_url(@collection.url_endpoint), @ruhoh.paths.compiled)
14
14
  FileUtils.mkdir_p compiled_path
15
15
 
16
- files = @collection.files.values
17
- # Don't copy over the layouts.
18
- files.delete_if { |p| p['id'].end_with?('.html') }
16
+ files = collection.files.values
17
+ files.delete_if { |p| !is_valid_file? (p['id']) }
18
+
19
19
  files.each do |pointer|
20
20
  compiled_file = File.join(compiled_path, pointer['id'])
21
21
  FileUtils.mkdir_p File.dirname(compiled_file)
@@ -23,5 +23,21 @@ module Ruhoh::Resources::Widgets
23
23
  Ruhoh::Friend.say { green " > #{pointer['id']}" }
24
24
  end
25
25
  end
26
+
27
+ def is_valid_file?(filepath)
28
+ return false if filepath.end_with?('.html')
29
+
30
+ collection.widgets.each do |name|
31
+ widget_config = collection.config[name] || {}
32
+
33
+ model = collection.find("#{ name }/#{ (widget_config['use'] || "default") }")
34
+ next unless model
35
+
36
+ excludes = Array(model.data['exclude']).map { |node| Regexp.new(node) }
37
+ excludes.each { |regex| return false if filepath =~ regex }
38
+ end
39
+
40
+ true
41
+ end
26
42
  end
27
43
  end
data/lib/ruhoh/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  class Ruhoh
2
- Version = VERSION = '2.1'
2
+ Version = VERSION = '2.2'
3
3
  RuhohSpec = '2.1'
4
4
  end
data/ruhoh.gemspec CHANGED
@@ -20,6 +20,10 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency 'redcarpet', "~> 2.1"
21
21
  s.add_dependency 'nokogiri', "~> 1.5"
22
22
 
23
+ s.add_development_dependency 'cucumber'
24
+ s.add_development_dependency 'capybara'
25
+ s.add_development_dependency 'rspec-expectations'
26
+
23
27
  s.files = `git ls-files`.
24
28
  split("\n").
25
29
  sort.
@@ -0,0 +1,38 @@
1
+ require 'sprockets'
2
+
3
+ module Ruhoh::SprocketsPlugin
4
+ module Compiler
5
+ extend Ruhoh::Base::CompilableAsset
6
+ def run
7
+ env = Sprockets::Environment.new
8
+ env.logger = Logger.new(STDOUT)
9
+ env.logger.level = Logger::WARN
10
+
11
+ collection = @collection
12
+
13
+ unless collection.paths?
14
+ Ruhoh::Friend.say { yellow "#{collection.resource_name.capitalize}: directory not found - skipping." }
15
+ return
16
+ end
17
+
18
+ Ruhoh::Friend.say { cyan "#{collection.resource_name.capitalize}: (using sprockets)" }
19
+
20
+ collection.paths.reverse.each do |path|
21
+ env.append_path(path)
22
+ end
23
+
24
+ compiled_path = Ruhoh::Utils.url_to_path(@ruhoh.to_url(collection.url_endpoint), @ruhoh.paths.compiled)
25
+ FileUtils.mkdir_p compiled_path
26
+
27
+ manifest = Sprockets::Manifest.new(env, compiled_path)
28
+ assets = collection.files.values.map{ |p|
29
+ Ruhoh::Friend.say { green " > #{p['id']}" }
30
+ p["id"]
31
+ }
32
+ manifest.compile(assets)
33
+
34
+ # Update the paths to the digest format:
35
+ @collection._cache.merge!(manifest.assets)
36
+ end
37
+ end
38
+ end
@@ -1,25 +1,4 @@
1
- require 'sprockets'
2
- module Ruhoh::Resources::Javascripts
3
- class Compiler
4
- include Ruhoh::Base::Compilable
5
-
6
- def run
7
- Ruhoh::Friend.say { cyan "Javascripts: (using sprockets)" }
8
- env = Sprockets::Environment.new
9
- env.logger = Logger.new(STDOUT)
10
- @collection.paths.reverse.each do |h|
11
- env.append_path(File.join(h["path"], @collection.resource_name))
12
- end
13
-
14
- compiled_path = Ruhoh::Utils.url_to_path(@collection.url_endpoint, @ruhoh.paths.compiled)
15
- FileUtils.mkdir_p compiled_path
16
-
17
- manifest = Sprockets::Manifest.new(env, compiled_path)
18
- assets = @collection.files.values.map{ |p| p["id"] }
19
- manifest.compile(assets)
20
-
21
- # Update the stylesheet paths to the digest format:
22
- @collection._cache.merge!(manifest.assets)
23
- end
24
- end
1
+ require File.join(File.dirname(__FILE__), '..', 'compiler.rb')
2
+ class Ruhoh::Resources::Javascripts::Compiler
3
+ include Ruhoh::SprocketsPlugin::Compiler
25
4
  end