markdown_record 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -43
  3. data/app/helpers/markdown_record/application_helper.rb +23 -0
  4. data/app/helpers/markdown_record/controller_helpers.rb +1 -1
  5. data/app/helpers/markdown_record/view_helpers.rb +9 -3
  6. data/{lib → app/models}/markdown_record/base.rb +10 -0
  7. data/{lib → app/models}/markdown_record/content_fragment.rb +17 -2
  8. data/lib/generators/markdown_record_generator.rb +6 -0
  9. data/lib/markdown_record/configuration.rb +4 -0
  10. data/lib/markdown_record/file_sorting/base.rb +25 -0
  11. data/lib/markdown_record/file_sorting/date_sorter.rb +14 -0
  12. data/lib/markdown_record/file_sorting/sem_ver_sorter.rb +47 -0
  13. data/lib/markdown_record/{association.rb → models/association.rb} +20 -3
  14. data/lib/markdown_record/{associations.rb → models/associations.rb} +17 -9
  15. data/lib/markdown_record/{content_associations.rb → models/content_associations.rb} +4 -2
  16. data/lib/markdown_record/models/filtering.rb +13 -0
  17. data/lib/markdown_record/models/filters/array_filter.rb +11 -0
  18. data/lib/markdown_record/models/filters/base_filter.rb +35 -0
  19. data/lib/markdown_record/models/filters/hash_filter.rb +81 -0
  20. data/lib/markdown_record/models/filters/nil_filter.rb +11 -0
  21. data/lib/markdown_record/models/filters/range_filter.rb +11 -0
  22. data/lib/markdown_record/models/filters/regexp_filter.rb +11 -0
  23. data/lib/markdown_record/models/filters/symbol_filter.rb +14 -0
  24. data/lib/markdown_record/models/filters.rb +22 -0
  25. data/lib/markdown_record/models/model_inflator.rb +65 -0
  26. data/lib/markdown_record/path_utilities.rb +20 -11
  27. data/lib/markdown_record/rendering/content_dsl/attribute.rb +22 -0
  28. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/directory_fragment.rb +3 -3
  29. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/disable.rb +2 -2
  30. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/enable.rb +2 -2
  31. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/end_attribute.rb +3 -3
  32. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/end_model.rb +3 -3
  33. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/fragment.rb +3 -3
  34. data/lib/markdown_record/rendering/content_dsl/model.rb +22 -0
  35. data/lib/markdown_record/rendering/content_dsl/scope.rb +22 -0
  36. data/lib/markdown_record/{content_dsl → rendering/content_dsl}/use_layout.rb +3 -3
  37. data/lib/markdown_record/rendering/content_dsl.rb +45 -0
  38. data/lib/markdown_record/rendering/html_renderer.rb +22 -0
  39. data/lib/markdown_record/{indexer.rb → rendering/indexer.rb} +0 -10
  40. data/lib/markdown_record/rendering/json_renderer.rb +20 -0
  41. data/lib/markdown_record/rendering/nodes/html_base.rb +95 -0
  42. data/lib/markdown_record/rendering/nodes/html_directory.rb +51 -0
  43. data/lib/markdown_record/rendering/nodes/html_file.rb +48 -0
  44. data/lib/markdown_record/rendering/nodes/json_base.rb +55 -0
  45. data/lib/markdown_record/rendering/nodes/json_directory.rb +69 -0
  46. data/lib/markdown_record/rendering/nodes/json_file.rb +172 -0
  47. data/lib/markdown_record/{rendering.rb → rendering/rendering.rb} +1 -1
  48. data/lib/markdown_record/{validator.rb → rendering/validator.rb} +4 -4
  49. data/lib/markdown_record/version.rb +1 -1
  50. data/lib/markdown_record.rb +21 -14
  51. data/templates/demo/content/10_custom_models_and_associations.md.erb +27 -7
  52. data/templates/demo/content/11_controller_helpers.md.erb +2 -2
  53. data/templates/demo/content/12_configuration.md.erb +22 -3
  54. data/templates/demo/content/13_sandbox/1_foo.md +2 -1
  55. data/templates/demo/content/13_sandbox/2_sandbox_nested/1_bar.md +4 -1
  56. data/templates/demo/content/1_home.md.erb +30 -13
  57. data/templates/demo/content/2_installation.md.erb +61 -46
  58. data/templates/demo/content/3_rendering_basics.md.erb +3 -3
  59. data/templates/demo/content/4_content_dsl.md.erb +22 -11
  60. data/templates/demo/content/5_routes.md.erb +10 -7
  61. data/templates/demo/content/6_model_basics.md.erb +11 -11
  62. data/templates/demo/content/7_content_frags.md.erb +9 -6
  63. data/templates/demo/content/8_erb_syntax_and_view_helpers.md.erb +32 -11
  64. data/templates/demo/content/9_layouts.md.erb +3 -3
  65. data/templates/demo/layouts/_global_layout.html.erb +1 -0
  66. data/templates/demo/models/dsl_command.rb +6 -0
  67. data/templates/demo/models/section.rb +5 -0
  68. data/templates/render_content.thor +2 -1
  69. data/templates/tests/assets/images/ruby-logo.png +0 -0
  70. data/templates/tests/content/1_test_files_home.md.erb +31 -0
  71. data/templates/tests/content/2_content_dsl_tests/1_content_dsl.md.erb +162 -0
  72. data/templates/tests/content/2_content_dsl_tests/2_nested_directory/1_associations.md.erb +83 -0
  73. data/templates/tests/layouts/_concatenated_layout.html.erb +12 -0
  74. data/templates/tests/layouts/_custom_layout.html.erb +14 -0
  75. data/templates/tests/layouts/_file_layout.html.erb +15 -0
  76. data/templates/tests/layouts/_global_layout.html.erb +116 -0
  77. metadata +53 -34
  78. data/app/models/markdown_record/demo/dsl_command.rb +0 -10
  79. data/app/models/markdown_record/demo/section.rb +0 -9
  80. data/app/models/markdown_record/tests/child_model.rb +0 -15
  81. data/app/models/markdown_record/tests/fake_active_record_model.rb +0 -13
  82. data/app/models/markdown_record/tests/model.rb +0 -15
  83. data/app/models/markdown_record/tests/other_child_model.rb +0 -15
  84. data/lib/markdown_record/cli.rb +0 -54
  85. data/lib/markdown_record/content_dsl/attribute.rb +0 -22
  86. data/lib/markdown_record/content_dsl/model.rb +0 -23
  87. data/lib/markdown_record/content_dsl/render_format.rb +0 -22
  88. data/lib/markdown_record/content_dsl/render_strategy.rb +0 -22
  89. data/lib/markdown_record/content_dsl.rb +0 -37
  90. data/lib/markdown_record/html_renderer.rb +0 -194
  91. data/lib/markdown_record/json_renderer.rb +0 -270
  92. data/lib/markdown_record/model_inflator.rb +0 -107
  93. data/lib/markdown_record/routes_renderer.rb +0 -0
  94. /data/lib/markdown_record/{file_saver.rb → rendering/file_saver.rb} +0 -0
