ruhoh 2.5 → 2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/Gemfile +1 -1
  2. data/bin/ruhoh +10 -3
  3. data/features/_root.feature +11 -0
  4. data/features/data.feature +78 -0
  5. data/features/javascripts.feature +36 -0
  6. data/features/permalinks.feature +23 -0
  7. data/features/plugins.feature +84 -0
  8. data/features/sort_order.feature +121 -0
  9. data/features/step_defs.rb +3 -3
  10. data/features/support/helpers.rb +3 -5
  11. data/history.json +21 -0
  12. data/lib/ruhoh.rb +28 -123
  13. data/lib/ruhoh/base/collectable.rb +273 -0
  14. data/lib/ruhoh/base/compilable.rb +30 -0
  15. data/lib/ruhoh/base/compilable_asset.rb +30 -0
  16. data/lib/ruhoh/base/model_viewable.rb +30 -0
  17. data/lib/ruhoh/base/modelable.rb +44 -0
  18. data/lib/ruhoh/base/page_like.rb +111 -0
  19. data/lib/ruhoh/base/page_viewable.rb +92 -0
  20. data/lib/ruhoh/base/routable.rb +20 -0
  21. data/lib/ruhoh/base/watchable.rb +18 -0
  22. data/lib/ruhoh/cascade.rb +93 -0
  23. data/lib/ruhoh/client.rb +1 -3
  24. data/lib/ruhoh/collections.rb +2 -1
  25. data/lib/ruhoh/config.rb +67 -0
  26. data/lib/ruhoh/console_methods.rb +0 -2
  27. data/lib/ruhoh/parse.rb +7 -5
  28. data/lib/ruhoh/plugins/initializer.rb +24 -0
  29. data/lib/ruhoh/plugins/local_plugins_plugin.rb +10 -0
  30. data/lib/ruhoh/plugins/plugin.rb +27 -0
  31. data/lib/ruhoh/programs/compile.rb +2 -6
  32. data/lib/ruhoh/programs/preview.rb +5 -2
  33. data/lib/ruhoh/programs/watch.rb +4 -6
  34. data/lib/ruhoh/publish/rsync.rb +2 -2
  35. data/lib/ruhoh/resources/_base/collection.rb +6 -0
  36. data/lib/ruhoh/resources/_base/compiler.rb +3 -0
  37. data/lib/ruhoh/resources/_base/model.rb +3 -0
  38. data/lib/ruhoh/resources/_base/model_view.rb +3 -0
  39. data/lib/ruhoh/resources/_base/watcher.rb +4 -0
  40. data/lib/ruhoh/resources/data/collection.rb +30 -9
  41. data/lib/ruhoh/resources/javascripts/collection_view.rb +5 -1
  42. data/lib/ruhoh/resources/javascripts/model_view.rb +15 -0
  43. data/lib/ruhoh/resources/layouts/client.rb +1 -1
  44. data/lib/ruhoh/resources/pages/client.rb +2 -2
  45. data/lib/ruhoh/resources/pages/collection.rb +2 -21
  46. data/lib/ruhoh/resources/theme/compiler.rb +2 -2
  47. data/lib/ruhoh/resources/widgets/collection.rb +2 -2
  48. data/lib/ruhoh/routes.rb +1 -1
  49. data/lib/ruhoh/summarizer.rb +2 -2
  50. data/lib/ruhoh/ui/dashboard.rb +13 -0
  51. data/lib/ruhoh/ui/page_not_found.rb +3 -2
  52. data/lib/ruhoh/url_slug.rb +23 -9
  53. data/lib/ruhoh/version.rb +1 -1
  54. data/lib/ruhoh/views/master_view.rb +1 -1
  55. data/spec/lib/ruhoh/plugins/initializer_spec.rb +43 -0
  56. data/spec/lib/ruhoh/plugins/plugin_spec.rb +40 -0
  57. data/spec/spec_helper.rb +1 -0
  58. data/system/config.json +21 -0
  59. data/system/{dash/index.html → dashboard.html} +1 -1
  60. data/{lib/ruhoh/ui → system}/page_not_found.html +0 -0
  61. data/system/plugins/sprockets/compiler.rb +1 -0
  62. data/system/widgets/comments/disqus.html +1 -1
  63. metadata +34 -15
  64. data/lib/ruhoh/base/collection.rb +0 -284
  65. data/lib/ruhoh/base/compiler.rb +0 -67
  66. data/lib/ruhoh/base/model.rb +0 -161
  67. data/lib/ruhoh/base/model_view.rb +0 -129
  68. data/lib/ruhoh/base/watcher.rb +0 -25
  69. data/lib/ruhoh/resources/dash/collection.rb +0 -10
  70. data/lib/ruhoh/resources/dash/model.rb +0 -5
  71. data/lib/ruhoh/resources/dash/model_view.rb +0 -5
  72. data/lib/ruhoh/resources/dash/previewer.rb +0 -13
