markdown_record 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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