@@ -0,0 +1,95 @@
1
+ module MarkdownRecord
2
+ module Rendering
3
+ module Nodes
4
+ class HtmlBase
5
+ include ::MarkdownRecord::PathUtilities
6
+ include ::MarkdownRecord::ContentDsl
7
+
8
+ attr_reader :name
9
+ attr_reader :rendered_html
10
+ attr_reader :processed_html
11
+
12
+ HTML_SUBSTITUTIONS = {
13
+ /<!---/ => "<!--",
14
+ /&lt;!---/ => "&lt;!--"
15
+ }
16
+
17
+ def initialize(pathname, markdown, options)
18
+ @markdown = markdown
19
+ @pathname = pathname
20
+ @options = options
21
+ @name = @pathname.relative_path_from(MarkdownRecord.config.content_root.parent).to_s
22
+ @sorter = MarkdownRecord.config.filename_sorter
23
+ end
24
+
25
+ def render(file_saver)
26
+ @rendered_html = ""
27
+ process_html
28
+ finalize_html
29
+ save(file_saver)
30
+ end
31
+
32
+ def process_html
33
+ @processed_html = render_erbs(@rendered_html)
34
+ end
35
+
36
+ def finalize_html
37
+ return unless @processed_html.present?
38
+
39
+ locals = erb_locals_from_path(@name)
40
+ final_html = remove_html_dsl_command(@processed_html)
41
+ final_html = render_erb(global_layout, locals.merge(html: final_html)) if global_layout
42
+
43
+ HTML_SUBSTITUTIONS.each do |find, replace|
44
+ final_html = final_html.gsub(find, replace)
45
+ end
46
+
47
+ final_html = final_html.squeeze("\n")
48
+ @final_html = HtmlBeautifier.beautify(final_html)
49
+ end
50
+
51
+ def save(file_saver)
52
+ return unless @final_html.present?
53
+
54
+ path = clean_path(@name)
55
+ file_saver.save_to_file(@final_html, "#{path}.html", @options)
56
+ end
57
+
58
+ def render_erbs(html)
59
+ processed_html = html.gsub(/<p>(\&lt;%(\S|\s)*?%\&gt;)<\/p>/){ CGI.unescapeHTML($1) }
60
+ processed_html = processed_html.gsub(/(\&lt;%(\S|\s)*?%\&gt;)/){ CGI.unescapeHTML($1) }
61
+ locals = erb_locals_from_path(name)
62
+ processed_html = render_erb(processed_html, locals) if name.ends_with?(".md.erb")
63
+ processed_html = render_erb(layout, locals.merge(html: processed_html)) if layout
64
+ processed_html
65
+ end
66
+
67
+ def layout
68
+ nil
69
+ end
70
+
71
+ def render_erb(html, locals)
72
+ render_controller = ::MarkdownRecord.config.render_controller || ::ApplicationController
73
+ rendered_erb = render_controller.render(
74
+ inline: html,
75
+ locals: locals
76
+ ).to_str
77
+ rendered_erb
78
+ end
79
+
80
+ def global_layout
81
+ global_layout_path = ::MarkdownRecord.config.global_layout_path
82
+ @global_layout ||= global_layout_path ? load_layout(global_layout_path) : nil
83
+ end
84
+
85
+ def load_layout(path)
86
+ File.read(::MarkdownRecord.config.layout_directory.join(path))
87
+ end
88
+
89
+ def sort_value
90
+ @sorter.path_to_sort_value(@name)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,51 @@
1
+ module MarkdownRecord
2
+ module Rendering
3
+ module Nodes
4
+ class HtmlDirectory < MarkdownRecord::Rendering::Nodes::HtmlBase
5
+
6
+ def render(file_saver)
7
+ children.each do |child|
8
+ child.render(file_saver)
9
+ end
10
+
11
+ concatenate_html
12
+ process_html
13
+ finalize_html
14
+
15
+ if @options[:concat]
16
+ save(file_saver)
17
+ end
18
+ end
19
+
20
+ def concatenate_html
21
+ return @rendered_html if @rendered_html.present?
22
+
23
+ sorted_children = children.sort_by(&:sort_value)
24
+ @rendered_html = sorted_children.map(&:concatenated_html).compact.join("\r\n")
25
+ end
26
+
27
+ def concatenated_html
28
+ @rendered_html
29
+ end
30
+
31
+ private
32
+
33
+ def children
34
+ @children ||= Dir.new(@pathname).children.map do |child|
35
+ pathname = @pathname.join(child)
36
+ if pathname.directory?
37
+ self.class.new(pathname, @markdown, @options)
38
+ else
39
+ MarkdownRecord::Rendering::Nodes::HtmlFile.new(pathname, @markdown, @options)
40
+ end
41
+ end
42
+ end
43
+
44
+ def layout
45
+ concatenated_layout_path = ::MarkdownRecord.config.concatenated_layout_path
46
+ concatenated_layout_path ? load_layout(concatenated_layout_path) : nil
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ module MarkdownRecord
2
+ module Rendering
3
+ module Nodes
4
+ class HtmlFile < MarkdownRecord::Rendering::Nodes::HtmlBase
5
+
6
+ def render(file_saver)
7
+ render_html
8
+ process_html
9
+ finalize_html
10
+
11
+ if @options[:deep]
12
+ save(file_saver)
13
+ end
14
+ end
15
+
16
+ def render_html
17
+ html = @markdown.render(raw_content)
18
+ html = remove_json_dsl_commands(html)
19
+ @rendered_html = html
20
+ end
21
+
22
+ def raw_content
23
+ @raw_content ||= File.read(@pathname)
24
+ end
25
+
26
+ def concatenated_html
27
+ @processed_html
28
+ end
29
+
30
+ def layout
31
+ @layout ||= custom_layout(@rendered_html) || file_layout
32
+ end
33
+
34
+ def custom_layout(html)
35
+ cust_layout = use_layout_dsl(html)
36
+ return nil unless cust_layout
37
+
38
+ load_layout(cust_layout)
39
+ end
40
+
41
+ def file_layout
42
+ file_layout_path = ::MarkdownRecord.config.file_layout_path
43
+ @file_layout ||= file_layout_path ? load_layout(file_layout_path) : nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ module MarkdownRecord
2
+ module Rendering
3
+ module Nodes
4
+ class JsonBase
5
+ include ::MarkdownRecord::PathUtilities
6
+ include ::MarkdownRecord::ContentDsl
7
+
8
+ attr_reader :name
9
+ attr_reader :json_models
10
+ attr_reader :concatenated
11
+
12
+ def initialize(pathname, options)
13
+ @pathname = pathname
14
+ @options = options
15
+ @name = @pathname.relative_path_from(MarkdownRecord.config.content_root.parent).to_s
16
+ @json_models = {}
17
+ @concatenated = nil
18
+ @scope = nil
19
+ end
20
+
21
+ def render(file_saver, inherited_scope = nil)
22
+ # This method defines an interface and should be overridden by a subclass.
23
+ add_content_fragment
24
+ save(file_saver)
25
+ end
26
+
27
+ def fragment_meta
28
+ # This method defines an interface and should be overridden by a subclass.
29
+ {}
30
+ end
31
+
32
+ def scope
33
+ @scope
34
+ end
35
+
36
+ def add_content_fragment
37
+ return unless @options[:render_content_fragment_json]
38
+
39
+ content_fragment_hash = fragment_attributes_from_path(name).merge("meta" => fragment_meta, "concatenated" => concatenated, "__scope__" => scope)
40
+
41
+ @json_models["markdown_record/content_fragment"] ||= []
42
+ @json_models["markdown_record/content_fragment"] << content_fragment_hash
43
+ end
44
+
45
+ def save(file_saver)
46
+ fragments = @json_models.slice("markdown_record/content_fragment")
47
+ non_fragments = @json_models.except("markdown_record/content_fragment")
48
+ path = clean_path(@name)
49
+ file_saver.save_to_file(non_fragments.to_json, "#{path}.json", @options)
50
+ file_saver.save_to_file(fragments.to_json, "#{path}.json", @options, true)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ module MarkdownRecord
2
+ module Rendering
3
+ module Nodes
4
+ class JsonDirectory < MarkdownRecord::Rendering::Nodes::JsonBase
5
+
6
+ def initialize(pathname, options, top_level = false)
7
+ super(pathname, options)
8
+ @concatenated = true
9
+ @top_level = top_level
10
+ end
11
+
12
+ def render(file_saver, inherited_scope = nil)
13
+ # Only override scope if this directory
14
+ # doesn't have its own scope defined
15
+ # First call to scope will search files
16
+ # and get the scope if defined.
17
+ @scope = inherited_scope unless scope
18
+
19
+ children.each do |child|
20
+ child.render(file_saver, scope)
21
+ end
22
+
23
+ concatenate_json
24
+ save(file_saver) if @options[:concat] || (@options[:concat_top_level] && @top_level)
25
+ end
26
+
27
+ def fragment_meta
28
+ @fragment_meta ||= children.select {|c| c.respond_to?(:directory_meta) }.reduce({}) { |meta, child| meta.merge(child.directory_meta) }
29
+ end
30
+
31
+ def scope
32
+ @scope ||= children.select {|c| c.respond_to?(:directory_scope) }.find(&:directory_scope)&.directory_scope
33
+ end
34
+
35
+ private
36
+
37
+ def concatenate_json
38
+ return unless @options[:concat] || (@options[:concat_top_level] && @top_level)
39
+
40
+ children.each do |child|
41
+ @json_models.merge!(child.json_models) do |key, oldval, newval|
42
+ oldval + newval
43
+ end
44
+ end
45
+
46
+ # IMPORTANT: Unattach from children's json_models
47
+ # This node's json_models may share references
48
+ # after the merge, so we need to detach them
49
+ @json_models.keys.each do |k|
50
+ @json_models[k] = @json_models[k].dup
51
+ end
52
+
53
+ add_content_fragment
54
+ end
55
+
56
+ def children
57
+ @children ||= Dir.new(@pathname).children.map do |child|
58
+ pathname = @pathname.join(child)
59
+ if pathname.directory?
60
+ self.class.new(pathname, @options)
61
+ else
62
+ MarkdownRecord::Rendering::Nodes::JsonFile.new(pathname, @options)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,172 @@
1
+ module MarkdownRecord
2
+ module Rendering
3
+ module Nodes
4
+ class JsonFile < MarkdownRecord::Rendering::Nodes::JsonBase
5
+
6
+ def initialize(...)
7
+ super(...)
8
+ @concatenated = false
9
+ end
10
+
11
+ def render(file_saver, inherited_scope = nil)
12
+ @scope ||= inherited_scope
13
+ render_json
14
+ apply_scope
15
+ save(file_saver) if @options[:deep]
16
+ end
17
+
18
+ def fragment_meta
19
+ @fragment_meta ||= fragment_dsl(raw_content)
20
+ @fragment_meta ||= {}
21
+ end
22
+
23
+ def scope
24
+ @scope ||= scope_dsl(raw_content)
25
+ end
26
+
27
+ def directory_scope
28
+ @directory_scope ||= scope
29
+ end
30
+
31
+ def directory_meta
32
+ @directory_meta ||= directory_fragment_dsl(raw_content)
33
+ @directory_meta ||= {}
34
+ end
35
+
36
+ private
37
+
38
+ def apply_scope
39
+ @json_models.each do |klass, array|
40
+ next if klass == MarkdownRecord::ContentFragment.name.underscore
41
+ array.each do |model|
42
+ model["scope"] = scope
43
+ model["scoped_id"] = to_scoped_id(scope, model["id"])
44
+ end
45
+ end
46
+ end
47
+
48
+ def raw_content
49
+ @raw_content ||= File.read(@pathname)
50
+ end
51
+
52
+ def render_json
53
+ return unless @options[:deep] || @options[:concat_top_level]
54
+
55
+ filename, subdirectory = full_path_to_parts(name)
56
+
57
+ described_models = []
58
+ enabled = true
59
+
60
+ raw_content.split(HTML_COMMENT_REGEX).each do |text|
61
+ dsl_command = HTML_COMMENT_REGEX =~ text
62
+
63
+ if dsl_command
64
+ enabled = false if disable_dsl(text)
65
+ enabled = true if enable_dsl(text)
66
+ end
67
+
68
+ if dsl_command && enabled
69
+ extract_model(text, described_models)
70
+ extract_attribute(text, described_models)
71
+ pop_attribute(text,described_models)
72
+ pop_model(text, described_models)
73
+ else
74
+ append_to_attribute(text, described_models)
75
+ end
76
+ end
77
+
78
+ described_models.each do |model|
79
+ finalize_attribute(model)
80
+ end
81
+
82
+ add_content_fragment if @options[:deep]
83
+ end
84
+
85
+ def extract_model(text, described_models)
86
+ model = model_dsl(text)
87
+
88
+ if model
89
+ return unless model["type"].present?
90
+ return unless model["id"].present?
91
+
92
+ filename, subdirectory = full_path_to_parts(name)
93
+
94
+ model["subdirectory"] = subdirectory
95
+ model["filename"] = filename
96
+
97
+ # reset "type" to not have a prefix, in order to ensure
98
+ # consistent results between the internal only :klass filter
99
+ # which is used as an index in a hash (and has the prefix removed
100
+ # for consistency and deterministic behavior)
101
+ # and the externally filterable :type field.
102
+ model["type"] = model["type"].delete_prefix("::").strip
103
+
104
+ @json_models[model["type"]] ||= []
105
+ @json_models[model["type"]] << model
106
+
107
+ described_models.push(model)
108
+ end
109
+ end
110
+
111
+ def extract_attribute(text, described_models)
112
+ attribute, type = attribute_dsl(text)
113
+ type ||= "md"
114
+
115
+ if described_models.last && attribute
116
+ model = described_models.last
117
+ model[:described_attribute_type] = type
118
+ model[:described_attribute] = attribute
119
+ model[model[:described_attribute]] = []
120
+ end
121
+ end
122
+
123
+ def append_to_attribute(text, described_models)
124
+ return if text.match(HTML_COMMENT_REGEX)
125
+
126
+ described_models.each do |model|
127
+ next unless model[:described_attribute].present?
128
+
129
+ model[model[:described_attribute]] ||= []
130
+ model[model[:described_attribute]] << text
131
+ end
132
+ end
133
+
134
+ def pop_attribute(text, described_models)
135
+ if described_models.last && end_attribute_dsl(text)
136
+ finalize_attribute(described_models.last)
137
+ end
138
+ end
139
+
140
+ def pop_model(text, described_models)
141
+ if end_model_dsl(text)
142
+ described_models.pop
143
+ end
144
+ end
145
+
146
+ def finalize_attribute(model)
147
+ return if model[:described_attribute].nil?
148
+
149
+ attribute_name = model.delete(:described_attribute)
150
+ attribute = model[attribute_name].join("\n")
151
+ type = model.delete(:described_attribute_type)
152
+
153
+ case type
154
+ when "html"
155
+ renderer = Redcarpet::Render::HTML.new
156
+ attribute = Redcarpet::Markdown.new(renderer).render(attribute)
157
+ when "md"
158
+ attribute = attribute.strip
159
+ when "int"
160
+ attribute = attribute.strip.gsub("\n", "").to_i
161
+ when "float"
162
+ attribute = attribute.strip.gsub("\n", "").to_f
163
+ when "string"
164
+ attribute = Redcarpet::Markdown.new(::Redcarpet::Render::StripDown).render(attribute).strip
165
+ end
166
+
167
+ model[attribute_name] = attribute
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -1,5 +1,5 @@
1
1
  module MarkdownRecord
