foxpage 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7eb6f55b4154a03fc2be9a3a623bedfbf03b4436fab184377b66d11310df899
4
- data.tar.gz: 20b6e0f2ee86a2064afc07eb84ac812347895db02638be7ce9441c16ae1c675c
3
+ metadata.gz: 98eb0f68d2163136732589c5da391975f8164e5c4e985dccd0bd24e893bc3045
4
+ data.tar.gz: e45d62a4c2a508010d066eab5fe822f8bc0a740c89b219a08d5a93e587b1ed8d
5
5
  SHA512:
6
- metadata.gz: a1ca9bb683c35b9b3bc661b288072a2cfe73d5e4eb01f517ea0e48cd0ee3fa1ccc31954c0028237cc490fb903ed227eadd01548edcf3e2161df40ff4ae768e2d
7
- data.tar.gz: ff733675525bf7fc4f31c593a6bc13670e71fea8349e7c1ae9ba06979e2476bff2104968990970be7c0aca03365dd965a0d0de80e2ca614992ab7d0c972c9754
6
+ metadata.gz: e6ae9e51e8c3defd140e169df2d9126e2227922af918dab4d4cdd69ed356214e680850f941f15d450b9656b130fc2a8ec03321864060f42eebe0d86dac33eeb3
7
+ data.tar.gz: 6dd3289ed9aba23e9b74f14fcdbc46d902f9ce4ed05c291b20707525b5061a6666ff2cd60f9dd15c238c8dfac866e4315a09dcb225ffa5b2d5672133e1024bf7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- foxpage (0.1.0)
4
+ foxpage (0.2.0)
5
5
  haml (~> 5.1)
6
6
  listen (~> 3.1)
7
7
  sassc (~> 2.0)
@@ -18,7 +18,7 @@ GEM
18
18
  concurrent-ruby (1.1.5)
19
19
  diff-lcs (1.3)
20
20
  ffi (1.11.1)
21
- haml (5.1.1)
21
+ haml (5.1.2)
22
22
  temple (>= 0.8.0)
23
23
  tilt
24
24
  jaro_winkler (1.5.2)
@@ -57,18 +57,17 @@ GEM
57
57
  unicode-display_width (>= 1.4.0, < 1.6)
58
58
  ruby-progressbar (1.10.1)
59
59
  ruby_dep (1.5.0)
60
- sassc (2.0.1)
60
+ sassc (2.2.1)
61
61
  ffi (~> 1.9)
62
- rake
63
- sprockets (4.0.0.beta9)
62
+ sprockets (4.0.0.beta10)
64
63
  concurrent-ruby (~> 1.0)
65
64
  rack (> 1, < 3)
66
- temple (0.8.1)
65
+ temple (0.8.2)
67
66
  thor (0.20.3)
68
- tilt (2.0.9)
67
+ tilt (2.0.10)
69
68
  unicode-display_width (1.5.0)
70
69
  webrick (1.4.2)
71
- zeitwerk (2.1.6)
70
+ zeitwerk (2.1.10)
72
71
 
73
72
  PLATFORMS
74
73
  ruby
@@ -25,6 +25,7 @@ module FoxPage
25
25
  # Set up application code loader
26
26
  @code_loader = Zeitwerk::Loader.new.tap do |loader|
27
27
  loader.push_dir(@root.join("app/controllers"))
28
+ loader.push_dir(@root.join("app/models"))
28
29
  loader.push_dir(@root.join("app/helpers"))
29
30
  loader.enable_reloading
30
31
  loader.setup
@@ -20,7 +20,7 @@ module FoxPage
20
20
 
21
21
  def image_assets
22
22
  image_assets_path = app.root.join("app/assets/images")