@@ -40,8 +40,6 @@ class Ruhoh
40
40
  return server if %w(s serve server).include?(cmd)
41
41
 
42
42
  @ruhoh = Ruhoh.new
43
- @ruhoh.setup
44
- @ruhoh.setup_paths
45
43
  @ruhoh.setup_plugins
46
44
 
47
45
  return __send__(cmd) if respond_to?(cmd)
@@ -136,7 +134,7 @@ class Ruhoh
136
134
  end
137
135
 
138
136
  if Ruhoh::Publish.const_defined?(service.to_sym)
139
- publish_config = Ruhoh::Parse.data_file(@ruhoh.base, "publish") || {}
137
+ publish_config = Ruhoh::Parse.data_file(@ruhoh.cascade.base, "publish") || {}
140
138
  Ruhoh::Publish.const_get(service.to_sym).new.run(@args, publish_config[service.downcase])
141
139
  else
142
140
  Ruhoh::Friend.say {
@@ -1,3 +1,4 @@
1
+ module Ruhoh::Base ; end
1
2
  module Ruhoh::Resources ; end
2
3
  # Require all the resources
3
4
  FileUtils.cd(File.join(File.dirname(__FILE__), 'base')) do
@@ -71,7 +72,7 @@ class Ruhoh
71
72
  def discover
72
73
  results = Set.new
73
74
 
74
- @ruhoh.cascade.each do |h|
75
+ @ruhoh.cascade.paths.each do |h|
75
76
  FileUtils.cd(h["path"]) do
76
77
  results += Dir['*'].select { |x|
77
78
  File.directory?(x) && !["plugins", 'compiled'].include?(x)
@@ -0,0 +1,67 @@
1
+ class Ruhoh
2
+ class Config < SimpleDelegator
3
+ include Observable
4
+
5
+ def initialize(ruhoh)
6
+ @ruhoh = ruhoh
7
+ @data = {}
8
+ super(@data)
9
+ end
10
+
11
+ # Regenerate the config data
12
+ def touch
13
+ data = @ruhoh.cascade.merge_data_file('config') || {}
14
+ data = Ruhoh::Utils.deep_merge(data, collections_config)
15
+ data = Ruhoh::Utils.deep_merge(data, find_theme_path)
16
+
17
+ @data.clear
18
+ @data.merge!(data)
19
+
20
+ Time.default_format = @data['date_format']
21
+ @data["compiled_path"] = File.expand_path(@data["compiled_path"])
22
+
23
+ changed
24
+ notify_observers(@data)
25
+
26
+ self
27
+ end
28
+
29
+ def base_path
30
+ return '/' unless (@ruhoh.env == 'production')
31
+
32
+ @data['base_path'] += "/" unless @data['base_path'][-1] == '/'
33
+ string = @data['base_path'].chomp('/').reverse.chomp('/').reverse
34
+ return '/' if string.empty? || string == '/'
35
+ "/#{ string }/"
36
+ end
37
+
38
+ private
39
+
40
+ def find_theme_path
41
+ theme_name = @data.find { |resource, data| data.is_a?(Hash) && data['use'] == "theme" }
42
+ if theme_name
43
+ Ruhoh::Friend.say { plain "Using theme: \"#{theme_name[0]}\""}
44
+ { "_theme_collection" => theme_name[0] }
45
+ else
46
+ { "_theme_collection" => nil }
47
+ end
48
+ end
49
+
50
+ # Quick and dirty way to scan for config files in collection folders.
51
+ # This is needed because we don't know which collection defines itself as a theme
52
+ # so we'll scan for any configs and merge the data to find the theme folder.
53
+ def collections_config
54
+ data = {}
55
+ @ruhoh.cascade.paths.map{ |a| a['path'] }.each do |path|
56
+ FileUtils.cd(path) {
57
+ Dir["*/config.*"].each { |id|
58
+ next unless File.exist?(id) && FileTest.file?(id)
59
+ data = Ruhoh::Utils.deep_merge(data, (Ruhoh::Parse.data_file(File.realpath(id)) || {}))
60
+ }
61
+ }
62
+ end
63
+
64
+ data
65
+ end
66
+ end
67
+ end
@@ -7,9 +7,7 @@ class Ruhoh
7
7
  def ruhoh
8
8
  return @ruhoh if @ruhoh
9
9
  @ruhoh = Ruhoh.new
10
- @ruhoh.setup
11
10
  @ruhoh.env = ConsoleMethods.env || 'development'
12
- @ruhoh.setup_paths
13
11
  @ruhoh
14
12
  end
15
13
 
@@ -42,13 +42,15 @@ class Ruhoh
42
42
  end
43
43
 
44
44
  def self.data_file(*args)
45
- base = File.__send__(:join, args)
45
+ filepath = File.__send__(:join, args)
46
+ if File.extname(filepath).to_s.empty?
47
+ path = nil
48
+ ["#{ filepath }.json", "#{ filepath }.yml", "#{ filepath }.yaml"].each do |result|
49
+ filepath = path = result and break if File.exist?(result)
50
+ end
46
51
 
47
- filepath = nil
48
- ["#{ base }.json", "#{ base }.yml", "#{ base }.yaml"].each do |result|
49
- filepath = result and break if File.exist?(result)
52
+ return nil unless path
50
53
  end
51
- return nil unless filepath
52
54
 
53
55
  file = File.open(filepath, 'r:UTF-8') { |f| f.read }
54
56
 
@@ -0,0 +1,24 @@
1
+ module Ruhoh::Plugins
2
+ class Initializer
3
+ attr_reader :name
4
+
5
+ def initialize name, &block
6
+ raise ArgumentError, "block required for initializer '#{name}'" unless block_given?
7
+ @name, @block = name, block
8
+ end
9
+
10
+ def run *args
11
+ raise "Initializer '#{name}' need to be bound" unless context
12
+ context.instance_exec *args, &block
13
+ end
14
+
15
+ def bind ctx
16
+ @context = ctx
17
+ self
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :block, :context
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module Ruhoh::Plugins
2
+ class LocalPluginsPlugin
3
+ include Plugin
4
+
5
+ initializer 'ruhoh.local_plugins' do
6
+ plugins = Dir[File.join(@base, "plugins", "**/*.rb")]
7
+ plugins.each { |f| require f }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ require 'ruhoh/plugins/initializer'
2
+
3
+ module Ruhoh::Plugins
4
+ module Plugin
5
+ def self.included base
6
+ base.send :extend, ClassMethods
7
+ end
8
+
9
+ def self.run_all(context, *args)
10
+ initializers.each do |i|
11
+ i.bind(context).run *args
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def self.initializers
18
+ @initializers ||= []
19
+ end
20
+
21
+ module ClassMethods
22
+ def initializer name, &block
23
+ Plugin.initializers << Initializer.new(name, &block)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,15 +5,11 @@ class Ruhoh
5
5
  # to properly omit drafts and other development-only settings.
6
6
  def self.compile(target=nil)
7
7
  ruhoh = Ruhoh.new
8
- ruhoh.setup
9
8
  ruhoh.env = 'production'
10
- ruhoh.setup_paths
11
9
  ruhoh.setup_plugins
12
-
10
+
13
11
  if target
14
- ruhoh.paths.compiled = File.expand_path(target)
15
- elsif ruhoh.config["compiled"]
16
- ruhoh.paths.compiled = ruhoh.config["compiled"]
12
+ ruhoh.config['compiled_path'] = File.expand_path(target)
17
13
  end
18
14
 
19
15
  ruhoh.compile
@@ -1,4 +1,5 @@
1
1
  require 'ruhoh/programs/watch'
2
+ require 'ruhoh/ui/dashboard'
2
3
  class Ruhoh
3
4
  module Program
4
5
  # Public: A program for running ruhoh as a rack application
@@ -17,9 +18,7 @@ class Ruhoh
17
18
  opts[:env] ||= 'development'
18
19
 
19
20
  ruhoh = Ruhoh.new
20
- ruhoh.setup
21
21
  ruhoh.env = opts[:env]
22
- ruhoh.setup_paths
23
22
  ruhoh.setup_plugins unless opts[:enable_plugins] == false
24
23
 
25
24
  # initialize the routes dictionary for all page resources.
@@ -52,6 +51,10 @@ class Ruhoh
52
51
  end
53
52
  end
54
53
 
54
+ map '/dash' do
55
+ run Ruhoh::UI::Dashboard.new(ruhoh)
56
+ end
57
+
55
58
  # The generic Page::Previewer is used to render any/all page-like resources,
56
59
  # since they likely have arbitrary urls based on permalink settings.
57
60
  map '/' do
@@ -7,12 +7,10 @@ class Ruhoh
7
7
  # The observer triggers data regeneration as files change
8
8
  # in order to keep the data up to date in real time.
9
9
  def self.watch(ruhoh)
10
- ruhoh.ensure_setup
11
-
12
10
  Ruhoh::Friend.say {
13
- cyan "=> Start watching: #{ruhoh.paths.base}"
11
+ cyan "=> Start watching: #{ruhoh.cascade.base}"
14
12
  }
15
- dw = DirectoryWatcher.new(ruhoh.paths.base, {
13
+ dw = DirectoryWatcher.new(ruhoh.cascade.base, {
16
14
  :glob => "**/*",
17
15
  :pre_load => true
18
16
  })
@@ -21,14 +19,14 @@ class Ruhoh
21
19
  args.each do |event|
22
20
  ruhoh.cache.delete(event['path'])
23
21
 
24
- path = event['path'].gsub(ruhoh.paths.base + '/', '')
22
+ path = event['path'].gsub(ruhoh.cascade.base + '/', '')
25
23
 
26
24
  Ruhoh::Friend.say {
27
25
  yellow "Watch [#{Time.now.strftime("%H:%M:%S")}] [Update #{path}] : #{args.size} files changed"
28
26
  }
29
27
 
30
28
  if %w{ config.json config.yml config.yaml }.include?(path)
31
- ruhoh.config true
29
+ ruhoh.config.touch
32
30
  else
33
31
  separator = File::ALT_SEPARATOR ?
34
32
  %r{#{ File::SEPARATOR }|#{ File::ALT_SEPARATOR }} :
@@ -22,9 +22,9 @@ class Ruhoh
22
22
  if @config["command"]
23
23
  system(@config["command"])
24
24
  else
25
- system('rsync', File.join(ruhoh.paths.compiled, '.'), '-avz', '--delete', '--exclude', '.git', remote)
25
+ system('rsync', File.join(ruhoh.config['compiled_path'], '.'), '-avz', '--delete', '--exclude', '.git', remote)
26
26
  end
27
- FileUtils.rm_r(ruhoh.paths.compiled)
27
+ FileUtils.rm_r(ruhoh.config['compiled_path'])
28
28
  end
29
29
 
30
30
  private
@@ -0,0 +1,6 @@
1
+ # Generic base implementation of a Collection class.
2
+ # All collections use this class by default
3
+ # unless the Collection class is explicitly defined for the collection.
4
+ class Ruhoh::Base::Collection
5
+ include Ruhoh::Base::Collectable
6
+ end
@@ -0,0 +1,3 @@
1
+ class Ruhoh::Base::Compiler
2
+ include Ruhoh::Base::Compilable
3
+ end
@@ -0,0 +1,3 @@
1
+ class Ruhoh::Base::Model
2
+ include Ruhoh::Base::Modelable
3
+ end
@@ -0,0 +1,3 @@
1
+ class Ruhoh::Base::ModelView < SimpleDelegator
2
+ include Ruhoh::Base::ModelViewable
3
+ end
@@ -0,0 +1,4 @@
1
+ # Base watcher class that loads if no custom Watcher class is defined.
2
+ class Ruhoh::Base::Watcher
3
+ include Ruhoh::Base::Watchable
4
+ end
@@ -2,24 +2,45 @@ module Ruhoh::Resources::Data
2
2
  class Collection
3
3
  include Ruhoh::Base::Collectable
4
4
 
5
+ def glob
6
+ "*"
7
+ end
8
+
9
+ def dictionary
10
+ resource_name == "data" ?
11
+ _support_legacy_api :
12
+ _support_new_data_api
13
+ end
14
+
15
+ private
16
+
17
+ def _support_new_data_api
18
+ data = {}
19
+ files.values.each do |pointer|
20
+ name = File.basename(pointer["id"], File.extname(pointer["id"]))
21
+ data[name] = Ruhoh::Parse.data_file(pointer["realpath"]) || {}
22
+ end
23
+
24
+ data
25
+ end
26
+
5
27
  # TODO: This is ugly but it works.
6
28
  # Should handle data extensions in the cascade more elegantly
7
- def dictionary
8
- found_path_prefix = nil
29
+ def _support_legacy_api
30
+ found_paths = []
9
31
 
10
- @ruhoh.cascade.reverse.map do |h|
32
+ @ruhoh.cascade.paths.each do |h|
11
33
  path_prefix = File.join(h["path"], resource_name)
12
34
 
13
35
  ["#{ path_prefix }.json", "#{ path_prefix }.yml", "#{ path_prefix }.yaml"].each do |file|
14
- found_path_prefix = path_prefix and break if File.exist?(file)
36
+ found_paths << path_prefix and break if File.exist?(file)
15
37
  end
16
-
17
- break if found_path_prefix
18
38
  end
19
39
 
20
- return {} unless found_path_prefix
40
+ data = {}
41
+ found_paths.each { |path| data.merge!(Ruhoh::Parse.data_file(path) || {}) }
21
42
 
22
- Ruhoh::Parse.data_file(found_path_prefix) || {}
43
+ data
23
44
  end
24
45
  end
25
- end
46
+ end
@@ -27,7 +27,11 @@ module Ruhoh::Resources::Javascripts
27
27
  }.join("\n")
28
28
  end
29
29
 
30
- protected
30
+ def all()
31
+ files.values.map { |pointer|
32
+ load_model_view(pointer)
33
+ }
34
+ end
31
35
 
32
36
  def make_url(name)
33
37
  return name if name =~ /^(http:|https:)?\/\//i
@@ -0,0 +1,15 @@
1
+ module Ruhoh::Resources::Javascripts
2
+ class ModelView < SimpleDelegator
3
+ def url()
4
+ self.collection.make_url(self.pointer['id'])
5
+ end
6
+
7
+ def id()
8
+ self.pointer['id']
9
+ end
10
+
11
+ def path()
12
+ self.pointer['realpath']
13
+ end
14
+ end
15
+ end
@@ -24,7 +24,7 @@ module Ruhoh::Resources::Layouts
24
24
  exit
25
25
  } if name.nil?
26
26
 
27
- filename = File.join((@ruhoh.paths.theme || @ruhoh.paths.base), "layouts", name.gsub(/\s/, '-').downcase) + ".html"
27
+ filename = File.join((@ruhoh.cascade.theme || @ruhoh.cascade.base), "layouts", name.gsub(/\s/, '-').downcase) + ".html"
28
28
 
29
29
  if File.exist?(filename)
30
30
  abort("Create new layout: aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n'
@@ -105,8 +105,8 @@ module Ruhoh::Resources::Pages
105
105
 
106
106
  name = "#{name}-#{@iterator}" unless @iterator.zero?
107
107
  filename = opts[:draft] ?
108
- File.join(@ruhoh.paths.base, @collection.resource_name, "drafts", "#{name}#{ext}") :
109
- File.join(@ruhoh.paths.base, @collection.resource_name, "#{name}#{ext}")
108
+ File.join(@ruhoh.cascade.base, @collection.resource_name, "drafts", "#{name}#{ext}") :
109
+ File.join(@ruhoh.cascade.base, @collection.resource_name, "#{name}#{ext}")
110
110
  @iterator += 1
111
111
  end while File.exist?(filename)
112
112
 
@@ -1,27 +1,8 @@
1
+ require 'ruhoh/base/routable'
1
2
  module Ruhoh::Resources::Pages
2
- module Routable
3
- def routes
4
- return @routes if @routes
5
- @routes = {}
6
- dictionary
7
- @routes
8
- end
9
-
10
- def routes_add(route, pointer)
11
- @routes ||= {}
12
- @routes[route] = pointer
13
- end
14
-
15
- def routes_delete(pointer)
16
- return unless @routes
17
- route = @routes.find{ |k, v| v == pointer }
18
- @routes.delete(route[0]) if route
19
- end
20
- end
21
-
22
3
  class Collection
23
4
  include Ruhoh::Base::Collectable
24
- include Routable
5
+ include Ruhoh::Base::Routable
25
6
 
26
7
  # model observer callback.
27
8
  def update(model_data)