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
@@ -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