23
- Dir.glob("#{image_assets_path}/**/*.{png,jpg,gif,jpeg}")
23
+ Dir.glob("#{image_assets_path}/**/*.{png,jpg,gif,jpeg,svg}")
24
24
  .map { |full_path| full_path.sub(%r{\A#{image_assets_path}/}, "") }
25
25
  end
26
26
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoxPage
4
+ module Builders
5
+ module Models
6
+ def load_models
7
+ inject_app_to_models
8
+ end
9
+
10
+ private
11
+
12
+ def inject_app_to_models
13
+ return if already_injected?
14
+
15
+ FoxPage::Model.instance_variable_set(:@__app, app)
16
+ end
17
+
18
+ def already_injected?
19
+ FoxPage::Model.instance_variable_get(:@__app)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -7,36 +7,66 @@ module FoxPage
7
7
  module Builders
8
8
  module Pages
9
9
  using Refinements::Camelize
10
+ using Refinements::Constantize
10
11
 
11
12
  def build_pages
12
13
  app.routes.each do |path, route|
13
- puts "PAGE\t#{path} => #{route.base_name}"
14
+ if route.generate_all
15
+ model = route.generate_all.camelize.constantize
14
16
 
15
- target_directory = File.join(output_directory, path)
16
- FileUtils.mkdir_p(target_directory)
17
-
18
- File.open(File.join(target_directory, "index.html"), "w") do |f|
19
- f.puts render_route(route)
17
+ model.all.each do |item|
18
+ target_path = format(path, id: item.id)
19
+ build_single_page(target_path, route, id: item.id)
20
+ end
21
+ next
20
22
  end
23
+
24
+ build_single_page(path, route)
25
+ end
26
+ end
27
+
28
+ def build_single_page(target_path, route, params = {})
29
+ if params.empty?
30
+ params_log_str = ""
31
+ else
32
+ params_log_str = "(#{params.inspect})"
33
+ route = route.dup
34
+ route.params = OpenStruct.new(route.params.to_h.merge(params))
35
+ end
36
+
37
+ puts "PAGE\t#{target_path} => #{route.base_name}##{route.method_name}#{params_log_str}"
38
+
39
+ target_file = File.join(output_directory, target_path)
40
+ unless route.single_file
41
+ FileUtils.mkdir_p(target_file)
42
+ target_file = File.join(target_file, "index.html")
43
+ end
44
+
45
+ File.open(target_file, "w") do |f|
46
+ f.puts render_route(route, target_path)
21
47
  end
22
48
  end
23
49
 
24
- def render_route(route)
25
- controller = spiced_controller(route).new
50
+ def render_route(route, path)
51
+ controller = spiced_controller(route, path).new
26
52
  controller.method(route.method_name).call
27
53
 
28
- layout = Tilt.new(layout_path(controller))
54
+ layout = layout_for(controller, route)
29
55
  page = Tilt.new(page_path(route))
30
56
 
31
57
  controller.instance_eval do
32
- layout.render(self) { page.render(self) }
58
+ if layout
59
+ layout.render(self) { page.render(self) }
60
+ else
61
+ page.render(self)
62
+ end
33
63
  end
34
64
  end
35
65
 
36
66
  # for the sake of keeping the original classes sane while building, we
37
67
  # create a subclass of the original dynamically and inject common helpers
38
68
  # to it and also run before_actions
39
- def spiced_controller(route) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/LineLength
69
+ def spiced_controller(route, path) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/LineLength
40
70
  Class.new(route.controller).tap do |klass| # rubocop:disable Metrics/BlockLength, Metrics/LineLength
41
71
  klass.include(Helpers::AppHelper.new(app))
42
72
  klass.include(Helpers::AssetsHelper)
@@ -57,6 +87,18 @@ module FoxPage
57
87
  # same difference
58
88
  end
59
89
 
90
+ klass.define_method(:params) do
91
+ route.params
92
+ end
93
+
94
+ klass.define_method(:current_path) do
95
+ path
96
+ end
97
+
98
+ klass.define_method(:current_controller_name) do
99
+ route.base_name
100
+ end
101
+
60
102
  klass.define_method(:inspect) do |*args|
61
103
  # report that we are actually the controller, not some random
62
104
  # anonymous class
@@ -89,9 +131,18 @@ module FoxPage
89
131
  end
90
132
  end
91
133
 
92
- def layout_path(controller)
134
+ def layout_for(controller, route)
135
+ layout = controller.class.superclass.instance_variable_get(:@__use_layout_for)&.[](route.method_name)
136
+ return if layout == false
137
+
138
+ Tilt.new(layout_path(controller, layout))
139
+ end
140
+
141
+ def layout_path(controller, layout)
142
+ layout ||= controller.class.layout
143
+
93
144
  File
94
- .join(views_path, controller.class.layout)
145
+ .join(views_path, layout)
95
146
  .tap(&method(:validate_file_exists))
96
147
  end
97
148
 
@@ -13,5 +13,38 @@ module FoxPage
13
13
  @__before_actions ||= []
14
14
  @__before_actions << method_name
15
15
  end
16
+
17
+ # Instructs the site builder to generate pages for all records of `model`.
18
+ def self.generate_all(model)
19
+ @__generate_all = model
20
+ end
21
+
22
+ def self.use_layout(layout)
23
+ @__use_layout = layout
24
+ end
25
+
26
+ def self.method_added(method_name)
27
+ return unless @__generate_all.nil? || @__use_layout.nil?
28
+
29
+ set_method_option(method_name, "generate_all")
30
+ set_method_option(method_name, "use_layout")
31
+ end
32
+
33
+ def self.set_method_option(method_name, option)
34
+ ivar_name = :"@__#{option}"
35
+ ivar_for_name = :"@__#{option}_for"
36
+
37
+ ivar_val = instance_variable_get(ivar_name)
38
+ return if ivar_val.nil?
39
+
40
+ instance_variable_set(ivar_name, nil)
41
+ unless instance_variable_get(ivar_for_name)
42
+ instance_variable_set(ivar_for_name, {})
43
+ end
44
+
45
+ instance_variable_get(ivar_for_name)[method_name] = ivar_val
46
+ end
47
+
48
+ private_class_method :set_method_option
16
49
  end
17
50
  end
@@ -3,14 +3,14 @@
3
3
  module FoxPage
4
4
  module Helpers
5
5
  module RenderHelper
6
- def render(view)
6
+ def render(view, params = {})
7
7
  full_path = Dir.glob(app.root.join("app/views/#{view}.*")).first
8
8
 
9
9
  unless full_path
10
10
  raise ArgumentError, "Could not find template for #{view}"
11
11
  end
12
12
 
13
- Tilt.new(full_path).render(self)
13
+ Tilt.new(full_path).render(self, params)
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module FoxPage
6
+ class Model
7
+ using Refinements::Camelize
8
+ using Refinements::Pluralize
9
+ using Refinements::Singularize
10
+ using Refinements::ToDeepOpenStruct
11
+ using Refinements::Underscore
12
+
13
+ @__data = []
14
+
15
+ VALID_STORAGE_TYPES = %i[yaml dir].freeze
16
+ DEFAULT_STORAGE_TYPE = :yaml
17
+ private_constant :VALID_STORAGE_TYPES
18
+ private_constant :DEFAULT_STORAGE_TYPE
19
+
20
+ def self.[](storage_type = DEFAULT_STORAGE_TYPE, storage_type_opts = {})
21
+ unless VALID_STORAGE_TYPES.include?(storage_type)
22
+ raise ArgumentError,
23
+ "type must be one of #{VALID_STORAGE_TYPES.join(',')}"
24
+ end
25
+
26
+ @__tmp_storage_type = storage_type
27
+ @__tmp_storage_type_opts = storage_type_opts
28
+
29
+ self
30
+ end
31
+
32
+ def self.inherited(subclass)
33
+ set_ivar_if_unset subclass, :storage_type, DEFAULT_STORAGE_TYPE
34
+ set_ivar_if_unset subclass, :storage_type_opts, {}
35
+
36
+ subclass.reload_all(@__app)
37
+ end
38
+
39
+ def self.set_ivar_if_unset(subclass, ivar, default)
40
+ target_ivar = :"@__#{ivar}"
41
+ return if subclass.instance_variable_get(target_ivar)
42
+
43
+ tmp_ivar = :"@__tmp_#{ivar}"
44
+ value = instance_variable_get tmp_ivar
45
+ instance_variable_set tmp_ivar, nil
46
+
47
+ subclass.instance_variable_set target_ivar, value || default
48
+ end
49
+ private_class_method :set_ivar_if_unset
50
+
51
+ def self.reload_all(app)
52
+ data_name = name.to_s.underscore.pluralize
53
+ puts "MODEL\t#{data_name}"
54
+
55
+ case @__storage_type
56
+ when :yaml
57
+ @__data = YAML.load_file(
58
+ app.root.join("data", data_name + ".yml")
59
+ ).to_deep_ostruct.map { |ostruct| new(ostruct) }
60
+ when :dir
61
+ default_opts = { extension: :md }
62
+ opts = default_opts.merge(@__storage_type_opts)
63
+ files = Dir[app.root.join("data", data_name, "*.#{opts.fetch(:extension)}")]
64
+
65
+ @__data = files.map do |fn|
66
+ id = File.basename(fn, ".#{opts.fetch(:extension)}")
67
+ content = IO.read(fn)
68
+
69
+ front_matter = {}
70
+ if content =~ /\A(---\n.*\n)^(?:---)\s*$\n?/m
71
+ content = Regexp.last_match.post_match
72
+ front_matter = YAML.safe_load(Regexp.last_match[1], [Time])
73
+ end
74
+
75
+ new front_matter.merge(id: id, content: content).to_deep_ostruct
76
+ end
77
+ end
78
+ end
79
+
80
+ # e.g. in ProjectCategory: has_many :projects, referenced_by: nil
81
+ #
82
+ # nil = default of self.class.name underscored
83
+ def self.has_many(what, referenced_by: nil) # rubocop:disable Naming/PredicateName, Metrics/LineLength
84
+ referenced_by ||= name.to_s.underscore
85
+ association_class = Kernel.const_get(what.to_s.singularize.camelize)
86
+
87
+ define_method(what.to_s) do
88
+ association_class.where(referenced_by => name.to_s)
89
+ end
90
+ end
91
+
92
+ # e.g. in Project: belongs_to :project_category, referenced_by: :name
93
+ def self.belongs_to(what, referenced_by: :name)
94
+ association_class = Kernel.const_get(what.to_s.camelize)
95
+
96
+ define_method("__#{what}_value") do
97
+ @__ostruct[what]
98
+ end
99
+
100
+ define_method(what) do
101
+ association_class.find(referenced_by => public_send("__#{what}_value"))
102
+ end
103
+ end
104
+
105
+ # define a parser method for attributes
106
+ # @example
107
+ # require "time"
108
+ #
109
+ # class BlogPost < FoxPage::Model[:dir]
110
+ # def_parser :date do |date|
111
+ # Time.parse(date)
112
+ # end
113
+ # end
114
+ #
115
+ # # in the blog posts' front matter:
116
+ # # ---
117
+ # # title: foo
118
+ # # date: Sat 13 Jul 13:38:43 CEST 2019
119
+ # # ---
120
+ #
121
+ # # then, anywhere else:
122
+ # blog_post.date # => 2019-07-13 13:38:43 +0200
123
+ # blog_post.date.class # => Time
124
+ def self.def_parser(attribute, &parser)
125
+ define_method(attribute) do
126
+ parser.call(@__ostruct[attribute])
127
+ end
128
+ end
129
+
130
+ def self.all
131
+ @__data
132
+ end
133
+
134
+ def self.each(&block)
135
+ @__data.each(&block)
136
+ end
137
+
138
+ def self.find(filter)
139
+ @__data.find do |object|
140
+ filter.all? do |key, value|
141
+ value_key_name = "__#{key}_value"
142
+ key = value_key_name if object.respond_to?(value_key_name)
143
+
144
+ object.public_send(key) == value
145
+ end
146
+ end
147
+ end
148
+
149
+ def self.where(filter)
150
+ @__data.select do |object|
151
+ filter.all? do |key, value|
152
+ value_key_name = "__#{key}_value"
153
+ key = value_key_name if object.respond_to?(value_key_name)
154
+
155
+ object.public_send(key) == value
156
+ end
157
+ end
158
+ end
159
+
160
+ def initialize(ostruct)
161
+ @__ostruct = ostruct
162
+ end
163
+
164
+ def method_missing(method, *args, &block)
165
+ hash_ostruct = @__ostruct.to_h
166
+ return super unless hash_ostruct.key?(method.to_sym)
167
+
168
+ hash_ostruct[method.to_sym]
169
+ end
170
+
171
+ def respond_to_missing?(method, *)
172
+ return true if @__ostruct.to_h.key?(method.to_sym)
173
+
174
+ super
175
+ end
176
+ end
177
+ end
@@ -8,6 +8,12 @@ module FoxPage
8
8
  split("_").map(&:capitalize).join
9
9
  end
10
10
  end
11
+
12
+ refine Symbol do
13
+ def camelize
14
+ to_s.camelize.to_sym
15
+ end
16
+ end
11
17
  end
12
18
  end
13
19
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoxPage
4
+ module Refinements
5
+ module Constantize
6
+ refine String do
7
+ def constantize
8
+ Kernel.const_get(self)
9
+ end
10
+ end
11
+
12
+ refine Symbol do
13
+ def constantize
14
+ to_s.constantize
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoxPage
4
+ module Refinements
5
+ module Pluralize
6
+ refine String do
7
+ def pluralize
8
+ return sub(/y$/, "ies") if end_with?("y")
9
+
10
+ "#{self}s"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoxPage
4
+ module Refinements
5
+ module Singularize
6
+ refine String do
7
+ def singularize
8
+ return sub(/ies$/, "y") if end_with?("ies")
9
+
10
+ sub(/s$/, "")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoxPage
4
+ module Refinements
5
+ module Underscore
6
+ refine String do
7
+ def underscore
8
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
9
+ end
10
+ end
11
+
12
+ refine Symbol do
13
+ def underscore
14
+ to_s.underscore.to_sym
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -21,36 +21,90 @@ module FoxPage
21
21
  routes
22
22
  end
23
23
 
24
- def root(target)
25
- routes["/"] = parse_target(target)
24
+ def root(target, params: {})
25
+ routes["/"] = parse_target(target, params: params)
26
26
  end
27
27
 
28
- def map(mapping)
28
+ def map(mapping, params: {}, single_file: false)
29
29
  mapping.each do |path, target|
30
- routes[path] = parse_target(target)
30
+ routes[path] = parse_target(target, params: params, single_file: single_file)
31
+ end
32
+ end
33
+
34
+ RESOURCE_ACTIONS = %i[index show].freeze
35
+
36
+ def resources(name, path: name, only: RESOURCE_ACTIONS)
37
+ actions = only.map(&:to_sym)
38
+ base_name = name.to_s
39
+ controller = controller_for(base_name)
40
+ base_path = "/#{path}"
41
+
42
+ # show action needs some additional stuff to make it work
43
+ # since we have to know all the ids beforehand
44
+ if actions.delete :show
45
+ method_name = :show
46
+ validate_controller_method(controller, method_name)
47
+ route_path = "#{base_path}/%<id>s"
48
+
49
+ routes[route_path] = make_target(
50
+ base_name: base_name,
51
+ controller: controller,
52
+ method_name: method_name,
53
+ params: {},
54
+ generate_all: controller.instance_variable_get(:@__generate_all_for)&.[](method_name)
55
+ )
56
+ end
57
+
58
+ actions.each do |action|
59
+ method_name = action
60
+ validate_controller_method(controller, method_name)
61
+ route_path = method_name == :index ? base_path : "#{base_path}/#{method_name}"
62
+
63
+ routes[route_path] = make_target(
64
+ base_name: base_name,
65
+ controller: controller,
66
+ method_name: method_name
67
+ )
31
68
  end
32
69
  end
33
70
 
34
71
  private
35
72
 
36
- def parse_target(target)
73
+ def parse_target(target, params: {}, single_file: false)
37
74
  base_name, method_name = target.split("#")
38
- controller = Kernel.const_get("#{base_name}_controller".camelize)
75
+ controller = controller_for(base_name)
39
76
  method_name = method_name.to_sym
40
77
 
41
78
  validate_controller_method(controller, method_name)
42
79
 
43
- OpenStruct.new(
80
+ make_target(
44
81
  base_name: base_name,
45
82
  controller: controller,
46
- method_name: method_name
83
+ method_name: method_name,
84
+ params: params,
85
+ single_file: single_file
47
86
  )
48
87
  end
49
88
 
89
+ def controller_for(base_name)
90
+ Kernel.const_get("#{base_name}_controller".camelize)
91
+ end
92
+
50
93
  def validate_controller_method(controller, method_name)
51
94
  return if controller.instance_methods.include?(method_name)
52
95
 
53
96
  raise ArgumentError, "#{controller} does not define ##{method_name}"
54
97
  end
98
+
99
+ def make_target(base_name:, controller:, method_name:, params: {}, generate_all: nil, single_file: false)
100
+ OpenStruct.new(
101
+ base_name: base_name,
102
+ controller: controller,
103
+ method_name: method_name,
104
+ params: OpenStruct.new(params),
105
+ generate_all: generate_all,
106
+ single_file: single_file
107
+ )
108
+ end
55
109
  end
56
110
  end
@@ -8,6 +8,7 @@ module FoxPage
8
8
  def initialize(app)
9
9
  @app = app
10
10
  @listener = Listen.to(app.root.join("app"),
11
+ app.root.join("data"),
11
12
  app.root.join("public"),
12
13
  &method(:handle_modified_app))
13
14
  @server = WEBrick::HTTPServer.new(
@@ -6,6 +6,7 @@ module FoxPage
6
6
  class SiteBuilder
7
7
  include Builders::Assets
8
8
  include Builders::FileCopy
9
+ include Builders::Models
9
10
  include Builders::Pages
10
11
 
11
12
  def self.build(app)
@@ -24,6 +25,7 @@ module FoxPage
24
25
 
25
26
  FileUtils.mkdir_p output_directory
26
27
 
28
+ load_models
27
29
  build_assets
28
30
  build_pages
29
31
  copy_public_files
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FoxPage
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foxpage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georg Gadinger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-03 00:00:00.000000000 Z
11
+ date: 2019-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -228,6 +228,7 @@ files:
228
228
  - lib/fox_page/application.rb
229
229
  - lib/fox_page/builders/assets.rb
230
230
  - lib/fox_page/builders/file_copy.rb
231
+ - lib/fox_page/builders/models.rb
231
232
  - lib/fox_page/builders/pages.rb
232
233
  - lib/fox_page/cli.rb
233
234
  - lib/fox_page/controller.rb
@@ -235,8 +236,13 @@ files:
235
236
  - lib/fox_page/helpers/app_helper.rb
236
237
  - lib/fox_page/helpers/assets_helper.rb
237
238
  - lib/fox_page/helpers/render_helper.rb
239
+ - lib/fox_page/model.rb
238
240
  - lib/fox_page/refinements/camelize.rb
241
+ - lib/fox_page/refinements/constantize.rb
242
+ - lib/fox_page/refinements/pluralize.rb
243
+ - lib/fox_page/refinements/singularize.rb
239
244
  - lib/fox_page/refinements/to_deep_open_struct.rb
245
+ - lib/fox_page/refinements/underscore.rb
240
246
  - lib/fox_page/router.rb
241
247
  - lib/fox_page/server.rb
242
248
  - lib/fox_page/site_builder.rb