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.
- data/Gemfile +3 -3
- data/README.md +3 -2
- data/Rakefile +1 -22
- data/bin/ruhoh +1 -5
- data/history.json +16 -0
- data/lib/ruhoh.rb +229 -84
- data/lib/ruhoh/base/collection.rb +280 -0
- data/lib/ruhoh/base/compiler.rb +55 -0
- data/lib/ruhoh/base/model.rb +220 -0
- data/lib/ruhoh/base/model_view.rb +152 -0
- data/lib/ruhoh/base/watcher.rb +25 -0
- data/lib/ruhoh/cache.rb +46 -0
- data/lib/ruhoh/client.rb +162 -0
- data/lib/ruhoh/collections.rb +172 -0
- data/lib/ruhoh/console_methods.rb +21 -0
- data/lib/ruhoh/{converters/converter.rb → converter.rb} +4 -1
- data/lib/ruhoh/programs/compile.rb +22 -0
- data/lib/ruhoh/programs/preview.rb +63 -0
- data/lib/ruhoh/programs/watch.rb +45 -0
- data/lib/ruhoh/resources/dash/collection.rb +10 -0
- data/lib/ruhoh/resources/dash/model.rb +5 -0
- data/lib/ruhoh/resources/dash/model_view.rb +5 -0
- data/lib/ruhoh/resources/dash/previewer.rb +13 -0
- data/lib/ruhoh/resources/data/collection.rb +9 -0
- data/lib/ruhoh/resources/data/collection_view.rb +23 -0
- data/lib/ruhoh/resources/javascripts/collection.rb +9 -0
- data/lib/ruhoh/resources/javascripts/collection_view.rb +46 -0
- data/lib/ruhoh/resources/javascripts/compiler.rb +5 -0
- data/lib/ruhoh/resources/layouts/client.rb +45 -0
- data/lib/ruhoh/resources/layouts/model.rb +16 -0
- data/lib/ruhoh/resources/media/collection.rb +9 -0
- data/lib/ruhoh/resources/media/compiler.rb +27 -0
- data/lib/ruhoh/resources/pages/client.rb +124 -0
- data/lib/ruhoh/resources/pages/collection.rb +86 -0
- data/lib/ruhoh/resources/pages/collection_view.rb +73 -0
- data/lib/ruhoh/resources/pages/compiler.rb +101 -0
- data/lib/ruhoh/resources/pages/model.rb +5 -0
- data/lib/ruhoh/resources/pages/model_view.rb +5 -0
- data/lib/ruhoh/resources/pages/previewer.rb +72 -0
- data/lib/ruhoh/resources/partials/model.rb +11 -0
- data/lib/ruhoh/resources/stylesheets/collection.rb +9 -0
- data/lib/ruhoh/resources/stylesheets/collection_view.rb +45 -0
- data/lib/ruhoh/resources/stylesheets/compiler.rb +5 -0
- data/lib/ruhoh/resources/theme/collection.rb +14 -0
- data/lib/ruhoh/resources/theme/compiler.rb +54 -0
- data/lib/ruhoh/resources/widgets/collection.rb +26 -0
- data/lib/ruhoh/resources/widgets/collection_view.rb +34 -0
- data/lib/ruhoh/resources/widgets/compiler.rb +27 -0
- data/lib/ruhoh/resources/widgets/model.rb +16 -0
- data/lib/ruhoh/routes.rb +29 -0
- data/lib/ruhoh/utils.rb +32 -49
- data/lib/ruhoh/version.rb +2 -2
- data/lib/ruhoh/views/helpers/categories.rb +38 -0
- data/lib/ruhoh/views/helpers/paginator.rb +39 -0
- data/lib/ruhoh/views/helpers/tags.rb +37 -0
- data/lib/ruhoh/views/master_view.rb +183 -0
- data/lib/ruhoh/views/rmustache.rb +24 -0
- data/ruhoh.gemspec +6 -82
- data/spec/spec_helper.rb +1 -1
- data/spec/support/shared_contexts.rb +6 -5
- data/system/{scaffolds/post.html → _scaffold.html} +1 -1
- data/system/{dash.html → dash/index.html} +37 -51
- data/system/{scaffolds/layout.html → layouts/_scaffold.html} +0 -0
- data/system/layouts/paginator.html +28 -0
- data/system/plugins/sprockets/javascripts/compiler.rb +25 -0
- data/system/plugins/sprockets/javascripts/previewer.rb +17 -0
- data/system/plugins/sprockets/stylesheets/compiler.rb +26 -0
- data/system/plugins/sprockets/stylesheets/previewer.rb +17 -0
- data/system/widgets/analytics/{layouts/getclicky.html → getclicky.html} +6 -2
- data/system/widgets/analytics/{layouts/google.html → google.html} +5 -1
- data/system/widgets/comments/{layouts/disqus.html → disqus.html} +6 -2
- data/system/widgets/comments/{layouts/facebook.html → facebook.html} +9 -2
- data/system/widgets/comments/{layouts/intensedebate.html → intensedebate.html} +5 -1
- data/system/widgets/comments/{layouts/livefyre.html → livefyre.html} +5 -1
- data/system/widgets/google_prettify/{layouts/google_prettify.html → default.html} +6 -2
- metadata +69 -66
- data/lib/ruhoh/client/client.rb +0 -306
- data/lib/ruhoh/client/console_methods.rb +0 -9
- data/lib/ruhoh/client/help.yml +0 -56
- data/lib/ruhoh/compiler.rb +0 -72
- data/lib/ruhoh/compilers/rss.rb +0 -39
- data/lib/ruhoh/compilers/theme.rb +0 -46
- data/lib/ruhoh/config.rb +0 -62
- data/lib/ruhoh/db.rb +0 -50
- data/lib/ruhoh/deployers/s3.rb +0 -71
- data/lib/ruhoh/page.rb +0 -106
- data/lib/ruhoh/parsers/javascripts.rb +0 -55
- data/lib/ruhoh/parsers/layouts.rb +0 -32
- data/lib/ruhoh/parsers/pages.rb +0 -79
- data/lib/ruhoh/parsers/partials.rb +0 -42
- data/lib/ruhoh/parsers/payload.rb +0 -49
- data/lib/ruhoh/parsers/posts.rb +0 -259
- data/lib/ruhoh/parsers/routes.rb +0 -20
- data/lib/ruhoh/parsers/scaffolds.rb +0 -35
- data/lib/ruhoh/parsers/site.rb +0 -19
- data/lib/ruhoh/parsers/stylesheets.rb +0 -63
- data/lib/ruhoh/parsers/theme_config.rb +0 -30
- data/lib/ruhoh/parsers/widgets.rb +0 -104
- data/lib/ruhoh/paths.rb +0 -83
- data/lib/ruhoh/previewer.rb +0 -48
- data/lib/ruhoh/program.rb +0 -68
- data/lib/ruhoh/templaters/asset_helpers.rb +0 -66
- data/lib/ruhoh/templaters/base_helpers.rb +0 -147
- data/lib/ruhoh/templaters/helpers.rb +0 -8
- data/lib/ruhoh/templaters/rmustache.rb +0 -70
- data/lib/ruhoh/urls.rb +0 -50
- data/lib/ruhoh/watch.rb +0 -78
- data/spec/config_spec.rb +0 -50
- data/spec/db_spec.rb +0 -91
- data/spec/page_spec.rb +0 -164
- data/spec/parsers/layouts_spec.rb +0 -41
- data/spec/parsers/pages_spec.rb +0 -120
- data/spec/parsers/posts_spec.rb +0 -309
- data/spec/parsers/routes_spec.rb +0 -39
- data/spec/parsers/site_spec.rb +0 -28
- data/spec/setup_spec.rb +0 -12
- data/system/scaffolds/draft.html +0 -9
- data/system/scaffolds/page.html +0 -4
- data/system/widgets/analytics/config.yml +0 -5
- data/system/widgets/comments/config.yml +0 -13
- 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"
|
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
|
+
gem 'rspec', "~> 2"
|
12
12
|
gem 'rake'
|
13
13
|
end
|
data/README.md
CHANGED
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
|
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
|
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
|
-
|
19
|
-
require 'ruhoh/
|
20
|
-
require 'ruhoh/
|
21
|
-
require 'ruhoh/
|
22
|
-
require 'ruhoh/
|
23
|
-
require 'ruhoh/
|
24
|
-
require 'ruhoh/
|
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 :
|
31
|
+
attr_reader :names, :root
|
34
32
|
end
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
65
|
-
self.
|
66
|
-
@
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
93
|
-
|
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
|
98
|
-
return if
|
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.
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
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
|