ruhoh 1.1 → 2.1

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 (121) hide show
  1. data/Gemfile +3 -3
  2. data/README.md +3 -2
  3. data/Rakefile +1 -22
  4. data/bin/ruhoh +1 -5
  5. data/history.json +16 -0
  6. data/lib/ruhoh.rb +229 -84
  7. data/lib/ruhoh/base/collection.rb +280 -0
  8. data/lib/ruhoh/base/compiler.rb +55 -0
  9. data/lib/ruhoh/base/model.rb +220 -0
  10. data/lib/ruhoh/base/model_view.rb +152 -0
  11. data/lib/ruhoh/base/watcher.rb +25 -0
  12. data/lib/ruhoh/cache.rb +46 -0
  13. data/lib/ruhoh/client.rb +162 -0
  14. data/lib/ruhoh/collections.rb +172 -0
  15. data/lib/ruhoh/console_methods.rb +21 -0
  16. data/lib/ruhoh/{converters/converter.rb → converter.rb} +4 -1
  17. data/lib/ruhoh/programs/compile.rb +22 -0
  18. data/lib/ruhoh/programs/preview.rb +63 -0
  19. data/lib/ruhoh/programs/watch.rb +45 -0
  20. data/lib/ruhoh/resources/dash/collection.rb +10 -0
  21. data/lib/ruhoh/resources/dash/model.rb +5 -0
  22. data/lib/ruhoh/resources/dash/model_view.rb +5 -0
  23. data/lib/ruhoh/resources/dash/previewer.rb +13 -0
  24. data/lib/ruhoh/resources/data/collection.rb +9 -0
  25. data/lib/ruhoh/resources/data/collection_view.rb +23 -0
  26. data/lib/ruhoh/resources/javascripts/collection.rb +9 -0
  27. data/lib/ruhoh/resources/javascripts/collection_view.rb +46 -0
  28. data/lib/ruhoh/resources/javascripts/compiler.rb +5 -0
  29. data/lib/ruhoh/resources/layouts/client.rb +45 -0
  30. data/lib/ruhoh/resources/layouts/model.rb +16 -0
  31. data/lib/ruhoh/resources/media/collection.rb +9 -0
  32. data/lib/ruhoh/resources/media/compiler.rb +27 -0
  33. data/lib/ruhoh/resources/pages/client.rb +124 -0
  34. data/lib/ruhoh/resources/pages/collection.rb +86 -0
  35. data/lib/ruhoh/resources/pages/collection_view.rb +73 -0
  36. data/lib/ruhoh/resources/pages/compiler.rb +101 -0
  37. data/lib/ruhoh/resources/pages/model.rb +5 -0
  38. data/lib/ruhoh/resources/pages/model_view.rb +5 -0
  39. data/lib/ruhoh/resources/pages/previewer.rb +72 -0
  40. data/lib/ruhoh/resources/partials/model.rb +11 -0
  41. data/lib/ruhoh/resources/stylesheets/collection.rb +9 -0
  42. data/lib/ruhoh/resources/stylesheets/collection_view.rb +45 -0
  43. data/lib/ruhoh/resources/stylesheets/compiler.rb +5 -0
  44. data/lib/ruhoh/resources/theme/collection.rb +14 -0
  45. data/lib/ruhoh/resources/theme/compiler.rb +54 -0
  46. data/lib/ruhoh/resources/widgets/collection.rb +26 -0
  47. data/lib/ruhoh/resources/widgets/collection_view.rb +34 -0
  48. data/lib/ruhoh/resources/widgets/compiler.rb +27 -0
  49. data/lib/ruhoh/resources/widgets/model.rb +16 -0
  50. data/lib/ruhoh/routes.rb +29 -0
  51. data/lib/ruhoh/utils.rb +32 -49
  52. data/lib/ruhoh/version.rb +2 -2
  53. data/lib/ruhoh/views/helpers/categories.rb +38 -0
  54. data/lib/ruhoh/views/helpers/paginator.rb +39 -0
  55. data/lib/ruhoh/views/helpers/tags.rb +37 -0
  56. data/lib/ruhoh/views/master_view.rb +183 -0
  57. data/lib/ruhoh/views/rmustache.rb +24 -0
  58. data/ruhoh.gemspec +6 -82
  59. data/spec/spec_helper.rb +1 -1
  60. data/spec/support/shared_contexts.rb +6 -5
  61. data/system/{scaffolds/post.html → _scaffold.html} +1 -1
  62. data/system/{dash.html → dash/index.html} +37 -51
  63. data/system/{scaffolds/layout.html → layouts/_scaffold.html} +0 -0
  64. data/system/layouts/paginator.html +28 -0
  65. data/system/plugins/sprockets/javascripts/compiler.rb +25 -0
  66. data/system/plugins/sprockets/javascripts/previewer.rb +17 -0
  67. data/system/plugins/sprockets/stylesheets/compiler.rb +26 -0
  68. data/system/plugins/sprockets/stylesheets/previewer.rb +17 -0
  69. data/system/widgets/analytics/{layouts/getclicky.html → getclicky.html} +6 -2
  70. data/system/widgets/analytics/{layouts/google.html → google.html} +5 -1
  71. data/system/widgets/comments/{layouts/disqus.html → disqus.html} +6 -2
  72. data/system/widgets/comments/{layouts/facebook.html → facebook.html} +9 -2
  73. data/system/widgets/comments/{layouts/intensedebate.html → intensedebate.html} +5 -1
  74. data/system/widgets/comments/{layouts/livefyre.html → livefyre.html} +5 -1
  75. data/system/widgets/google_prettify/{layouts/google_prettify.html → default.html} +6 -2
  76. metadata +69 -66
  77. data/lib/ruhoh/client/client.rb +0 -306
  78. data/lib/ruhoh/client/console_methods.rb +0 -9
  79. data/lib/ruhoh/client/help.yml +0 -56
  80. data/lib/ruhoh/compiler.rb +0 -72
  81. data/lib/ruhoh/compilers/rss.rb +0 -39
  82. data/lib/ruhoh/compilers/theme.rb +0 -46
  83. data/lib/ruhoh/config.rb +0 -62
  84. data/lib/ruhoh/db.rb +0 -50
  85. data/lib/ruhoh/deployers/s3.rb +0 -71
  86. data/lib/ruhoh/page.rb +0 -106
  87. data/lib/ruhoh/parsers/javascripts.rb +0 -55
  88. data/lib/ruhoh/parsers/layouts.rb +0 -32
  89. data/lib/ruhoh/parsers/pages.rb +0 -79
  90. data/lib/ruhoh/parsers/partials.rb +0 -42
  91. data/lib/ruhoh/parsers/payload.rb +0 -49
  92. data/lib/ruhoh/parsers/posts.rb +0 -259
  93. data/lib/ruhoh/parsers/routes.rb +0 -20
  94. data/lib/ruhoh/parsers/scaffolds.rb +0 -35
  95. data/lib/ruhoh/parsers/site.rb +0 -19
  96. data/lib/ruhoh/parsers/stylesheets.rb +0 -63
  97. data/lib/ruhoh/parsers/theme_config.rb +0 -30
  98. data/lib/ruhoh/parsers/widgets.rb +0 -104
  99. data/lib/ruhoh/paths.rb +0 -83
  100. data/lib/ruhoh/previewer.rb +0 -48
  101. data/lib/ruhoh/program.rb +0 -68
  102. data/lib/ruhoh/templaters/asset_helpers.rb +0 -66
  103. data/lib/ruhoh/templaters/base_helpers.rb +0 -147
  104. data/lib/ruhoh/templaters/helpers.rb +0 -8
  105. data/lib/ruhoh/templaters/rmustache.rb +0 -70
  106. data/lib/ruhoh/urls.rb +0 -50
  107. data/lib/ruhoh/watch.rb +0 -78
  108. data/spec/config_spec.rb +0 -50
  109. data/spec/db_spec.rb +0 -91
  110. data/spec/page_spec.rb +0 -164
  111. data/spec/parsers/layouts_spec.rb +0 -41
  112. data/spec/parsers/pages_spec.rb +0 -120
  113. data/spec/parsers/posts_spec.rb +0 -309
  114. data/spec/parsers/routes_spec.rb +0 -39
  115. data/spec/parsers/site_spec.rb +0 -28
  116. data/spec/setup_spec.rb +0 -12
  117. data/system/scaffolds/draft.html +0 -9
  118. data/system/scaffolds/page.html +0 -4
  119. data/system/widgets/analytics/config.yml +0 -5
  120. data/system/widgets/comments/config.yml +0 -13
  121. data/system/widgets/google_prettify/config.yml +0 -1