2
- module Rendering
2
+ module RenderingHelpers
3
3
  def generate_render_strategy_options(options)
4
4
 
5
5
  strategy_options = if options[:strat].present?
@@ -19,7 +19,7 @@ module MarkdownRecord
19
19
 
20
20
  def validate_filenames(val)
21
21
  if val.is_a?(Hash)
22
- temp_keys = val.keys.map { |v| remove_numeric_prefixes(v) }
22
+ temp_keys = val.keys.map { |v| remove_prefix(v) }
23
23
 
24
24
  dups = temp_keys.group_by{|e| clean_path(e)}.keep_if{|_, e| e.length > 1}
25
25
  if dups.any?
@@ -33,8 +33,8 @@ module MarkdownRecord
33
33
  end
34
34
 
35
35
  def validate_models
36
- @json["#{base_content_path.basename}.concat"].each do |klass, array|
37
- ids = array.map { |o| o["id"] }
36
+ @json.each do |klass, array|
37
+ ids = array.map { |o| [o["id"], o["scope"]] }
38
38
  dups = ids.group_by{|e| e}.keep_if{|_, e| e.length > 1}
39
39
  if dups.any?
40
40
  raise ::MarkdownRecord::Errors::DuplicateIdError.new(klass, dups.keys.first)
@@ -43,7 +43,7 @@ module MarkdownRecord
43
43
  end
