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
data/Gemfile CHANGED
@@ -2,12 +2,12 @@ source "http://rubygems.org"
2
2
  gemspec
3
3
 
4
4
  gem 'rack', "~> 1.4"
5
- gem 'directory_watcher', "~> 1.4"
6
- gem 'psych', "~> 1.3", :platforms => [:ruby_18, :mingw_18]
5
+ gem 'directory_watcher', "~> 1.4.0"
6
+ gem 'psych', "~> 1.3"#, :platforms => [:ruby_18, :mingw_18]
7
7
  gem 'redcarpet', "~> 2.1"
8
8
  gem 'nokogiri', "~> 1.5"
9
9
 
10
10
  group :development do
11
- gem 'rspec', "~> 2.11"
11
+ gem 'rspec', "~> 2"
12
12
  gem 'rake'
13
13
  end
data/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  ### Usage
6
6
 
7
- $ gem install ruhoh
8
- $ ruhoh help
7
+ This is the new ruhoh 2.x.alpha code.
8
+
9
+ Please follow the directions on the [new 2.0.alpha blog scaffold](https://github.com/ruhoh/blog/tree/2.0.alpha#readme)
9
10
 
10
11
  ### Platforms
11
12
 
data/Rakefile CHANGED
@@ -17,33 +17,12 @@ task :release => :build do
17
17
  sh "gem push pkg/#{name}-#{Ruhoh::VERSION}.gem"
18
18
  end
19
19
 
20
- task :build => :gemspec do
20
+ task :build do
21
21
  sh "mkdir -p pkg"
22
22
  sh "gem build #{gemspec_file}"
23
23
  sh "mv #{gem_file} pkg"
24
24
  end
25
25
 
26
- task :gemspec do
27
- # read spec file and split out manifest section
28
- spec = File.read(gemspec_file)
29
- head, manifest, tail = spec.split(" # = MANIFEST =\n")
30
-
31
- # determine file list from git ls-files
32
- files = `git ls-files`.
33
- split("\n").
34
- sort.
35
- reject { |file| file =~ /^\./ }.
36
- reject { |file| file =~ /^(rdoc|pkg|coverage)/ }.
37
- map { |file| " #{file}" }.
38
- join("\n")
39
-
40
- # piece file back together and write
41
- manifest = " s.files = %w[\n#{files}\n ]\n"
42
- spec = [head, manifest, tail].join(" # = MANIFEST =\n")
43
- File.open(gemspec_file, 'w') { |io| io.write(spec) }
44
- puts "Updated #{gemspec_file}"
45
- end
46
-
47
26
  ## Tests
48
27
 
49
28
  RSpec::Core::RakeTask.new('spec')
data/bin/ruhoh CHANGED
@@ -4,7 +4,7 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
5
  require 'ruhoh'
6
6
  require 'ruhoh/version'
7
- require 'ruhoh/client/client'
7
+ require 'ruhoh/client'
8
8
 
9
9
  require 'optparse'
10
10
 
@@ -14,10 +14,6 @@ options = Options.new
14
14
  opt_parser = OptionParser.new do |opts|
15
15
  opts.banner = 'Use `ruhoh help` for full command list.'
16
16
 
17
- opts.on("-e", "--ext [EXT]", "Specify filename extension. Defaults to '.md' ") do |ext|
18
- options.ext = ext
19
- end
20
-
21
17
  opts.on("--hg", "Use mercurial (hg) instead of git for source control.") do
22
18
  options.hg = true
23
19
  end
data/history.json CHANGED
@@ -1,4 +1,20 @@
1
1
  [
2
+ {
3
+ "version" : "2.1",
4
+ "date" : "19.05.2013",
5
+ "changes" : [
6
+ "Major release. Internal logic has been drastically changed."
7
+ ],
8
+ "features" : [
9
+ "Support for arbitrary custom collections, e.g. ability to model essays, snippets, posts, etc.",
10
+ "Page finders can omit file extensions so you can reference the about.md file using 'about'.",
11
+ "All collections can have their own paginator.",
12
+ "themes and config.yml are optional implying very basic websites can be built without configuration overhead.",
13
+ "ruhoh environment is not instantiated, meaning everything exists in its own instance.",
14
+ "Improved cache management",
15
+ "Support for asset pipeline."
16
+ ]
17
+ },
2
18
  {
3
19
  "version" : "1.1",
4
20
  "date" : "26.08.2012",
data/lib/ruhoh.rb CHANGED
@@ -3,111 +3,256 @@ Encoding.default_internal = 'UTF-8'
3
3
  require 'yaml'
4
4
  require 'psych'
5
5
  YAML::ENGINE.yamler = 'psych'
6
-
7
6
  require 'json'
8
7
  require 'time'
9
8
  require 'cgi'
10
9
  require 'fileutils'
11
10
  require 'ostruct'
11
+ require 'delegate'
12
+ require 'digest'
13
+ require 'observer'
12
14
 
13
15
  require 'mustache'
14
16
 
15
17
  require 'ruhoh/logger'
16
18
  require 'ruhoh/utils'
17
19
  require 'ruhoh/friend'
18
- require 'ruhoh/config'
19
- require 'ruhoh/paths'
20
- require 'ruhoh/urls'
21
- require 'ruhoh/db'
22
- require 'ruhoh/templaters/rmustache'
23
- require 'ruhoh/converters/converter'
24
- require 'ruhoh/page'
25
- require 'ruhoh/previewer'
26
- require 'ruhoh/watch'
27
- require 'ruhoh/program'
20
+
21
+ require 'ruhoh/converter'
22
+ require 'ruhoh/views/master_view'
23
+ require 'ruhoh/collections'
24
+ require 'ruhoh/cache'
25
+ require 'ruhoh/routes'
26
+ require 'ruhoh/programs/preview'
28
27
 
29
28
  class Ruhoh
30
-
31
29
  class << self
32
30
  attr_accessor :log
33
- attr_reader :config, :names, :paths, :root, :urls, :base
31
+ attr_reader :names, :root
34
32
  end
35
-
36
- @log = Ruhoh::Logger.new
33
+
34
+ attr_accessor :log, :env
35
+ attr_reader :config, :paths, :root, :base, :cache, :collections, :routes
36
+
37
37
  Root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
38
- Names = {
39
- :assets => 'assets',
40
- :config_data => 'config.yml',
41
- :compiled => 'compiled',
42
- :dashboard_file => 'dash.html',
43
- :layouts => 'layouts',
44
- :media => 'media',
45
- :pages => 'pages',
46
- :partials => 'partials',
47
- :plugins => 'plugins',
48
- :posts => 'posts',
49
- :javascripts => 'javascripts',
50
- :scaffolds => 'scaffolds',
51
- :site_data => 'site.yml',
52
- :stylesheets => 'stylesheets',
53
- :system => 'system',
54
- :themes => 'themes',
55
- :theme_config => 'theme.yml',
56
- :widgets => 'widgets',
57
- :widget_config => 'config.yml'
58
- }
59
- @names = OpenStruct.new(Names)
38
+ @log = Ruhoh::Logger.new
60
39
  @root = Root
61
-
40
+
41
+ def initialize
42
+ @collections = Ruhoh::Collections.new(self)
43
+ @cache = Ruhoh::Cache.new(self)
44
+ @routes = Ruhoh::Routes.new(self)
45
+ end
46
+
47
+ def master_view(pointer)
48
+ Ruhoh::Views::MasterView.new(self, pointer)
49
+ end
50
+
62
51
  # Public: Setup Ruhoh utilities relative to the current application directory.
63
52
  # Returns boolean on success/failure
64
- def self.setup(opts={})
65
- self.reset
66
- @log.log_file = opts[:log_file] if opts[:log_file]
67
- @base = opts[:source] if opts[:source]
68
- @config = Ruhoh::Config.generate
69
- !!@config
70
- end
71
-
72
- def self.reset
73
- @base = Dir.getwd
74
- end
75
-
76
- def self.setup_paths
77
- self.ensure_config
78
- @paths = Ruhoh::Paths.generate
79
- end
80
-
81
- def self.setup_urls
82
- self.ensure_config
83
- @urls = Ruhoh::Urls.generate
84
- end
85
-
86
- def self.setup_plugins
87
- self.ensure_paths
88
- plugins = Dir[File.join(self.paths.plugins, "**/*.rb")]
53
+ def setup(opts={})
54
+ self.class.log.log_file = opts[:log_file] if opts[:log_file] #todo
55
+ @base = opts[:source] ? opts[:source] : Dir.getwd
56
+ !!config
57
+ end
58
+
59
+ def collection(resource)
60
+ @collections.load(resource)
61
+ end
62
+
63
+ def config
64
+ config = Ruhoh::Utils.parse_yaml_file(@base, "config.yml") || {}
65
+ config['compiled'] = config['compiled'] ? File.expand_path(config['compiled']) : "compiled"
66
+
67
+ config['_root'] ||= {}
68
+ config['_root']['permalink'] ||= "/:relative_path/:filename"
69
+ config['_root']['paginator'] ||= {}
70
+ config['_root']['paginator']['url'] ||= "/index/"
71
+ config['_root']['rss'] ||= {}
72
+ config['_root']['rss']['url'] ||= "/"
73
+
74
+ config['base_path'] = config['base_path'].to_s.strip
75
+ if config['base_path'].empty?
76
+ config['base_path'] = '/'
77
+ else
78
+ config['base_path'] += "/" unless config['base_path'][-1] == '/'
79
+ end
80
+
81
+ @config = config
82
+ end
83
+
84
+ Paths = Struct.new(:base, :theme, :system, :compiled)
85
+ def setup_paths
86
+ @paths = Paths.new
87
+ @paths.base = @base
88
+ @paths.system = File.join(Ruhoh::Root, "system")
89
+ @paths.compiled = @config["compiled"]
90
+
91
+ theme = @config.find{ |resource, data| data['use'] == "theme" }
92
+ if theme
93
+ Ruhoh::Friend.say { plain "Using theme: \"#{theme[0]}\""}
94
+ @paths.theme = File.join(@base, theme[0])
95
+ end
96
+
97
+ @paths
98
+ end
99
+
100
+ # Default paths to the 3 levels of the cascade.
101
+ def cascade
102
+ a = [
103
+ {
104
+ "name" => "system",
105
+ "path" => paths.system
106
+ },
107
+ {
108
+ "name" => "base",
109
+ "path" => paths.base
110
+ }
111
+ ]
112
+ a << {
113
+ "name" => "theme",
114
+ "path" => paths.theme
115
+ } if paths.theme
116
+
117
+ a
118
+ end
119
+
120
+ def setup_plugins
121
+ ensure_paths
122
+
123
+ enable_sprockets = @config['asset_pipeline']['enable'] rescue false
124
+ if enable_sprockets
125
+ Ruhoh::Friend.say { green "=> Oh boy! Asset pipeline enabled by way of sprockets =D" }
126
+ sprockets = Dir[File.join(@paths.system, "plugins", "sprockets", "**/*.rb")]
127
+ sprockets.each {|f| require f }
128
+ end
129
+
130
+ plugins = Dir[File.join(@base, "plugins", "**/*.rb")]
89
131
  plugins.each {|f| require f } unless plugins.empty?
90
132
  end
91
-
92
- def self.ensure_setup
93
- return if Ruhoh.config && Ruhoh.paths && Ruhoh.urls
133
+
134
+ def env
135
+ @env || 'development'
136
+ end
137
+
138
+ def base_path
139
+ (env == 'production') ?
140
+ config['base_path'] :
141
+ '/'
142
+ end
143
+
144
+ # @config['base_path'] is assumed to be well-formed.
145
+ # Always remove trailing slash.
146
+ # Returns String - normalized url with prepended base_path
147
+ def to_url(*args)
148
+ url = base_path + args.join('/')
149
+ url = url.gsub(/\/{2,}/, '/')
150
+ (url == "/") ? url : url.chomp('/')
151
+ end
152
+
153
+ def relative_path(filename)
154
+ filename.gsub(Regexp.new("^#{@base}/"), '')
155
+ end
156
+
157
+ # Compile the ruhoh instance (save to disk).
158
+ # Note: This method recursively removes the target directory. Should there be a warning?
159
+ #
160
+ # Extending:
161
+ # TODO: Deprecate this functionality and come up with a 2.0-friendly interface.
162
+ # The Compiler module is a namespace for all compile "tasks".
163
+ # A "task" is a ruby Class that accepts @ruhoh instance via initialize.
164
+ # At compile time all classes in the Ruhoh::Compiler namespace are initialized and run.
165
+ # To add your own compile task simply namespace a class under Ruhoh::Compiler
166
+ # and provide initialize and run methods:
167
+ #
168
+ # class Ruhoh
169
+ # module Compiler
170
+ # class CustomTask
171
+ # def initialize(ruhoh)
172
+ # @ruhoh = ruhoh
173
+ # end
174
+ #
175
+ # def run
176
+ # # do something here
177
+ # end
178
+ # end
179
+ # end
180
+ # end
181
+ def compile
182
+ ensure_paths
183
+ Ruhoh::Friend.say { plain "Compiling for environment: '#{@env}'" }
184
+ FileUtils.rm_r @paths.compiled if File.exist?(@paths.compiled)
185
+ FileUtils.mkdir_p @paths.compiled
186
+
187
+ # Run the resource compilers
188
+ compilers = @collections.all
189
+ # Hack to ensure assets are processed first so post-processing logic reflects in the templates.
190
+ compilers.delete('stylesheets')
191
+ compilers.unshift('stylesheets')
192
+ compilers.delete('javascripts')
193
+ compilers.unshift('javascripts')
194
+
195
+ compilers.each do |name|
196
+ collection = collection(name)
197
+ next unless collection.compiler?
198
+ collection.load_compiler.run
199
+ end
200
+
201
+ # Run extra compiler tasks if available:
202
+ if Ruhoh.const_defined?(:Compiler)
203
+ Ruhoh::Compiler.constants.each {|c|
204
+ compiler = Ruhoh::Compiler.const_get(c)
205
+ next unless compiler.respond_to?(:new)
206
+ task = compiler.new(self)
207
+ next unless task.respond_to?(:run)
208
+ task.run
209
+ }
210
+ end
211
+
212
+ true
213
+ end
214
+
215
+ # Find a file in the base cascade directories
216
+ # @return[Hash, nil] a single file pointer
217
+ def find_file(key)
218
+ dict = _all_files
219
+ dict[key] || dict.values.find{ |a| key == a['id'].gsub(/.[^.]+$/, '') }
220
+ end
221
+
222
+ # Collect all files from the base bascade directories.
223
+ # @return[Hash] dictionary of file pointers
224
+ def _all_files
225
+ dict = {}
226
+ cascade.map{ |a| a['path'] }.each do |path|
227
+ FileUtils.cd(path) {
228
+ Dir["*"].each { |id|
229
+ next unless File.exist?(id) && FileTest.file?(id)
230
+ dict[id] = {
231
+ "id" => id,
232
+ "realpath" => File.realpath(id),
233
+ }
234
+ }
235
+ }
236
+ end
237
+
238
+ dict
239
+ end
240
+
241
+ def ensure_setup
242
+ return if @config && @paths
94
243
  raise 'Ruhoh has not been fully setup. Please call: Ruhoh.setup'
95
- end
96
-
97
- def self.ensure_config
98
- return if Ruhoh.config
99
- raise 'Ruhoh has not setup config. Please call: Ruhoh.setup'
100
- end
101
-
102
- def self.ensure_paths
103
- return if Ruhoh.config && Ruhoh.paths
244
+ end
245
+
246
+ def ensure_paths
247
+ return if @config && @paths
104
248
  raise 'Ruhoh has not setup paths. Please call: Ruhoh.setup'
105
- end
106
-
107
- def self.ensure_urls
108
- return if Ruhoh.config && Ruhoh.urls
109
- raise 'Ruhoh has not setup urls. Please call: Ruhoh.setup + Ruhoh.setup_urls'
110
- end
111
-
112
-
113
- end # Ruhoh
249
+ end
250
+
251
+ def self.collection(resource)
252
+ Collections.load(resource)
253
+ end
254
+
255
+ def self.model(resource)
256
+ Collections.get_module_namespace_for(resource).const_get(:ModelView)
257
+ end
258
+ end
@@ -0,0 +1,280 @@
1
+ module Ruhoh::Base
2
+
3
+ module Collectable
4
+
5
+ def self.included(klass)
6
+ klass.__send__(:attr_accessor, :resource_name, :master)
7
+ klass.__send__(:attr_reader, :ruhoh)
8
+ end
9
+
10
+ def initialize(ruhoh)
11
+ @ruhoh = ruhoh
12
+ end
13
+
14
+ # Public API for finding a resource from this collection
15
+ # @param name_or_pointer [String, Hash]
16
+ # Hash - File pointer
17
+ # String - id (filename) with full extension, e.g: about-me.md
18
+ # String - name (filename) without the extension e.g: about-me
19
+ # Returns the first matched filename.
20
+ # See implementation for how match is determined.
21
+ # @param opts [Hash] Optional options
22
+ # opts[:all] - true to search all files as some may be invalid as resources
23
+ #
24
+ # @return[model, nil] the model is always wrapped in its view.
25
+ def find(name_or_pointer, opts={})
26
+ pointer = find_file(name_or_pointer, opts)
27
+ return nil unless pointer
28
+
29
+ @ruhoh.cache.get(pointer['realpath']) ||
30
+ @ruhoh.cache.set(pointer['realpath'], load_model_view(pointer))
31
+ end
32
+
33
+ # Public API
34
+ # @return[Hash] dictionary of models { "id" => Model }
35
+ def dictionary
36
+ dict = {}
37
+ files.values.each { |pointer|
38
+ dict.merge!({
39
+ pointer['id'] => find(pointer)
40
+ })
41
+ }
42
+ dict
43
+ end
44
+
45
+ def resource_name
46
+ @resource_name ||= self.class.name.split("::").pop
47
+ end
48
+
49
+ # Implemented via Observable module
50
+ # See http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html
51
+ # Collection subscribes to its child models.
52
+ # #update is called on model #process.
53
+ # noop
54
+ def update(model_data)
55
+ end
56
+
57
+ # The default glob for finding files.
58
+ # Every file in all child directories.
59
+ def glob
60
+ "**/*"
61
+ end
62
+
63
+ # Default paths to the 3 levels of the cascade.
64
+ def paths
65
+ Array(@ruhoh.cascade.map{|h| h["path"]}).map { |path|
66
+ collection_path = File.join(path, resource_name)
67
+ next unless File.directory?(collection_path)
68
+
69
+ collection_path
70
+ }.compact
71
+ end
72
+
73
+ # Does this resource have any valid paths to process?
74
+ # A valid path may exist on any of the cascade levels.
75
+ # False means there are no directories on any cascade level.
76
+ # @returns[Boolean]
77
+ def paths?
78
+ !paths.empty?
79
+ end
80
+
81
+ def config
82
+ config = @ruhoh.config[resource_name] || {}
83
+ unless config.is_a?(Hash)
84
+ Ruhoh.log.error("'#{resource_name}' config key in config.yml" +
85
+ " is a #{config.class}; it needs to be a Hash (object).")
86
+ end
87
+ config
88
+ end
89
+
90
+ # NOOP
91
+ # touch a model.
92
+ # Used to perform custom regeneration logic against a model.
93
+ def touch(name_or_pointer)
94
+ end
95
+
96
+ # @param key [String, Hash]
97
+ # String - id (filename) with full extension, e.g: about-me.md
98
+ # String - name (filename) without the extension e.g: about-me
99
+ # Returns the first matched filename.
100
+ # See implementation for how match is determined.
101
+ # Hash - File pointer
102
+ #
103
+ # @param opts [Hash] Optional options
104
+ # opts[:all] - true to search all files as some may be invalid as resources
105
+ #
106
+ # @return [pointer, nil]
107
+ def find_file(key, opts={})
108
+ return key if key.is_a?(Hash) # assume valid pointer
109
+
110
+ dict = opts[:all] ? _all_files : files
111
+
112
+ dict[key] || dict.values.find{ |a| key == a['id'].gsub(/.[^.]+$/, '') }
113
+ end
114
+
115
+ # Collect all files (as mapped by data resources) for this data endpoint.
116
+ # Each resource can have 3 file references, one per each cascade level.
117
+ # The file hashes are collected in order
118
+ # so they will overwrite eachother if found.
119
+
120
+ # @param id [String, Array] Optional.
121
+ # Collect all files for a single data resource.
122
+ # Can be many files due to the cascade.
123
+ # @param [block] Optional.
124
+ # Implement custom validation logic by passing in a block.
125
+ # The block is given (id, self) as args.
126
+ # Return true/false for whether the file is valid/invalid.
127
+ #
128
+ # @return[Hash] dictionary of pointers.
129
+ def files(id=nil, &block)
130
+ return @ruhoh.cache.get(files_cache_key) if @ruhoh.cache.get(files_cache_key)
131
+
132
+ dict = _all_files
133
+ dict.keep_if do |id, pointer|
134
+ block_given? ? yield(id, self) : valid_file?(id)
135
+ end
136
+
137
+ @ruhoh.cache.set(files_cache_key, dict)
138
+ dict
139
+ end
140
+
141
+ # Collect all files within this collection, valid or otherwise.
142
+ # Each resource can have 3 file references, one per each cascade level.
143
+ # The file hashes are collected in order and overwrite eachother if found.
144
+ # This is a low-level method, see #files for the public interface.
145
+ #
146
+ # @return[Hash] dictionary of pointers.
147
+ def _all_files
148
+ dict = {}
149
+ paths.each do |path|
150
+ FileUtils.cd(path) {
151
+ Dir[glob].each { |id|
152
+ next unless File.exist?(id) && FileTest.file?(id)
153
+ dict[id] = {
154
+ "id" => id,
155
+ "realpath" => File.realpath(id),
156
+ "resource" => resource_name,
157
+ }
158
+ }
159
+ }
160
+ end
161
+
162
+ dict
163
+ end
164
+
165
+ def valid_file?(filepath)
166
+ return false if filepath.start_with?('.')
167
+ return false if filepath.start_with?('_')
168
+ excludes = Array(config['exclude']).map { |node| Regexp.new(node) }
169
+ excludes.each { |regex| return false if filepath =~ regex }
170
+ true
171
+ end
172
+
173
+ %w{
174
+ collection_view
175
+ model
176
+ model_view
177
+ client
178
+ compiler
179
+ watcher
180
+ previewer
181
+ }.each do |method_name|
182
+ define_method(method_name) do
183
+ get_module_namespace.const_get(camelize(method_name).to_sym)
184
+ end
185
+
186
+ define_method("#{method_name}?") do
187
+ get_module_namespace.const_defined?(camelize(method_name).to_sym)
188
+ end
189
+ end
190
+
191
+ def load_collection_view
192
+ @_collection_view ||= collection_view? ?
193
+ collection_view.new(self) :
194
+ self
195
+ end
196
+
197
+ def load_model(pointer)
198
+ _model = model? ?
199
+ model.new(@ruhoh, pointer) :
200
+ Ruhoh::Base::Model.new(@ruhoh, pointer)
201
+ _model.add_observer(self)
202
+ _model
203
+ end
204
+
205
+ def load_model_view(pointer)
206
+ model_view? ?
207
+ model_view.new(load_model(pointer)) :
208
+ Ruhoh::Base::ModelView.new(load_model(pointer))
209
+ end
210
+
211
+ def load_client(opts)
212
+ @_client ||= client.new(load_collection_view, opts)
213
+ end
214
+
215
+ def load_compiler
216
+ @_compiler ||= compiler.new(load_collection_view)
217
+ end
218
+
219
+ def load_watcher(*args)
220
+ @_watcher ||= watcher? ?
221
+ watcher.new(load_collection_view) :
222
+ Ruhoh::Base::Watcher.new(load_collection_view)
223
+ end
224
+
225
+ def load_previewer(*args)
226
+ @_previewer ||= previewer.new(@ruhoh)
227
+ end
228
+
229
+ def files_cache_key
230
+ "#{ resource_name }-files"
231
+ end
232
+
233
+ def scaffold
234
+ pointer = find_file('_scaffold', all: true) || @ruhoh.find_file('_scaffold')
235
+ return '' unless pointer
236
+
237
+ File.open(pointer['realpath'], 'r:UTF-8') { |f| f.read }
238
+ end
239
+
240
+ protected
241
+
242
+ # Load the registered resource else default to Pages if not configured.
243
+ # @returns[Constant] the resource's module namespace
244
+ def get_module_namespace
245
+ type = @ruhoh.config[resource_name]["use"] rescue nil
246
+ if type
247
+ if @ruhoh.collections.registered.include?(type)
248
+ Ruhoh::Resources.const_get(camelize(type))
249
+ elsif @ruhoh.collections.base.include?(type)
250
+ Ruhoh::Base.const_get(camelize(type))
251
+ else
252
+ klass = camelize(type)
253
+ Friend.say {
254
+ red "#{resource_name} resource set to use:'#{type}' in config.yml" +
255
+ " but Ruhoh::Resources::#{klass} does not exist."
256
+ }
257
+ abort
258
+ end
259
+ else
260
+ if @ruhoh.collections.registered.include?(resource_name)
261
+ Ruhoh::Resources.const_get(camelize(resource_name))
262
+ else
263
+ Ruhoh::Resources.const_get(:Pages)
264
+ end
265
+ end
266
+ end
267
+
268
+ def camelize(name)
269
+ name.to_s.split('_').map { |a| a.capitalize }.join
270
+ end
271
+ end
272
+
273
+ # Generic base implementation of a Collection class.
274
+ # All collections use this class by default
275
+ # unless the Collection class is explicitly defined for the resource.
276
+ class Collection
277
+ include Collectable
278
+ end
279
+
280
+ end