markdown_record 0.1.3

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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +225 -0
  4. data/Rakefile +9 -0
  5. data/app/assets/config/markdown_cms_manifest.js +1 -0
  6. data/app/assets/stylesheets/markdown_cms/application.css +15 -0
  7. data/app/controllers/markdown_record/application_controller.rb +11 -0
  8. data/app/controllers/markdown_record/content_controller.rb +19 -0
  9. data/app/controllers/markdown_record/html_controller.rb +11 -0
  10. data/app/controllers/markdown_record/json_controller.rb +11 -0
  11. data/app/helpers/markdown_record/application_helper.rb +4 -0
  12. data/app/helpers/markdown_record/controller_helpers.rb +39 -0
  13. data/app/helpers/markdown_record/view_helpers.rb +84 -0
  14. data/app/models/markdown_record/demo/dsl_command.rb +10 -0
  15. data/app/models/markdown_record/demo/section.rb +9 -0
  16. data/app/models/markdown_record/tests/child_model.rb +15 -0
  17. data/app/models/markdown_record/tests/fake_active_record_model.rb +13 -0
  18. data/app/models/markdown_record/tests/model.rb +15 -0
  19. data/app/models/markdown_record/tests/other_child_model.rb +15 -0
  20. data/config/routes.rb +13 -0
  21. data/lib/generators/markdown_record_generator.rb +44 -0
  22. data/lib/markdown_record/association.rb +131 -0
  23. data/lib/markdown_record/associations.rb +106 -0
  24. data/lib/markdown_record/base.rb +25 -0
  25. data/lib/markdown_record/cli.rb +54 -0
  26. data/lib/markdown_record/configuration.rb +66 -0
  27. data/lib/markdown_record/content_associations.rb +46 -0
  28. data/lib/markdown_record/content_dsl/attribute.rb +22 -0
  29. data/lib/markdown_record/content_dsl/directory_fragment.rb +22 -0
  30. data/lib/markdown_record/content_dsl/disable.rb +22 -0
  31. data/lib/markdown_record/content_dsl/enable.rb +22 -0
  32. data/lib/markdown_record/content_dsl/end_attribute.rb +22 -0
  33. data/lib/markdown_record/content_dsl/end_model.rb +22 -0
  34. data/lib/markdown_record/content_dsl/fragment.rb +22 -0
  35. data/lib/markdown_record/content_dsl/model.rb +23 -0
  36. data/lib/markdown_record/content_dsl/render_format.rb +22 -0
  37. data/lib/markdown_record/content_dsl/render_strategy.rb +22 -0
  38. data/lib/markdown_record/content_dsl/use_layout.rb +22 -0
  39. data/lib/markdown_record/content_dsl.rb +37 -0
  40. data/lib/markdown_record/content_fragment.rb +123 -0
  41. data/lib/markdown_record/engine.rb +13 -0
  42. data/lib/markdown_record/errors/base.rb +6 -0
  43. data/lib/markdown_record/errors/duplicate_filename_error.rb +21 -0
  44. data/lib/markdown_record/errors/duplicate_id_error.rb +11 -0
  45. data/lib/markdown_record/errors/missing_parent_error.rb +11 -0
  46. data/lib/markdown_record/file_saver.rb +39 -0
  47. data/lib/markdown_record/html_renderer.rb +194 -0
  48. data/lib/markdown_record/indexer.rb +34 -0
  49. data/lib/markdown_record/json_renderer.rb +270 -0
  50. data/lib/markdown_record/model_inflator.rb +107 -0
  51. data/lib/markdown_record/path_utilities.rb +86 -0
  52. data/lib/markdown_record/rendering.rb +57 -0
  53. data/lib/markdown_record/routes_renderer.rb +0 -0
  54. data/lib/markdown_record/validator.rb +59 -0
  55. data/lib/markdown_record/version.rb +3 -0
  56. data/lib/markdown_record.rb +28 -0
  57. data/templates/Thorfile +3 -0
  58. data/templates/base/content/example_content.md +3 -0
  59. data/templates/base/layouts/_concatenated_layout.html.erb +8 -0
  60. data/templates/base/layouts/_custom_layout.html.erb +7 -0
  61. data/templates/base/layouts/_file_layout.html.erb +8 -0
  62. data/templates/base/layouts/_global_layout.html.erb +8 -0
  63. data/templates/demo/assets/images/ruby-logo.png +0 -0
  64. data/templates/demo/content/10_custom_models_and_associations.md.erb +78 -0
  65. data/templates/demo/content/11_controller_helpers.md.erb +20 -0
  66. data/templates/demo/content/12_configuration.md.erb +26 -0
  67. data/templates/demo/content/13_sandbox/1_foo.md +12 -0
  68. data/templates/demo/content/13_sandbox/2_sandbox_nested/1_bar.md +18 -0
  69. data/templates/demo/content/1_home.md.erb +40 -0
  70. data/templates/demo/content/2_installation.md.erb +129 -0
  71. data/templates/demo/content/3_rendering_basics.md.erb +71 -0
  72. data/templates/demo/content/4_content_dsl.md.erb +104 -0
  73. data/templates/demo/content/5_routes.md.erb +43 -0
  74. data/templates/demo/content/6_model_basics.md.erb +105 -0
  75. data/templates/demo/content/7_content_frags.md.erb +78 -0
  76. data/templates/demo/content/8_erb_syntax_and_view_helpers.md.erb +88 -0
  77. data/templates/demo/content/9_layouts.md.erb +15 -0
  78. data/templates/demo/layouts/_concatenated_layout.html.erb +12 -0
  79. data/templates/demo/layouts/_custom_layout.html.erb +14 -0
  80. data/templates/demo/layouts/_file_layout.html.erb +15 -0
  81. data/templates/demo/layouts/_global_layout.html.erb +108 -0
  82. data/templates/markdown_record_initializer.rb +3 -0
  83. data/templates/render_content.thor +57 -0
  84. metadata +270 -0
