ruhoh 1.1 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
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