44
44
 
45
45
  def validate_fragments
46
- frags = @json["#{base_content_path.basename}.concat"]["markdown_record/content_fragment"]
46
+ frags = @json["markdown_record/content_fragment"]
47
47
  frags.each do |frag|
48
48
  parent_id = frag.dig("meta", "parent_id")
49
49
  if parent_id
@@ -1,3 +1,3 @@
1
1
  module MarkdownRecord
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.5"
3
3
  end
@@ -2,20 +2,27 @@ require "markdown_record/version"
2
2
  require "markdown_record/engine"
3
3
  require "markdown_record/configuration"
4
4
  require "markdown_record/path_utilities"
5
- require "markdown_record/validator"
6
- require "markdown_record/rendering"
7
- require "markdown_record/cli"
8
- require "markdown_record/content_dsl"
9
- require "markdown_record/indexer"
10
- require "markdown_record/file_saver"
11
- require "markdown_record/html_renderer"
12
- require "markdown_record/json_renderer"
13
- require "markdown_record/model_inflator"
14
- require "markdown_record/association"
15
- require "markdown_record/associations"
16
- require "markdown_record/content_associations"
17
- require "markdown_record/base"
18
- require "markdown_record/content_fragment"
5
+ require "markdown_record/file_sorting/base"
6
+ require "markdown_record/file_sorting/date_sorter"
7
+ require "markdown_record/file_sorting/sem_ver_sorter"
8
+ require "markdown_record/rendering/validator"
9
+ require "markdown_record/rendering/rendering"
10
+ require "markdown_record/rendering/content_dsl"
11
+ require "markdown_record/rendering/indexer"
12
+ require "markdown_record/rendering/file_saver"
13
+ require "markdown_record/rendering/json_renderer"
14
+ require "markdown_record/rendering/html_renderer"
15
+ require "markdown_record/rendering/nodes/json_base"
16
+ require "markdown_record/rendering/nodes/json_file"
17
+ require "markdown_record/rendering/nodes/json_directory"
18
+ require "markdown_record/rendering/nodes/html_base"
19
+ require "markdown_record/rendering/nodes/html_file"
20
+ require "markdown_record/rendering/nodes/html_directory"
21
+ require "markdown_record/models/filtering"
22
+ require "markdown_record/models/model_inflator"
23
+ require "markdown_record/models/association"
24
+ require "markdown_record/models/associations"
25
+ require "markdown_record/models/content_associations"
19
26
 