@@ -0,0 +1,37 @@
1
+ require "markdown_record/content_dsl/model"
2
+ require "markdown_record/content_dsl/attribute"
3
+ require "markdown_record/content_dsl/end_attribute"
4
+ require "markdown_record/content_dsl/end_model"
5
+ require "markdown_record/content_dsl/fragment"
6
+ require "markdown_record/content_dsl/directory_fragment"
7
+ require "markdown_record/content_dsl/use_layout"
8
+ require "markdown_record/content_dsl/disable"
9
+ require "markdown_record/content_dsl/enable"
10
+
11
+ module MarkdownRecord
12
+ module ContentDsl
13
+ include Model
14
+ include Attribute
15
+ include EndAttribute
16
+ include EndModel
17
+ include DirectoryFragment
18
+ include Fragment
19
+ include UseLayout
20
+ include Disable
21
+ include Enable
22
+
23
+ HTML_COMMENT_REGEX = /(<!--(?:(?:\s|.)(?!-->))*(?:.|\s)-->)/
24
+
25
+ def remove_dsl(text)
26
+ text = Model.remove_dsl(text)
27
+ text = Attribute.remove_dsl(text)
28
+ text = EndAttribute.remove_dsl(text)
29
+ text = EndModel.remove_dsl(text)
30
+ text = Fragment.remove_dsl(text)
31
+ text = UseLayout.remove_dsl(text)
32
+ text = Disable.remove_dsl(text)
33
+ text = Enable.remove_dsl(text)
34
+ text
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,123 @@
1
+ module MarkdownRecord
2
+ class ContentFragment < MarkdownRecord::Base
3
+ include MarkdownRecord::PathUtilities
4
+
5
+ attribute :meta, :type => Object
6
+ attribute :concatenated, :type => Boolean
7
+
8
+ def initialize(attributes = nil, options = {})
9
+ super
10
+
11
+ if self.meta.class == Hash
12
+ self.meta = self.meta.with_indifferent_access
13
+ end
14
+ end
15
+
16
+ # Override the new_association method on MarkdownRecord::Base
17
+ # to force all association queries to only look for fragments
18
+ def self.new_association(base_filters = {}, search_filters = {})
19
+ MarkdownRecord::Association.new(base_filters, search_filters).fragmentize
20
+ end
21
+
22
+ def fragment_id
23
+ id
24
+ end
25
+
26
+ def name
27
+ meta[:name]
28
+ end
29
+
30
+ def exists?
31
+ json_exists? || html_exists?
32
+ end
33
+
34
+ def json_exists?
35
+ Pathname.new(json_path).exist?
36
+ end
37
+
38
+ def html_exists?
39
+ Pathname.new(html_path).exist?
40
+ end
41
+
42
+ def read_json
43
+ File.read(json_path) if json_exists?
44
+ end
45
+
46
+ def read_html
47
+ File.read(html_path) if html_exists?
48
+ end
49
+
50
+ def json_path
51
+ path("json")
52
+ end
53
+
54
+ def json_route
55
+ route("json", id)
56
+ end
57
+
58
+ def html_path
59
+ path("html")
60
+ end
61
+
62
+ def html_route
63
+ route("html", id)
64
+ end
65
+
66
+ # Associations
67
+ def ancestors
68
+ ancestors_from(::MarkdownRecord.config.content_root.basename.to_s)
69
+ end
70
+
71
+ def parent
72
+ self.class.find(meta[:parent_id] || subdirectory)
73
+ end
74
+
75
+ def ancestors_from(ancestor)
76
+ ancestor = self.class.find(ancestor) if ancestor.is_a?(String)
77
+
78
+ parents = []
79
+ current_parent = self.class.find(subdirectory)
80
+
81
+ while current_parent
82
+ if current_parent.id == ancestor.id
83
+ parents.unshift(current_parent)
84
+ current_parent = nil
85
+ else
86
+ parents.unshift(current_parent)
87
+ current_parent = self.class.find(current_parent.subdirectory)
88
+ end
89
+ end
90
+
91
+ parents
92
+ end
93
+
94
+ def parents_from(ancestor)
95
+ ancestor = self.class.find(ancestor) if ancestor.is_a?(String)
96
+
97
+ parents = []
98
+ current_parent = self
99
+
100
+ while current_parent
101
+ if current_parent.id == ancestor&.id
102
+ parents.unshift(current_parent)
103
+ current_parent = nil
104
+ else
105
+ parents.unshift(current_parent)
106
+ current_parent = current_parent.parent
107
+ end
108
+ end
109
+
110
+ parents
111
+ end
112
+
113
+ private
114
+
115
+ def path(ext)
116
+ "#{::MarkdownRecord.config.rendered_content_root.join(id).to_s}.#{ext}"
117
+ end
118
+
119
+ def route(ext, path)
120
+ "#{ext}/#{path}"
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,13 @@
1
+ module MarkdownRecord
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace MarkdownRecord
4
+
5
+ initializer "markdown_record.view_helpers" do
6
+ ActiveSupport.on_load(:action_view) { include MarkdownRecord::ViewHelpers }
7
+ end
8
+
9
+ initializer 'markdown_record.controller_helpers' do
10
+ ActiveSupport.on_load(:action_controller) { include ::MarkdownRecord::ControllerHelpers }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module MarkdownRecord
2
+ module Errors
3
+ class Base < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ require "markdown_record/errors/base"
2
+
3
+ module MarkdownRecord
4
+ module Errors
5
+ class DuplicateFilenameError < MarkdownRecord::Errors::Base
6
+ def initialize(filenames)
7
+ msgs = []
8
+
9
+ filenames.each do |f|
10
+ if MarkdownRecord.config.ignore_numeric_prefix
11
+ msgs << "There are multiple files that resolve to [#{f}] after numeric prefixes are ignored."
12
+ else
13
+ msgs << "There are multiple files that resolve to [#{f}]."
14
+ end
15
+ end
16
+
17
+ super(msgs.join("\n"))
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require "markdown_record/errors/base"
2
+
3
+ module MarkdownRecord
4
+ module Errors
5
+ class DuplicateIdError < MarkdownRecord::Errors::Base
6
+ def initialize(type, id)
7
+ super("There are multiple models of type #{type} with id: #{id}.")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require "markdown_record/errors/base"
2
+
3
+ module MarkdownRecord
4
+ module Errors
5
+ class MissingParentError < MarkdownRecord::Errors::Base
6
+ def initialize(parent_id)
7
+ super("No content fragment matched parent_id: #{parent_id}.")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ module MarkdownRecord
2
+ class FileSaver
3
+ include ::MarkdownRecord::PathUtilities
4
+
5
+ attr_accessor :saved_files
6
+
7
+ def initialize
8
+ @saved_files = {:html => [], :json => []}
9
+ end
10
+
11
+ def save_to_file(content, rendered_subdirectory, options, fragments = false)
12
+ filename, subdirectory, extension = full_path_to_parts(rendered_subdirectory)
13
+ filename = file_name(filename, extension, fragments)
14
+
15
+ save_path = ::MarkdownRecord.config.rendered_content_root.join(subdirectory).join(filename)
16
+ relative_path = save_path.to_s.gsub(Rails.root.to_s, "")
17
+
18
+ @saved_files[extension.to_sym].unshift(relative_path)
19
+
20
+ if options[:save]
21
+ save_path.dirname.mkpath
22
+
23
+ save_path.open('wb') do |file|
24
+ file << content
25
+ end
26
+ end
27
+ end
28
+
29
+ def base_content_name
30
+ ::MarkdownRecord.config.content_root.basename
31
+ end
32
+
33
+ def file_name(subdirectory, extension, fragments)
34
+ base_name = ::MarkdownRecord.config.content_root.join(subdirectory).basename.to_s
35
+ base_name += "_fragments" if fragments
36
+ "#{base_name}.#{extension}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,194 @@
1
+ require "redcarpet"
2
+ require "htmlbeautifier"
3
+
4
+ module MarkdownRecord
5
+ class HtmlRenderer < ::Redcarpet::Render::HTML
6
+ include ::MarkdownRecord::PathUtilities
7
+ include ::MarkdownRecord::ContentDsl
8
+
9
+ HTML_SUBSTITUTIONS = {
10
+ /<!---/ => "<!--",
11
+ /&lt;!---/ => "&lt;!--"
12
+ }
13
+
14
+ def initialize(
15
+ file_saver: ::MarkdownRecord::FileSaver.new)
16
+ super(::MarkdownRecord.config.html_render_options)
17
+ @indexer = ::MarkdownRecord::Indexer.new
18
+ @markdown = ::Redcarpet::Markdown.new(self, ::MarkdownRecord.config.markdown_extensions)
19
+ @file_saver = file_saver
20
+ end
21
+
22
+ def render_html_for_subdirectory(subdirectory: "", **options)
23
+ content = @indexer.index(subdirectory: subdirectory)
24
+ rendered_subdirectory = base_content_path.join(subdirectory)
25
+
26
+ html_content = render_html_recursively(content, rendered_subdirectory.to_s)
27
+ processed_html = process_html_recursively(html_content[rendered_subdirectory.to_s], rendered_subdirectory, options)
28
+ final_html = finalize_html_recursively(processed_html, rendered_subdirectory)
29
+
30
+ save_content_recursively(final_html, options, rendered_subdirectory)
31
+ { :html_content => processed_html, :saved_files => @file_saver.saved_files }
32
+ nil
33
+ end
34
+
35
+ private
36
+
37
+ def render_html_recursively(file_or_directory, file_or_directory_name)
38
+ case file_or_directory.class.name
39
+ when Hash.name # if it is a directory
40
+ directory_hash = { file_or_directory_name => {} } # hash representing the directory and its contents
41
+
42
+ file_or_directory.each do |child_file_or_directory_name, child_file_or_directory|
43
+ child_content_hash = render_html_recursively(child_file_or_directory, child_file_or_directory_name)
44
+ directory_hash[file_or_directory_name].merge!(child_content_hash)
45
+ end
46
+
47
+ directory_hash
48
+ when String.name # if it is a file
49
+ { file_or_directory_name => render_html(file_or_directory) }
50
+ end
51
+ end
52
+
53
+ def render_html(html)
54
+ rendered_html = @markdown.render(html)
55
+ remove_dsl(rendered_html)
56
+ end
57
+
58
+ def process_html_recursively(file_or_directory, full_path, options)
59
+ case file_or_directory.class.name
60
+ when Hash.name
61
+ directory_hash = { full_path.to_s => {} } # hash representing the directory and its contents
62
+
63
+ file_or_directory.each do |child_file_or_directory_name, child_file_or_directory|
64
+ child_content_hash = process_html_recursively(child_file_or_directory, full_path.join(child_file_or_directory_name), options)
65
+ directory_hash[full_path.to_s].merge!(child_content_hash)
66
+ end
67
+
68
+ if options[:concat]
69
+ concatenated_html = concatenate_html_recursively(directory_hash[full_path.to_s], []).join("\r\n")
70
+ directory_hash["#{full_path.to_s}.concat"] = process_html(concatenated_html, full_path.to_s, concatenated_layout)
71
+ end
72
+
73
+ directory_hash.compact
74
+ when String.name # if it is a file
75
+ if options[:deep]
76
+ { full_path.to_s => process_html(file_or_directory, full_path, custom_layout(file_or_directory) || file_layout) }
77
+ else
78
+ { }
79
+ end
80
+ end
81
+ end
82
+
83
+ def process_html(html, full_path, layout = nil)
84
+ processed_html = html.gsub(/<p>(\&lt;%(\S|\s)*?%\&gt;)<\/p>/){ CGI.unescapeHTML($1) }
85
+ processed_html = processed_html.gsub(/(\&lt;%(\S|\s)*?%\&gt;)/){ CGI.unescapeHTML($1) }
86
+ locals = erb_locals_from_path(full_path.to_s)
87
+ processed_html = render_erb(processed_html, locals) if full_path.to_s.ends_with?(".md.erb")
88
+ processed_html = render_erb(layout, locals.merge(html: processed_html)) if layout
89
+ processed_html
90
+ end
91
+
92
+ def render_erb(html, locals)
93
+ render_controller = ::MarkdownRecord.config.render_controller || ::ApplicationController
94
+ rendered_html = render_controller.render(
95
+ inline: html,
96
+ locals: locals
97
+ ).to_str
98
+ rendered_html
99
+ end
100
+
101
+ def concatenate_html_recursively(content, html)
102
+ case content.class.name
103
+ when Hash.name
104
+ sorted_hash_values(content).each do |v|
105
+ concatenate_html_recursively(v, html)
106
+ end
107
+ when String.name
108
+ html << content
109
+ end
110
+ html
111
+ end
112
+
113
+ def sorted_hash_values(hash)
114
+ values = []
115
+ keys = hash.keys.sort_by do |k|
116
+ basename = k.split("/").last
117
+ m = basename.match(/^(\d+)_/)
118
+ m.try(:[], 1).to_i || k
119
+ end
120
+
121
+ keys.each do |k|
122
+ values << hash[k] if !k.include?(".concat")
123
+ end
124
+ values
125
+ end
126
+
127
+ def custom_layout(html)
128
+ layout = use_layout_dsl(html)
129
+ return unless layout
130
+
131
+ File.read(::MarkdownRecord.config.layout_directory.join(layout))
132
+ end
133
+
134
+ def global_layout
135
+ global_layout_path = ::MarkdownRecord.config.global_layout_path
136
+ @global_layout ||= global_layout_path ? layout(global_layout_path) : nil
137
+ end
138
+
139
+ def concatenated_layout
140
+ concatenated_layout_path = ::MarkdownRecord.config.concatenated_layout_path
141
+ @concatenated_layout ||= concatenated_layout_path ? layout(concatenated_layout_path) : nil
142
+ end
143
+
144
+ def file_layout
145
+ file_layout_path = ::MarkdownRecord.config.file_layout_path
146
+ @file_layout ||= file_layout_path ? layout(file_layout_path) : nil
147
+ end
148
+
149
+ def layout(path)
150
+ File.read(::MarkdownRecord.config.layout_directory.join(path))
151
+ end
152
+
153
+ def finalize_html_recursively(content, rendered_subdirectory)
154
+ case content.class.name
155
+ when Hash.name
156
+ final_hash = {}
157
+ content.each do |key, value|
158
+ child_path = rendered_subdirectory.join(key)
159
+ final_hash[key] = finalize_html_recursively(value, child_path)
160
+ end
161
+ final_hash
162
+ when String.name
163
+ finalize_html(content, rendered_subdirectory)
164
+ end
165
+ end
166
+
167
+ def finalize_html(html, full_path)
168
+ locals = erb_locals_from_path(full_path)
169
+ final_html = html
170
+ final_html = render_erb(global_layout, locals.merge(html: final_html)) if global_layout
171
+
172
+ HTML_SUBSTITUTIONS.each do |find, replace|
173
+ final_html = final_html.gsub(find, replace)
174
+ end
175
+
176
+ final_html = final_html.squeeze("\n")
177
+ final_html = HtmlBeautifier.beautify(final_html)
178
+ final_html
179
+ end
180
+
181
+ def save_content_recursively(content, options, rendered_subdirectory)
182
+ case content.class.name
183
+ when Hash.name
184
+ content.each do |key, value|
185
+ child_path = rendered_subdirectory.join(key)
186
+ save_content_recursively(value, options, child_path)
187
+ end
188
+ when String.name
189
+ path = clean_path(rendered_subdirectory.to_s)
190
+ @file_saver.save_to_file(content, "#{path}.html", options)
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,34 @@
1
+ module MarkdownRecord
2
+ class Indexer
3
+
4
+ def index(subdirectory: "")
5
+ content_path = ::MarkdownRecord.config.content_root.join(subdirectory)
6
+ index = {}
7
+ recursive_index(content_path, index)
8
+ index
9
+ end
10
+
11
+ def file(path)
12
+ file_path = ::MarkdownRecord.config.content_root.join(path)
13
+
14
+ if Pathname.new(path).extname == ".md" && file_path.exist?
15
+ File.read(file_path)
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ def recursive_index(parent_dir_path, index)
22
+ parent_root = Dir.new(parent_dir_path)
23
+ parent_root.children.each do |child|
24
+ pathname = Pathname.new("#{parent_dir_path}/#{child}")
25
+ if pathname.directory?
26
+ index[child] = {}
27
+ recursive_index(pathname, index[child])
28
+ else
29
+ index[child] = File.read(pathname) if (pathname.extname == ".md" || pathname.to_s =~ /\.md\.erb$/)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end