@@ -0,0 +1,55 @@
1
+ module Ruhoh::Base
2
+
3
+ module Compilable
4
+ def self.included(klass)
5
+ __send__(:attr_reader, :collection)
6
+ end
7
+
8
+ def initialize(collection)
9
+ @ruhoh = collection.ruhoh
10
+ @collection = collection
11
+ end
12
+ end
13
+
14
+ module CompilableAsset
15
+ include Compilable
16
+
17
+ # A basic compiler task which copies each valid collection resource file to the compiled folder.
18
+ # Valid files are identified by their pointers.
19
+ # Invalid files are files that are excluded from the resource's configuration settings.
20
+ # The collection's url_endpoint is used to determine the final compiled path.
21
+ #
22
+ # @returns Nothing.
23
+ def run
24
+ collection = @collection
25
+
26
+ unless @collection.paths?
27
+ Ruhoh::Friend.say { yellow "#{collection.resource_name.capitalize}: directory not found - skipping." }
28
+ return
29
+ end
30
+ Ruhoh::Friend.say { cyan "#{collection.resource_name.capitalize}: (copying valid files)" }
31
+
32
+ compiled_path = Ruhoh::Utils.url_to_path(@ruhoh.to_url(@collection.url_endpoint), @ruhoh.paths.compiled)
33
+ FileUtils.mkdir_p compiled_path
34
+
35
+ manifest = {}
36
+ @collection.files.values.each do |pointer|
37
+ digest = Digest::MD5.file(pointer['realpath']).hexdigest
38
+ digest_file = pointer['id'].sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
39
+ manifest[pointer['id']] = digest_file
40
+
41
+ compiled_file = File.join(compiled_path, digest_file)
42
+ FileUtils.mkdir_p File.dirname(compiled_file)
43
+ FileUtils.cp_r pointer['realpath'], compiled_file
44
+ Ruhoh::Friend.say { green " > #{pointer['id']}" }
45
+ end
46
+
47
+ # Update the paths to the digest format:
48
+ @collection.load_collection_view._cache.merge!(manifest)
49
+ end
50
+ end
51
+
52
+ class Compiler
53
+ include Ruhoh::Base::Compilable
54
+ end
55
+ end
@@ -0,0 +1,220 @@
1
+ module Ruhoh::Base
2
+ module Modelable
3
+ include Observable
4
+
5
+ def self.included(klass)
6
+ klass.__send__(:attr_reader, :pointer, :ruhoh)
7
+ end
8
+
9
+ def initialize(ruhoh, pointer)
10
+ raise "Cannot instantiate a model with a nil pointer" unless pointer
11
+ @ruhoh = ruhoh
12
+ @pointer = pointer
13
+ end
14
+
15
+ # @returns[Hash Object] Top page metadata
16
+ def data
17
+ return @data if @data
18
+ process
19
+ @data || {}
20
+ end
21
+
22
+ # @returns[String] Raw (unconverted) page content
23
+ def content
24
+ return @content if @content
25
+ process
26
+ @content || ''
27
+ end
28
+
29
+ def collection
30
+ @ruhoh.collection(@pointer['resource'])
31
+ end
32
+
33
+ # Override this to process custom data
34
+ def process
35
+ changed
36
+ notify_observers(@pointer)
37
+ @pointer
38
+ end
39
+
40
+ def try(method)
41
+ return __send__(method) if respond_to?(method)
42
+ return data[method] if data.key?(method.to_s)
43
+ false
44
+ end
45
+ end
46
+
47
+ module PageLike
48
+ include Modelable
49
+
50
+ FMregex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
51
+ DateMatcher = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
52
+ Matcher = /^(.+\/)*(.*)(\.[^.]+)$/
53
+
54
+ # Process this file. See #parse_page_file
55
+ # @return[Hash] the processed data from the file.
56
+ # ex:
57
+ # { "content" => "..", "data" => { "key" => "value" } }
58
+ def process
59
+ return {} unless file?
60
+
61
+ parsed_page = parse_page_file
62
+ data = parsed_page['data']
63
+
64
+ filename_data = parse_page_filename(@pointer['id'])
65
+
66
+ data['pointer'] = @pointer
67
+ data['id'] = @pointer['id']
68
+
69
+ data['title'] = data['title'] || filename_data['title']
70
+ data['date'] ||= filename_data['date'].to_s
71
+ data['url'] = permalink(data)
72
+ data['layout'] = collection.config['layout'] if data['layout'].nil?
73
+
74
+ parsed_page['data'] = data
75
+
76
+ changed
77
+ notify_observers(parsed_page)
78
+ data
79
+ end
80
+
81
+ protected
82
+
83
+ # Is the resource backed by a physical file in the filesystem?
84
+ # For example the pagination system uses a page-stub
85
+ # that has no reference to an actual file.
86
+ # @return[Boolean]
87
+ def file?
88
+ !!@pointer['realpath']
89
+ end
90
+
91
+ # Primary method to parse the file as a page-like object.
92
+ # File API is currently defines:
93
+ # 1. Top YAML meta-data
94
+ # 2. Page Body
95
+ #
96
+ # @returns[Hash Object] processed top meta-data, raw (unconverted) content body
97
+ def parse_page_file
98
+ raise "File not found: #{@pointer['realpath']}" unless File.exist?(@pointer['realpath'])
99
+
100
+ page = File.open(@pointer['realpath'], 'r:UTF-8') {|f| f.read }
101
+
102
+ front_matter = page.match(FMregex)
103
+ data = front_matter ?
104
+ (YAML.load(front_matter[0].gsub(/---\n/, "")) || {}) :
105
+ {}
106
+
107
+ result = {
108
+ "data" => data,
109
+ "content" => page.gsub(FMregex, '')
110
+ }
111
+
112
+ # variable cache
113
+ @data = data
114
+ @content = result['content']
115
+
116
+ result
117
+ rescue Psych::SyntaxError => e
118
+ Ruhoh.log.error("Psych::SyntaxError while parsing top YAML Metadata in #{ @pointer['realpath'] }\n" +
119
+ "#{ e.message }\n" +
120
+ "Try validating the YAML metadata using http://yamllint.com"
121
+ )
122
+ nil
123
+ end
124
+
125
+ def formatted_date(date)
126
+ Time.parse(date.to_s).strftime('%Y-%m-%d') rescue false
127
+ end
128
+
129
+ def parse_page_filename(filename)
130
+ data = *filename.match(DateMatcher)
131
+ data = *filename.match(Matcher) if data.empty?
132
+ return {} if data.empty?
133
+
134
+ if filename =~ DateMatcher
135
+ {
136
+ "path" => data[1],
137
+ "date" => data[2],
138
+ "slug" => data[3],
139
+ "title" => self.to_title(data[3]),
140
+ "extension" => data[4]
141
+ }
142
+ else
143
+ {
144
+ "path" => data[1],
145
+ "slug" => data[2],
146
+ "title" => to_title(data[2]),
147
+ "extension" => data[3]
148
+ }
149
+ end
150
+ end
151
+
152
+ # my-post-title ===> My Post Title
153
+ def to_title(file_slug)
154
+ if file_slug == 'index' && !@pointer['id'].index('/').nil?
155
+ file_slug = @pointer['id'].split('/')[-2]
156
+ end
157
+
158
+ file_slug.gsub(/[^\p{Word}+]/u, ' ').gsub(/\b\w/){$&.upcase}
159
+ end
160
+
161
+ # Another blatently stolen method from Jekyll
162
+ # The category is only the first one if multiple categories exist.
163
+ def permalink(page_data)
164
+ format = page_data['permalink'] || collection.config['permalink']
165
+ format ||= "/:path/:filename"
166
+
167
+ url = if format.include?(':')
168
+ title = Ruhoh::Utils.to_url_slug(page_data['title'])
169
+ filename = File.basename(page_data['id'])
170
+ category = Array(page_data['categories'])[0]
171
+ category = category.split('/').map {|c| Ruhoh::Utils.to_url_slug(c) }.join('/') if category
172
+ relative_path = File.dirname(page_data['id'])
173
+ relative_path = "" if relative_path == "."
174
+ data = {
175
+ "title" => title,
176
+ "filename" => filename,
177
+ "path" => File.join(@pointer["resource"], relative_path),
178
+ "relative_path" => relative_path,
179
+ "categories" => category || '',
180
+ }
181
+
182
+ date = Date.parse(page_data['date']) rescue nil
183
+ if date
184
+ data.merge({
185
+ "year" => date.strftime("%Y"),
186
+ "month" => date.strftime("%m"),
187
+ "day" => date.strftime("%d"),
188
+ "i_day" => date.strftime("%d").to_i.to_s,
189
+ "i_month" => date.strftime("%m").to_i.to_s,
190
+ })
191
+ end
192
+
193
+ data.inject(format) { |result, token|
194
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
195
+ }.gsub(/\/+/, "/")
196
+ else
197
+ # Use the literal permalink if it is a non-tokenized string.
198
+ format.gsub(/^\//, '').split('/').map {|p| CGI::escape(p) }.join('/')
199
+ end
200
+
201
+ # Only recognize extensions registered from a 'convertable' module.
202
+ # This means 'non-convertable' extensions should pass-through.
203
+ if Ruhoh::Converter.extensions.include?(File.extname(url))
204
+ url = url.gsub(%r{#{File.extname(url)}$}, '.html')
205
+ end
206
+
207
+ unless (page_data['permalink_ext'] || collection.config['permalink_ext'])
208
+ url = url.gsub(/index.html$/, '').gsub(/\.html$/, '')
209
+ end
210
+
211
+ url = '/' if url.empty?
212
+
213
+ @ruhoh.to_url(url)
214
+ end
215
+ end
216
+
217
+ class Model
218
+ include Modelable
219
+ end
220
+ end
@@ -0,0 +1,152 @@
1
+ module Ruhoh::Base
2
+ module ModelViewable
3
+ def initialize(model)
4
+ super(model)
5
+ @model = model
6
+ @ruhoh = model.ruhoh
7
+
8
+ # TODO: THIS AUTOMATICALLY CALLS PROCESS ON THE MODEL =XXXX
9
+ # Define direct access to the data Hash object
10
+ # but don't overwrite methods if already defined.
11
+ data.keys.each do |method|
12
+ (class << self; self; end).class_eval do
13
+ next if method_defined?(method)
14
+ define_method method do |*args, &block|
15
+ data[method]
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def <=>(other)
22
+ id <=> other.id
23
+ end
24
+
25
+ def [](attribute)
26
+ __send__(attribute)
27
+ end
28
+
29
+ def []=(key, value)
30
+ __send__("#{key}=", value)
31
+ end
32
+ end
33
+
34
+ module PageViewable
35
+ include ModelViewable
36
+
37
+ # Default order by alphabetical title name.
38
+ def <=>(other)
39
+ sort = @model.collection.config["sort"] || []
40
+ attribute = sort[0] || "title"
41
+ direction = sort[1] || "asc"
42
+
43
+ this_data = __send__(attribute)
44
+ other_data = other.__send__(attribute)
45
+ if attribute == "date"
46
+ begin
47
+ this_data = Date.parse(this_data)
48
+ other_data = Date.parse(other_data)
49
+ rescue ArgumentError
50
+ Ruhoh.log.error(
51
+ "ArgumentError:" +
52
+ " The '#{ @model.collection.resource_name }' collection is configured to sort based on 'date'" +
53
+ " but '#{ @model.pointer['id'] }' has no parseable date in its metadata." +
54
+ " Add date: 'YYYY-MM-DD' to its YAML metadata."
55
+ )
56
+ end
57
+ direction = sort[1] || "desc" #default should be reverse
58
+ end
59
+
60
+ if direction == "asc"
61
+ this_data <=> other_data
62
+ else
63
+ other_data <=> this_data
64
+ end
65
+ end
66
+
67
+ def categories
68
+ @model.collection.to_categories(data['categories'])
69
+ end
70
+
71
+ def tags
72
+ @model.collection.to_tags(data['tags'])
73
+ end
74
+
75
+ # Lazy-load the page body.
76
+ # Notes:
77
+ # @content is not used for caching, it's used to manually
78
+ # define content for a given page. Useful in the case that
79
+ # you want to model a resource that does not actually
80
+ # reference a file.
81
+ def content
82
+ return @content if @content
83
+ content = @model.collection.master.render(@model.content)
84
+ Ruhoh::Converter.convert(content, id)
85
+ end
86
+
87
+ def is_active_page
88
+ id == @model.collection.master.page_data['id']
89
+ end
90
+
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.
97
+ 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
106
+ break
107
+ end
108
+ else
109
+ line_count += 1
110
+ end
111
+ end
112
+
113
+ summary = content.lines.to_a[0, line_breakpoint].join
114
+
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}"
121
+ end
122
+ end
123
+
124
+ summary = @model.collection.master.render(summary)
125
+ Ruhoh::Converter.convert(summary, id)
126
+ end
127
+
128
+ def next
129
+ return unless id
130
+ all_cache = @model.collection.all
131
+ index = all_cache.index {|p| p["id"] == id}
132
+ return unless index && (index-1 >= 0)
133
+ _next = all_cache[index-1]
134
+ return unless _next
135
+ _next
136
+ end
137
+
138
+ def previous
139
+ return unless id
140
+ all_cache = @model.collection.all
141
+ index = all_cache.index {|p| p["id"] == id}
142
+ return unless index && (index+1 >= 0)
143
+ prev = all_cache[index+1]
144
+ return unless prev
145
+ prev
146
+ end
147
+ end
148
+
149
+ class ModelView < SimpleDelegator
150
+ include ModelViewable
151
+ end
152
+ end
@@ -0,0 +1,25 @@
1
+ module Ruhoh::Base
2
+ module Watchable
3
+ def self.included(klass)
4
+ klass.__send__(:attr_accessor, :collection)
5
+ end
6
+
7
+ def initialize(collection)
8
+ @collection = collection
9
+ end
10
+
11
+ def update(path)
12
+ # Drop the resource namespace
13
+ matcher = File::ALT_SEPARATOR ?
14
+ %r{^.+(#{ File::SEPARATOR }|#{ File::ALT_SEPARATOR })} :
15
+ %r{^.+#{ File::SEPARATOR }}
16
+
17
+ collection.touch(path.gsub(matcher, ''))
18
+ end
19
+ end
20
+
21
+ # Base watcher class that loads if no custom Watcher class is defined.
22
+ class Watcher
23
+ include Watchable
24
+ end
25
+ end