20
27
  module MarkdownRecord
21
28
  def self.config
@@ -1,5 +1,5 @@
1
- <!--fragment { "author": "Bryant Morrill", "parent_id": "content/home", "name": "Custom Models and Associations" } -->
2
- <!---model { "type": "markdown_record/demo/section", "id": 8, "name": "Custom Models and Associations" } -->
1
+ <!--fragment { "author": "Bryant Morrill", "relative_parent_id": "home", "name": "Custom Models and Associations" } -->
2
+ <!--model { "type": "section", "id": 9, "name": "Custom Models and Associations" } -->
3
3
 
4
4
  # Custom Models and Associations
5
5
 
@@ -43,24 +43,44 @@ end
43
43
  Once the associations are defined, you will want to make sure your JSON objects in your markdown have the required fields. For the example above, the DslCommand model will automatically be given a `section_id` field that will need to be populated in your markdown like so:
44
44
 
45
45
  ```html
46
- <!---model { "type": "markdown_record/demo/dsl_command", "id": 6, "name": "directory_fragment", "section_id": 1 } -->
46
+ <!---model { "type": "dsl_command", "id": 6, "name": "directory_fragment", "section_id": 1 } -->
47
47
  ```
48
48
 
49
49
  Then you can use the association in your code just like ActiveRecord associations:
