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
@@ -0,0 +1,45 @@
1
+ require 'directory_watcher'
2
+
3
+ class Ruhoh
4
+ module Program
5
+
6
+ # Internal: Watch website source directory for file changes.
7
+ # The observer triggers data regeneration as files change
8
+ # in order to keep the data up to date in real time.
9
+ def self.watch(ruhoh)
10
+ ruhoh.ensure_setup
11
+
12
+ Ruhoh::Friend.say {
13
+ cyan "=> Start watching: #{ruhoh.paths.base}"
14
+ }
15
+ dw = DirectoryWatcher.new(ruhoh.paths.base, {
16
+ :glob => "**/*",
17
+ :pre_load => true
18
+ })
19
+ dw.interval = 1
20
+ dw.add_observer do |*args|
21
+ args.each do |event|
22
+ ruhoh.cache.delete(event['path'])
23
+
24
+ path = event['path'].gsub(ruhoh.paths.base + '/', '')
25
+
26
+ Ruhoh::Friend.say {
27
+ yellow "Watch [#{Time.now.strftime("%H:%M:%S")}] [Update #{path}] : #{args.size} files changed"
28
+ }
29
+
30
+ separator = File::ALT_SEPARATOR ?
31
+ %r{#{ File::SEPARATOR }|#{ File::ALT_SEPARATOR }} :
32
+ File::SEPARATOR
33
+ resource = path.split(separator)[0]
34
+
35
+ ruhoh.cache.delete(ruhoh.collection(resource).files_cache_key)
36
+
37
+ ruhoh.collection(resource).load_watcher.update(path)
38
+ end
39
+ end
40
+
41
+ dw.start
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ module Ruhoh::Resources::Dash
2
+ class Collection
3
+ include Ruhoh::Base::Collectable
4
+
5
+ def url_endpoint
6
+ "/dash"
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Ruhoh::Resources::Dash
2
+ class Model
3
+ include Ruhoh::Base::PageLike
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Ruhoh::Resources::Dash
2
+ class ModelView < SimpleDelegator
3
+ include Ruhoh::Base::PageViewable
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ module Ruhoh::Resources::Dash
2
+ class Previewer
3
+ def initialize(ruhoh)
4
+ @ruhoh = ruhoh
5
+ end
6
+
7
+ def call(env)
8
+ pointer = @ruhoh.collection("dash").find_file('index')
9
+ view = @ruhoh.master_view(pointer)
10
+ [200, {'Content-Type' => 'text/html'}, [view.render_full]]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Ruhoh::Resources::Data
2
+ class Collection
3
+ include Ruhoh::Base::Collectable
4
+
5
+ def dictionary
6
+ Ruhoh::Utils.parse_yaml_file(@ruhoh.paths.base, "#{resource_name}.yml") || {}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Ruhoh::Resources::Data
2
+ class CollectionView < SimpleDelegator
3
+
4
+ def initialize(collection)
5
+ super(collection)
6
+
7
+ # Define direct access to the dictionary Hash object
8
+ # but don't overwrite methods if already defined.
9
+ dictionary.keys.each do |method|
10
+ (class << self; self; end).class_eval do
11
+ next if method_defined?(method)
12
+ define_method method do |*args, &block|
13
+ dictionary[method]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def [](attribute)
20
+ __send__(attribute)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Ruhoh::Resources::Javascripts
2
+ class Collection
3
+ include Ruhoh::Base::Collectable
4
+
5
+ def url_endpoint
6
+ "assets/#{ resource_name }"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module Ruhoh::Resources::Javascripts
2
+ class CollectionView < SimpleDelegator
3
+ attr_accessor :_cache
4
+
5
+ def initialize(collection)
6
+ super(collection)
7
+ @_cache = {}
8
+ end
9
+
10
+ # Load javascripts as defined within the given sub_context
11
+ #
12
+ # Example:
13
+ # {{# javascripts.load }}
14
+ # app.js
15
+ # scroll.js
16
+ # {{/ javascripts.load }}
17
+ # (scripts are separated by newlines)
18
+ #
19
+ # This is a convenience method that will automatically create script tags
20
+ # with respect to ruhoh's internal URL generation mechanism; e.g. base_path.
21
+ #
22
+ # @returns[String] HTML script tags for given javascripts.
23
+ def load(sub_context)
24
+ javascripts = sub_context.split("\n").map{ |s| s.gsub(/\s/, '') }.delete_if(&:empty?)
25
+ javascripts.map { |name|
26
+ "<script src='#{make_url(name)}'></script>"
27
+ }.join("\n")
28
+ end
29
+
30
+ protected
31
+
32
+ def make_url(name)
33
+ return name if name =~ /^(http:|https:)?\/\//i
34
+
35
+ path = if @_cache[name]
36
+ @_cache[name]
37
+ else
38
+ @_cache[name] = name
39
+ "#{name}?#{rand()}"
40
+ end
41
+
42
+ ruhoh.to_url(url_endpoint, path)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module Ruhoh::Resources::Javascripts
2
+ class Compiler
3
+ include Ruhoh::Base::CompilableAsset
4
+ end
5
+ end
@@ -0,0 +1,45 @@
1
+ module Ruhoh::Resources::Layouts
2
+ class Client
3
+ Help = [
4
+ {
5
+ "command" => "new <name>",
6
+ "desc" => "Create a new layout for the currently active theme."
7
+ }
8
+ ]
9
+
10
+ def initialize(collection, data)
11
+ @ruhoh = collection.ruhoh
12
+ @collection = collection
13
+ @args = data[:args]
14
+ @options = data[:options]
15
+ end
16
+
17
+ # Public: Create a new layout file for the active theme.
18
+ def new
19
+ ruhoh = @ruhoh
20
+ name = @args[2]
21
+ Ruhoh::Friend.say {
22
+ red "Please specify a layout name."
23
+ cyan "ex: ruhoh layouts new splash"
24
+ exit
25
+ } if name.nil?
26
+
27
+ filename = File.join((@ruhoh.paths.theme || @ruhoh.paths.base), "layouts", name.gsub(/\s/, '-').downcase) + ".html"
28
+
29
+ if File.exist?(filename)
30
+ abort("Create new layout: aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n'
31
+ end
32
+
33
+ FileUtils.mkdir_p File.dirname(filename)
34
+
35
+ File.open(filename, 'w:UTF-8') do |page|
36
+ page.puts (@collection.scaffold || '')
37
+ end
38
+
39
+ Ruhoh::Friend.say {
40
+ green "New layout:"
41
+ plain ruhoh.relative_path(filename)
42
+ }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ module Ruhoh::Resources::Layouts
2
+ class Model
3
+ include Ruhoh::Base::PageLike
4
+
5
+ def process
6
+ parsed_page = parse_page_file
7
+ # This ensures the call to @sub_layout.layout does not error.
8
+ parsed_page['data']['layout'] ||= nil
9
+
10
+ changed
11
+ notify_observers(parsed_page)
12
+
13
+ parsed_page
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Ruhoh::Resources::Media
2
+ class Collection
3
+ include Ruhoh::Base::Collectable
4
+
5
+ def url_endpoint
6
+ "/assets/media"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ module Ruhoh::Resources::Media
2
+ class Compiler
3
+ include Ruhoh::Base::Compilable
4
+
5
+ # TODO: Use the asset compiler.
6
+ # We can't use it now because there is automatic digest support
7
+ # but currently no way to dynamically update all media links in views with digest path.
8
+ def run
9
+ collection = @collection
10
+ unless @collection.paths?
11
+ Ruhoh::Friend.say { yellow "#{collection.resource_name.capitalize}: directory not found - skipping." }
12
+ return
13
+ end
14
+ Ruhoh::Friend.say { cyan "#{collection.resource_name.capitalize}: (copying valid files)" }
15
+
16
+ compiled_path = Ruhoh::Utils.url_to_path(@ruhoh.to_url(@collection.url_endpoint), @ruhoh.paths.compiled)
17
+ FileUtils.mkdir_p compiled_path
18
+
19
+ @collection.files.values.each do |pointer|
20
+ compiled_file = File.join(compiled_path, pointer['id'])
21
+ FileUtils.mkdir_p File.dirname(compiled_file)
22
+ FileUtils.cp_r pointer['realpath'], compiled_file
23
+ Ruhoh::Friend.say { green " > #{pointer['id']}" }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,124 @@
1
+ module Ruhoh::Resources::Pages
2
+ class Client
3
+ Help = [
4
+ {
5
+ "command" => "draft <title>",
6
+ "desc" => "Create a new draft. Title is optional.",
7
+ },
8
+ {
9
+ "command" => "new <title>",
10
+ "desc" => "Create a new resource. Title is optional.",
11
+ },
12
+ {
13
+ "command" => "titleize",
14
+ "desc" => "Update draft filenames to their corresponding titles. Drafts without titles are ignored.",
15
+ },
16
+ {
17
+ "command" => "drafts",
18
+ "desc" => "List all drafts.",
19
+ },
20
+ {
21
+ "command" => "list",
22
+ "desc" => "List all resources.",
23
+ }
24
+ ]
25
+
26
+ def initialize(collection, data)
27
+ @ruhoh = collection.ruhoh
28
+ @collection = collection
29
+ @args = data[:args]
30
+ @options = data[:options]
31
+ @iterator = 0
32
+ end
33
+
34
+ def draft
35
+ create(draft: true)
36
+ end
37
+
38
+ def new
39
+ create
40
+ end
41
+
42
+ # Public: Update draft filenames to their corresponding titles.
43
+ def titleize
44
+ @collection.dictionary.each do |id, data|
45
+ next unless File.basename(data['id']) =~ /^untitled/
46
+ new_name = Ruhoh::Utils.to_slug(data['title'])
47
+ new_file = "#{new_name}#{File.extname(data['id'])}"
48
+ old_file = File.basename(data['id'])
49
+ next if old_file == new_file
50
+
51
+ FileUtils.cd(File.dirname(data['pointer']['realpath'])) {
52
+ FileUtils.mv(old_file, new_file)
53
+ }
54
+ Ruhoh::Friend.say { green "Renamed #{old_file} to: #{new_file}" }
55
+ end
56
+ end
57
+
58
+ def drafts
59
+ _list(@collection.drafts)
60
+ end
61
+
62
+ def list
63
+ _list(@collection.all)
64
+ end
65
+
66
+ protected
67
+
68
+ def create(opts={})
69
+ ruhoh = @ruhoh
70
+
71
+ begin
72
+ file = @args[2] || "untitled"
73
+ ext = File.extname(file).to_s
74
+ ext = ext.empty? ? @collection.config["ext"] : ext
75
+
76
+ # filepath vs title
77
+ name = if file.include?('/')
78
+ name = File.basename(file, ext).gsub(/\s+/, '-')
79
+ File.join(File.dirname(file), name)
80
+ else
81
+ Ruhoh::Utils.to_slug(File.basename(file, ext))
82
+ end
83
+
84
+ name = "#{name}-#{@iterator}" unless @iterator.zero?
85
+ filename = opts[:draft] ?
86
+ File.join(@ruhoh.paths.base, @collection.resource_name, "drafts", "#{name}#{ext}") :
87
+ File.join(@ruhoh.paths.base, @collection.resource_name, "#{name}#{ext}")
88
+ @iterator += 1
89
+ end while File.exist?(filename)
90
+
91
+ FileUtils.mkdir_p File.dirname(filename)
92
+ output = (@collection.scaffold || '').gsub('{{DATE}}', Time.now.strftime('%Y-%m-%d'))
93
+
94
+ File.open(filename, 'w:UTF-8') {|f| f.puts output }
95
+
96
+ resource_name = @collection.resource_name
97
+ Ruhoh::Friend.say {
98
+ green "New #{resource_name}:"
99
+ green " > #{ruhoh.relative_path(filename)}"
100
+ if opts[:draft]
101
+ plain "View drafts at the URL: /dash"
102
+ end
103
+ }
104
+ end
105
+
106
+ def _list(data)
107
+ if @options.verbose
108
+ Ruhoh::Friend.say {
109
+ data.each_value do |p|
110
+ cyan("- #{p['id']}")
111
+ plain(" title: #{p['title']}")
112
+ plain(" url: #{p['url']}")
113
+ end
114
+ }
115
+ else
116
+ Ruhoh::Friend.say {
117
+ data.each do |p|
118
+ cyan("- #{p['id']}")
119
+ end
120
+ }
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,86 @@
1
+ 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
+ class Collection
23
+ include Ruhoh::Base::Collectable
24
+ include Routable
25
+
26
+ # model observer callback.
27
+ def update(model_data)
28
+ routes_add(model_data['data']['url'], model_data['data']['pointer'])
29
+ @ruhoh.cache.set(model_data['data']['pointer']['realpath'], model_data)
30
+ end
31
+
32
+ # Easy way to regenerate a model
33
+ # Used in the file watcher implementation.
34
+ def touch(name_or_pointer)
35
+ pointer = find_file(name_or_pointer)
36
+ routes_delete(pointer)
37
+ find(name_or_pointer) # find/load so the route is regenerated
38
+ end
39
+
40
+ def config
41
+ hash = super
42
+ hash['permalink'] ||= "/:path/:filename"
43
+ hash['summary_lines'] ||= 20
44
+ hash['summary_lines'] = hash['summary_lines'].to_i
45
+ hash['latest'] ||= 2
46
+ hash['latest'] = hash['latest'].to_i
47
+ hash['ext'] ||= ".md"
48
+
49
+ rss = hash['rss'] || {}
50
+ rss['limit'] ||= 20
51
+ rss['limit'] = rss['limit'].to_i
52
+ rss["url"] ||= "/#{ resource_name }"
53
+ rss["url"] = rss["url"].to_s
54
+ rss["url"] = "/#{ rss["url"] }" unless rss["url"].start_with?('/')
55
+ rss["url"] = rss["url"].chomp('/') unless rss["url"] == '/'
56
+ hash['rss'] = rss
57
+
58
+ paginator = hash['paginator'] || {}
59
+ paginator["url"] ||= "/#{ resource_name }/index"
60
+ paginator["url"] = paginator["url"].to_s
61
+ unless paginator["url"].start_with?('/')
62
+ paginator["url"] = "/#{paginator["url"]}"
63
+ end
64
+ unless paginator["url"] == '/'
65
+ paginator["url"] = paginator["url"].chomp('/')
66
+ end
67
+
68
+ paginator["per_page"] ||= 5
69
+ paginator["per_page"] = paginator["per_page"].to_i
70
+ paginator["layout"] ||= "paginator"
71
+
72
+ if paginator["root_page"]
73
+ unless paginator["root_page"].start_with?('/')
74
+ paginator["root_page"] = "/#{paginator["root_page"]}"
75
+ end
76
+ unless paginator["root_page"] == '/'
77
+ paginator["root_page"] = paginator["root_page"].chomp('/')
78
+ end
79
+ end
80
+
81
+ hash['paginator'] = paginator
82
+
83
+ hash
84
+ end
85
+ end
86
+ end