50
50
 
51
51
  ```ruby
52
- MarkdownRecord::Demo::Section.find(3).dsl_commands.all
52
+ Section.find(3).dsl_commands.all
53
53
  =>
54
- [#<MarkdownRecord::Demo::DslCommand section_id: 1, description: "...", ...>, ...]
54
+ [#<DslCommand section_id: 1, description: "...", ...>, ...]
55
55
  ```
56
56
 
57
57
  ```ruby
58
- MarkdownRecord::Demo::DslCommand.find(1).section
59
- => #<MarkdownRecord::Demo::Section filename: "content_dsl", id: 3, name: "Content DSL", subdirectory: "content", type: "markdown_record/demo/section">
58
+ DslCommand.find(1).section
59
+ => #<Section filename: "content_dsl", id: 3, name: "Content DSL", subdirectory: "content", type: "section">
60
60
  ```
61
61
 
62
62
  `has_many_content` will define a method that returns an `MarkdownRecord::Association` object, which you can then chain queries onto. `belongs_to_content` and `has_one_content` will define methods that returns a single MarkdownContent model.
63
63
 
64
+ ### Scopes
65
+
66
+ If a model is defined within a scope (using the `scope` Content DSL command), then all its associations are assumed to be within that scope, and must point to other models that are within that scope.
67
+
68
+ Furthermore, when querying for models that are defined within a scope without using associations, you will need to provide the scope like so:
69
+
70
+ ```ruby
71
+ DslCommand.find(1, "v_0_1_3")
72
+ => #<DslCommand description: "model is the main command...", scope: "v_0_1_3", scoped_id: "v_0_1_3:s:1", section_id: 3, subdirectory: "content", type: "dsl_command">
73
+ ```
74
+
75
+ Or
76
+
77
+ ```ruby
78
+ DslCommand.where(:section_id => 3, :scope => "v_0_1_3").all
79
+ => [#<DslCommand >,...]
80
+ ````
81
+
82
+ Association methods do not need the `scope` provided, since they are assumed to be in the same scope as the model instance you call them on.
83
+
64
84
  ### Associations on ActiveRecord Models
65
85
 
66
86
  You can easily integrate MarkdownRecord and ActiveRecord models together using the `MarkdownRecord::ContentAssociations` module. Just include it in your ActiveRecord model like so:
@@ -1,5 +1,5 @@
1
- <!--fragment { "author": "Bryant Morrill", "parent_id": "content/home", "name": "Controller Helpers" } -->
2
- <!---model { "type": "markdown_record/demo/section", "id": 9, "name": "Controller Helpers" } -->
1
+ <!--fragment { "author": "Bryant Morrill", "relative_parent_id": "home", "name": "Controller Helpers" } -->
2
+ <!--model { "type": "section", "id": 10, "name": "Controller Helpers" } -->
3
3
 
4
4
  # Controller Helpers
5
5
 
@@ -1,5 +1,5 @@
1
- <!--fragment { "author": "Bryant Morrill", "parent_id": "content/home", "name": "Configuration" } -->
2
- <!---model { "type": "markdown_record/demo/section", "id": 9, "name": "Configuration" } -->
1
+ <!--fragment { "author": "Bryant Morrill", "relative_parent_id": "home", "name": "Configuration" } -->
2
+ <!--model { "type": "section", "id": 11, "name": "Configuration" } -->
3
3
 
4
4
  # Configuration
5
5
 
@@ -23,4 +23,23 @@ You can configure `MarkdownRecord` in the initializer that is generated upon ins
23
23
  - `mount_path`: the path where the MarkdownRecord engine is mounted inside the host application. It is `mdr` by default.
24
24
  - `render_content_fragment_json`: a boolean value that determines whether MarkdownRecord renders content fragments and saves them to the `rendered_content_root` in JSON files with the `_fragments` suffix. If this is set to `false`, then MarkdownRecord will render your markdown content each time MarkdownRecord models queries are executed. This is set to `true` by default.
25
25
  - `render_controller`: the controller used to render ERB content during the rendering process. If this isn't set, then the host application's `ApplicationController` will be used. By default this is `nil`.
26
- - `ignore_numeric_prefix`: configures whether or not MarkdownRecord ignores the numeric prefixes of filenames. The default value is `true`. When it is set to `false`, the numeric prefixes will be included in the `filename` attribute of models, the `id` attribute of content fragments, and the `content_path` parameter of requests to the routes for rendered content.
26
+ - `ignore_numeric_prefix`: configures whether or not MarkdownRecord ignores the numeric prefixes of filenames. The default value is `true`. When it is set to `false`, the numeric prefixes will be included in the `filename` attribute of models, the `id` attribute of content fragments, and the `content_path` parameter of requests to the routes for rendered content.
27
+ - `filename_sorter`: the file sorting instance to be used to organized concatenated content. The default value is `MarkdownRecord::FileSorting::Base.new`
28
+
29
+ ## File sorting
30
+
31
+ MarkdownRecord uses an instance of `MarkdownRecord::FileSorting::Base` by default to sort files in order to determine concatenation order of the markdown content it renders. This class is also responsible for trimming the numeric prefix prefixes off filenames. However, two additional classes are available for this purpose to allow the use of different kinds of prefixes and sort orders. They are:
32
+
33
+ - `MarkdownRecord::FileSorting::DateSorter`: this class sorts files by a date prefix. By default it expects to find a prefix matching the regular expression: `/^(\d\d\d\d?_\d\d?_\d\d?)/` and parses that prefix using the data pattern: `"%Y_%m_%d"`. However you can override both of these upon instantiation like so:
34
+
35
+ ```ruby
36
+ MarkdownRecord::FileSorting::DateSorter.new(<regexp>, <date_pattern_string>)
37
+ ````
38
+
39
+ - `MarkdownRecord::FileSorting::SemVerSorter`: this class sorts files by a semantive version prefix. By default it expects to find a prefix matching the regular expression: `/(\d+(?:_\d+)*)(?:_|$)/`. This allows any number of version parts, including just a single number, which would make it functionally equivalent to the default file sorter `MarkdownRecord::FileSorting::Base`. However, you can override the regex used like so:
40
+
41
+ ```ruby
42
+ MarkdownRecord::FileSorting::SemVerSorter.new(<regexp>)
43
+ ````
44
+
45
+ You may also implement your own file sorter. It only needs to implement a `to_sort_value(value)` method which returns an object that responds to `<=>`.