lookbook 1.2.1 → 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +47 -14
  4. data/app/assets/lookbook/css/themes/blue.css +4 -2
  5. data/app/assets/lookbook/css/themes/green.css +66 -0
  6. data/app/assets/lookbook/css/themes/indigo.css +4 -2
  7. data/app/assets/lookbook/css/themes/rose.css +66 -0
  8. data/app/assets/lookbook/css/themes/zinc.css +4 -2
  9. data/app/components/lookbook/base_component.rb +2 -2
  10. data/app/components/lookbook/code/component.css +2 -2
  11. data/app/components/lookbook/code/component.html.erb +3 -2
  12. data/app/components/lookbook/code/component.rb +13 -2
  13. data/app/components/lookbook/code/highlight_github.css +406 -0
  14. data/app/components/lookbook/header/component.html.erb +1 -1
  15. data/app/components/lookbook/inspector_panel/component.rb +4 -6
  16. data/app/components/lookbook/nav/component.rb +8 -15
  17. data/app/components/lookbook/nav/directory/component.html.erb +28 -0
  18. data/app/components/lookbook/nav/directory/component.rb +4 -0
  19. data/app/components/lookbook/nav/{item → entity}/component.html.erb +8 -8
  20. data/app/components/lookbook/nav/entity/component.rb +49 -0
  21. data/app/components/lookbook/nav/item/component.css +15 -0
  22. data/app/components/lookbook/nav/item/component.js +4 -0
  23. data/app/components/lookbook/nav/item/component.rb +13 -56
  24. data/app/components/lookbook/params/editor/component.html.erb +2 -2
  25. data/app/components/lookbook/params/editor/component.rb +3 -10
  26. data/app/components/lookbook/params/field/component.css +3 -3
  27. data/app/components/lookbook/params/field/component.html.erb +8 -8
  28. data/app/components/lookbook/params/field/component.rb +21 -72
  29. data/app/components/lookbook/split_layout/component.html.erb +1 -1
  30. data/app/components/lookbook/tabs/component.html.erb +1 -1
  31. data/app/components/lookbook/tabs/component.js +4 -0
  32. data/app/components/lookbook/tag_component.rb +1 -1
  33. data/app/components/lookbook/viewport/component.css +1 -1
  34. data/app/components/lookbook/viewport/component.html.erb +1 -1
  35. data/app/components/lookbook/viewport/component.rb +1 -1
  36. data/app/controllers/concerns/lookbook/targetable_concern.rb +131 -0
  37. data/app/controllers/concerns/lookbook/with_preview_controller_concern.rb +13 -0
  38. data/app/controllers/lookbook/application_controller.rb +21 -9
  39. data/app/controllers/lookbook/inspector_controller.rb +45 -0
  40. data/app/controllers/lookbook/page_controller.rb +13 -9
  41. data/app/controllers/lookbook/pages_controller.rb +9 -15
  42. data/app/controllers/lookbook/previews_controller.rb +4 -210
  43. data/app/helpers/lookbook/application_helper.rb +2 -2
  44. data/app/helpers/lookbook/output_helper.rb +5 -5
  45. data/app/helpers/lookbook/page_helper.rb +7 -4
  46. data/app/views/layouts/lookbook/application.html.erb +40 -38
  47. data/app/views/layouts/lookbook/page.html.erb +2 -2
  48. data/app/views/layouts/lookbook/shell.html.erb +3 -2
  49. data/app/views/layouts/lookbook/skeleton.html.erb +7 -7
  50. data/app/views/lookbook/index.html.erb +13 -2
  51. data/app/views/lookbook/{previews → inspector}/inputs/_color.html.erb +0 -0
  52. data/app/views/lookbook/{previews → inspector}/inputs/_range.html.erb +0 -0
  53. data/app/views/lookbook/{previews → inspector}/inputs/_select.html.erb +0 -0
  54. data/app/views/lookbook/{previews → inspector}/inputs/_text.html.erb +0 -0
  55. data/app/views/lookbook/{previews → inspector}/inputs/_textarea.html.erb +0 -0
  56. data/app/views/lookbook/{previews → inspector}/inputs/_toggle.html.erb +5 -5
  57. data/app/views/lookbook/{previews → inspector}/panels/_content.html.erb +0 -0
  58. data/app/views/lookbook/{previews → inspector}/panels/_notes.html.erb +2 -2
  59. data/app/views/lookbook/{previews → inspector}/panels/_output.html.erb +0 -0
  60. data/app/views/lookbook/inspector/panels/_params.html.erb +15 -0
  61. data/app/views/lookbook/{previews → inspector}/panels/_preview.html.erb +0 -0
  62. data/app/views/lookbook/{previews → inspector}/panels/_source.html.erb +0 -0
  63. data/app/views/lookbook/{previews → inspector}/show.html.erb +5 -2
  64. data/config/app.yml +11 -1
  65. data/config/inputs.yml +12 -12
  66. data/config/languages.yml +41 -0
  67. data/config/panels.yml +6 -6
  68. data/config/routes.rb +5 -5
  69. data/config/tags.yml +8 -1
  70. data/lib/lookbook/engine.rb +103 -130
  71. data/lib/lookbook/entities/collections/component_collection.rb +4 -0
  72. data/lib/lookbook/entities/collections/concerns/hierarchical_collection.rb +23 -0
  73. data/lib/lookbook/entities/collections/entity_collection.rb +61 -0
  74. data/lib/lookbook/entities/collections/page_collection.rb +30 -0
  75. data/lib/lookbook/entities/collections/preview_collection.rb +41 -0
  76. data/lib/lookbook/entities/collections/preview_example_collection.rb +4 -0
  77. data/lib/lookbook/entities/component.rb +31 -0
  78. data/lib/lookbook/entities/concerns/annotatable.rb +58 -0
  79. data/lib/lookbook/entities/concerns/inspectable.rb +44 -0
  80. data/lib/lookbook/entities/concerns/locatable.rb +73 -0
  81. data/lib/lookbook/entities/concerns/navigable.rb +43 -0
  82. data/lib/lookbook/entities/entity.rb +53 -0
  83. data/lib/lookbook/entities/page.rb +80 -0
  84. data/lib/lookbook/entities/page_section.rb +43 -0
  85. data/lib/lookbook/entities/preview.rb +87 -0
  86. data/lib/lookbook/entities/preview_example.rb +100 -0
  87. data/lib/lookbook/entities/preview_group.rb +48 -0
  88. data/lib/lookbook/file_watcher.rb +47 -0
  89. data/lib/lookbook/lang.rb +12 -35
  90. data/lib/lookbook/param.rb +99 -0
  91. data/lib/lookbook/{preview_controller.rb → preview_actions.rb} +14 -3
  92. data/lib/lookbook/preview_parser.rb +53 -0
  93. data/lib/lookbook/process.rb +21 -0
  94. data/lib/lookbook/rendered_example.rb +37 -0
  95. data/lib/lookbook/services/code/code_beautifier.rb +21 -0
  96. data/lib/lookbook/services/code/code_highlighter.rb +69 -0
  97. data/lib/lookbook/services/code/code_indenter.rb +14 -0
  98. data/lib/lookbook/services/data/parsers/data_parser.rb +22 -0
  99. data/lib/lookbook/services/data/parsers/json_parser.rb +7 -0
  100. data/lib/lookbook/services/data/parsers/yaml_parser.rb +7 -0
  101. data/lib/lookbook/services/data/resolvers/data_resolver.rb +70 -0
  102. data/lib/lookbook/services/data/resolvers/eval_resolver.rb +10 -0
  103. data/lib/lookbook/services/data/resolvers/file_resolver.rb +28 -0
  104. data/lib/lookbook/services/data/resolvers/method_resolver.rb +10 -0
  105. data/lib/lookbook/services/data/resolvers/yaml_resolver.rb +18 -0
  106. data/lib/lookbook/services/entities/entity_tree_builder.rb +45 -0
  107. data/lib/lookbook/services/markdown_renderer.rb +29 -0
  108. data/lib/lookbook/services/position_prefix_parser.rb +16 -0
  109. data/lib/lookbook/services/string_value_caster.rb +60 -0
  110. data/lib/lookbook/services/tags/tag_options_parser.rb +62 -0
  111. data/lib/lookbook/services/templates/action_view_annotations_handler.rb +21 -0
  112. data/lib/lookbook/services/templates/action_view_annotations_stripper.rb +15 -0
  113. data/lib/lookbook/services/templates/frontmatter_extractor.rb +28 -0
  114. data/lib/lookbook/services/templates/styles_extractor.rb +38 -0
  115. data/lib/lookbook/services/{search_param_builder.rb → urls/search_param_builder.rb} +1 -1
  116. data/lib/lookbook/services/{search_param_parser.rb → urls/search_param_parser.rb} +1 -1
  117. data/lib/lookbook/stores/config_store.rb +12 -9
  118. data/lib/lookbook/stores/input_store.rb +7 -3
  119. data/lib/lookbook/stores/panel_store.rb +2 -2
  120. data/lib/lookbook/stores/tag_store.rb +3 -5
  121. data/lib/lookbook/support/null_object.rb +10 -0
  122. data/lib/lookbook/support/service.rb +2 -2
  123. data/lib/lookbook/support/store.rb +2 -35
  124. data/lib/lookbook/support/tree_node.rb +87 -0
  125. data/lib/lookbook/support/utils/path_utils.rb +32 -5
  126. data/lib/lookbook/support/utils/utils.rb +24 -0
  127. data/lib/lookbook/tags/component_tag.rb +13 -0
  128. data/lib/lookbook/tags/custom_tag.rb +61 -0
  129. data/lib/lookbook/tags/display_tag.rb +15 -0
  130. data/lib/lookbook/tags/hidden_tag.rb +13 -0
  131. data/lib/lookbook/tags/id_tag.rb +7 -0
  132. data/lib/lookbook/tags/label_tag.rb +4 -0
  133. data/lib/lookbook/tags/logical_path_tag.rb +7 -0
  134. data/lib/lookbook/tags/param_tag.rb +63 -0
  135. data/lib/lookbook/tags/position_tag.rb +16 -0
  136. data/lib/lookbook/tags/source_tag.rb +7 -0
  137. data/lib/lookbook/tags/tag_provider.rb +18 -0
  138. data/lib/lookbook/tags/yard_tag.rb +90 -0
  139. data/lib/lookbook/theme.rb +8 -0
  140. data/lib/lookbook/version.rb +1 -1
  141. data/lib/lookbook/websocket.rb +60 -0
  142. data/lib/lookbook.rb +13 -8
  143. data/public/lookbook-assets/css/lookbook.css +487 -411
  144. data/public/lookbook-assets/css/lookbook.css.map +1 -1
  145. data/public/lookbook-assets/css/themes/blue.css +3 -1
  146. data/public/lookbook-assets/css/themes/blue.css.map +1 -1
  147. data/public/lookbook-assets/css/themes/green.css +68 -0
  148. data/public/lookbook-assets/css/themes/green.css.map +1 -0
  149. data/public/lookbook-assets/css/themes/indigo.css +3 -1
  150. data/public/lookbook-assets/css/themes/indigo.css.map +1 -1
  151. data/public/lookbook-assets/css/themes/rose.css +68 -0
  152. data/public/lookbook-assets/css/themes/rose.css.map +1 -0
  153. data/public/lookbook-assets/css/themes/zinc.css +3 -1
  154. data/public/lookbook-assets/css/themes/zinc.css.map +1 -1
  155. data/public/lookbook-assets/js/embed.js +10 -1
  156. data/public/lookbook-assets/js/embed.js.map +1 -1
  157. data/public/lookbook-assets/js/lookbook.js +358 -629
  158. data/public/lookbook-assets/js/lookbook.js.map +1 -1
  159. metadata +96 -44
  160. data/app/components/lookbook/code/highlight_github_light.css +0 -217
  161. data/app/views/lookbook/previews/panels/_params.html.erb +0 -15
  162. data/lib/lookbook/code_formatter.rb +0 -68
  163. data/lib/lookbook/collection.rb +0 -161
  164. data/lib/lookbook/component.rb +0 -34
  165. data/lib/lookbook/entity.rb +0 -47
  166. data/lib/lookbook/markdown.rb +0 -22
  167. data/lib/lookbook/page.rb +0 -195
  168. data/lib/lookbook/page_collection.rb +0 -19
  169. data/lib/lookbook/page_section.rb +0 -29
  170. data/lib/lookbook/params.rb +0 -157
  171. data/lib/lookbook/parser.rb +0 -42
  172. data/lib/lookbook/preview.rb +0 -174
  173. data/lib/lookbook/preview_collection.rb +0 -23
  174. data/lib/lookbook/preview_example.rb +0 -93
  175. data/lib/lookbook/preview_group.rb +0 -62
  176. data/lib/lookbook/source_inspector.rb +0 -95
  177. data/lib/lookbook/support/utils/attribute_utils.rb +0 -9
  178. data/lib/lookbook/tag.rb +0 -122
  179. data/lib/lookbook/tag_options.rb +0 -111
  180. data/lib/lookbook/tags.rb +0 -17
  181. data/lib/lookbook/template_parser.rb +0 -72
  182. data/lib/lookbook/utils.rb +0 -105
@@ -0,0 +1,48 @@
1
+ module Lookbook
2
+ class PreviewGroup < Entity
3
+ include Navigable
4
+
5
+ attr_reader :examples, :preview
6
+
7
+ def initialize(name, examples, preview)
8
+ @name = Utils.name(name)
9
+ @examples = PreviewExampleCollection.new(examples)
10
+ @preview = preview
11
+ @lookup_path = "#{parent.lookup_path}/#{@name}"
12
+ end
13
+
14
+ def display_options
15
+ merged = {}
16
+ examples.to_a.reverse.map do |example|
17
+ merged.merge!(example.display_options)
18
+ end
19
+ merged
20
+ end
21
+
22
+ def components
23
+ @_components ||= ComponentCollection.new(examples.flat_map(&:components).uniq(&:path))
24
+ end
25
+
26
+ def search_terms
27
+ [parent.label, label]
28
+ end
29
+
30
+ def tags(tag_name = nil)
31
+ examples.flat_map { |example| example.tags(tag_name) }
32
+ end
33
+
34
+ def tag(tag_name = nil)
35
+ tags(tag_name).first
36
+ end
37
+
38
+ def url_path
39
+ lookbook_inspect_path(path)
40
+ end
41
+
42
+ def type
43
+ :group
44
+ end
45
+
46
+ alias_method :parent, :preview
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ module Lookbook
2
+ class FileWatcher
3
+ attr_reader :listeners, :force_polling
4
+
5
+ def initialize(force_polling: false)
6
+ @force_polling = force_polling
7
+ @listeners = []
8
+ end
9
+
10
+ def watch(paths, extensions = ".*", opts = nil, &block)
11
+ paths = PathUtils.normalize_paths(paths)
12
+
13
+ if paths.any?
14
+ opts = opts.to_h
15
+ opts[:only] = /\.(#{Array(extensions).join("|")})$/
16
+
17
+ listeners << init_listener(paths, opts, &block)
18
+ end
19
+ end
20
+
21
+ def start
22
+ if listeners.any?
23
+ Lookbook.logger.debug "Starting listeners"
24
+ listeners.each { |l| l.start }
25
+ end
26
+ end
27
+
28
+ def stop
29
+ if listeners.any?
30
+ Lookbook.logger.debug "Stopping listeners"
31
+ listeners.each { |l| l.stop }
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def init_listener(paths, opts, &block)
38
+ Listen.to(
39
+ *paths,
40
+ **opts,
41
+ force_polling: force_polling
42
+ ) do |modified, added, removed|
43
+ block.call({modified: modified, added: added, removed: removed})
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/lookbook/lang.rb CHANGED
@@ -1,46 +1,23 @@
1
1
  module Lookbook
2
2
  module Lang
3
3
  class << self
4
- LANGUAGES = [
5
- {
6
- name: "ruby",
7
- ext: ".rb",
8
- label: "Ruby",
9
- comment: "# %s"
10
- },
11
- {
12
- name: "html",
13
- ext: ".html",
14
- label: "HTML",
15
- comment: "<!-- %s -->"
16
- },
17
- {
18
- name: "erb",
19
- ext: ".erb",
20
- label: "ERB",
21
- comment: "<%%# %s %%>"
22
- },
23
- {
24
- name: "haml",
25
- ext: ".haml",
26
- label: "Haml",
27
- comment: "<!-- %s -->"
28
- },
29
- {
30
- name: "slim",
31
- ext: ".slim",
32
- label: "Slim",
33
- comment: "<!-- %s -->"
34
- }
35
- ]
4
+ CONFIG_FILE = "config/languages.yml"
5
+ LANGUAGES = [] # retained for backwards compatability
36
6
 
37
7
  def find(name)
38
- LANGUAGES.find { |l| l[:name] == name.to_s }
8
+ languages.find { |l| l[:name] == name.to_s }
39
9
  end
40
10
 
41
- def guess(path)
11
+ def guess(path, fallback_name = nil)
42
12
  ext = File.extname(path)
43
- LANGUAGES.find { |l| l[:ext] == ext }
13
+ lang = languages.find { |l| l[:ext] == ext }
14
+ lang || (find(fallback_name) if fallback_name)
15
+ end
16
+
17
+ protected
18
+
19
+ def languages
20
+ @_languages ||= [*ConfigLoader.call(CONFIG_FILE).definitions, *LANGUAGES]
44
21
  end
45
22
  end
46
23
  end
@@ -0,0 +1,99 @@
1
+ module Lookbook
2
+ class Param
3
+ attr_reader :name, :options, :value_default, :description
4
+
5
+ def initialize(name:, input: nil, description: nil, value_type: nil, value_default: nil, value: nil, options: {})
6
+ @name = name
7
+ @input = input
8
+ @description = description
9
+ @value_type = value_type
10
+ @value_default = value_default
11
+ @value = value
12
+ @options = options
13
+ end
14
+
15
+ def label
16
+ options.label || name.titleize
17
+ end
18
+
19
+ def hint
20
+ options.hint
21
+ end
22
+
23
+ def input
24
+ @input || guess_input
25
+ end
26
+
27
+ def value
28
+ @value || value_default
29
+ end
30
+
31
+ def value_type
32
+ @value_type || guess_value_type
33
+ end
34
+
35
+ def input_options
36
+ return @_input_options if @_input_options
37
+
38
+ runtime_options = options.except([*methods, :name, :value_default, :description])
39
+ @_input_options ||= Store.new(input_config.options.merge(runtime_options))
40
+ end
41
+
42
+ def input_partial
43
+ input_config.partial
44
+ end
45
+
46
+ def cast_value
47
+ raise ArgumentError.new("Cannot cast param '#{name}' without a value set") if value.nil?
48
+
49
+ StringValueCaster.call(value, value_type)
50
+ end
51
+
52
+ def self.from_tag(tag, value: nil)
53
+ new(
54
+ name: tag.name,
55
+ input: tag.input || tag.options.input,
56
+ description: tag.description || tag.options.description,
57
+ value_type: tag.value_type || tag.options.value_type,
58
+ value_default: tag.value_default,
59
+ options: tag.options,
60
+ value: value
61
+ )
62
+ end
63
+
64
+ protected
65
+
66
+ def input_config
67
+ config = Lookbook::Engine.inputs.get_input(input)
68
+ config || raise(LookbookError.new("Unknown input type '#{input}'"))
69
+ end
70
+
71
+ def guess_input
72
+ if @value_type == "boolean" || (@value_type.blank? && boolean?(value_default))
73
+ "toggle"
74
+ else
75
+ "text"
76
+ end
77
+ end
78
+
79
+ def guess_value_type
80
+ if input == "toggle"
81
+ "boolean"
82
+ elsif input == "number"
83
+ "integer"
84
+ elsif boolean?(value_default)
85
+ "boolean"
86
+ elsif value_default.is_a?(Symbol)
87
+ "symbol"
88
+ elsif ["date", "datetime-local"].include?(input) || value_default.is_a?(DateTime)
89
+ "datetime"
90
+ else
91
+ "string"
92
+ end
93
+ end
94
+
95
+ def boolean?(value)
96
+ value == true || value == false
97
+ end
98
+ end
99
+ end
@@ -1,5 +1,9 @@
1
1
  module Lookbook
2
- module PreviewController
2
+ module PreviewActions
3
+ def self.included(klass)
4
+ klass.helper Lookbook::PreviewHelper
5
+ end
6
+
3
7
  def render_example_to_string(preview, example_name)
4
8
  prepend_application_view_paths
5
9
  prepend_preview_examples_view_path
@@ -12,7 +16,7 @@ module Lookbook
12
16
  opts[:layout] = nil
13
17
  opts[:locals] = locals if locals.present?
14
18
 
15
- Utils.with_optional_action_view_annotations do
19
+ with_optional_action_view_annotations do
16
20
  render html: render_to_string(template, **opts)
17
21
  end
18
22
  end
@@ -20,7 +24,7 @@ module Lookbook
20
24
  def render_in_layout_to_string(template, locals, opts = {})
21
25
  append_view_path Lookbook::Engine.root.join("app/views")
22
26
 
23
- Utils.with_optional_action_view_annotations do
27
+ with_optional_action_view_annotations do
24
28
  html = render_to_string(template, locals: locals, **determine_layout(opts[:layout]))
25
29
  if opts[:append_html].present?
26
30
  html += opts[:append_html]
@@ -28,5 +32,12 @@ module Lookbook
28
32
  render html: html
29
33
  end
30
34
  end
35
+
36
+ protected
37
+
38
+ def with_optional_action_view_annotations(&block)
39
+ disable = Lookbook.config.preview_disable_action_view_annotations
40
+ ActionViewAnnotationsHandler.call(disable_annotations: disable, &block)
41
+ end
31
42
  end
32
43
  end
@@ -0,0 +1,53 @@
1
+ require "yard"
2
+
3
+ module Lookbook
4
+ class PreviewParser
5
+ def initialize(paths, tags = nil)
6
+ @paths = paths
7
+ @after_parse_callbacks = []
8
+ @after_parse_once_callbacks = []
9
+ @parsing = false
10
+
11
+ define_tags(tags)
12
+ YARD::Parser::SourceParser.after_parse_list { run_callbacks }
13
+ end
14
+
15
+ def parse(&block)
16
+ unless @parsing
17
+ @parsing = true
18
+ @after_parse_once_callbacks << block if block
19
+ YARD::Registry.clear
20
+ YARD.parse(paths)
21
+ end
22
+ end
23
+
24
+ def after_parse(&block)
25
+ @after_parse_callbacks << block
26
+ end
27
+
28
+ def paths
29
+ PathUtils.normalize_paths(@paths).map { |path| "#{path}/**/*preview.rb" }
30
+ end
31
+
32
+ protected
33
+
34
+ def callbacks
35
+ [
36
+ *@after_parse_callbacks,
37
+ *@after_parse_once_callbacks
38
+ ]
39
+ end
40
+
41
+ def run_callbacks
42
+ callbacks.each { |cb| cb.call(YARD::Registry) }
43
+ @after_parse_once_callbacks = []
44
+ @parsing = false
45
+ end
46
+
47
+ def define_tags(tags = nil)
48
+ tags.to_h.each do |name, tag|
49
+ YARD::Tags::Library.define_tag(tag[:label], name, Lookbook::TagProvider)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ module Lookbook
2
+ class Process
3
+ attr_reader :env
4
+
5
+ def initialize(env: Rails.env)
6
+ @env = env
7
+ end
8
+
9
+ def supports_listening?
10
+ !rake_task? && !Rails.const_defined?(:Console)
11
+ end
12
+
13
+ def rake_task?
14
+ if defined?(Rake) && Rake.respond_to?(:application)
15
+ File.basename($0) == "rake" || Rake.application.top_level_tasks.any?
16
+ else
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module Lookbook
2
+ class RenderedExample
3
+ delegate_missing_to :example
4
+
5
+ attr_reader :output, :example
6
+
7
+ def initialize(example, output, params)
8
+ @example = example
9
+ @params = params
10
+ @output = output
11
+ end
12
+
13
+ def source
14
+ has_custom_template? ? template_source(template) : example.source
15
+ end
16
+
17
+ def source_lang
18
+ has_custom_template? ? template_lang(template) : example.source_lang
19
+ end
20
+
21
+ protected
22
+
23
+ attr_reader :params
24
+
25
+ def render_args
26
+ @_render_args ||= preview.render_args(example.name, params: params)
27
+ end
28
+
29
+ def template
30
+ render_args[:template]
31
+ end
32
+
33
+ def has_custom_template?
34
+ template != "view_components/preview" && !custom_source?
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ require "htmlbeautifier"
2
+
3
+ module Lookbook
4
+ class CodeBeautifier < Service
5
+ attr_reader :source, :opts
6
+
7
+ def initialize(source, opts = {})
8
+ @source = source.to_s
9
+ @opts = opts
10
+ end
11
+
12
+ def call
13
+ language = opts.fetch(:language, "html")
14
+ stripped_source = source.strip
15
+ result = (language.downcase == "html") ? HtmlBeautifier.beautify(stripped_source, opts) : stripped_source
16
+ result.to_s.strip.html_safe
17
+ rescue
18
+ source.strip.html_safe
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,69 @@
1
+ require "rouge"
2
+ require "htmlentities"
3
+
4
+ module Lookbook
5
+ class CodeHighlighter < Service
6
+ attr_reader :source
7
+
8
+ def initialize(source, opts = {})
9
+ @source = source.to_s
10
+ @opts = opts
11
+ end
12
+
13
+ def opts
14
+ (@opts.is_a?(String) || @opts.is_a?(Symbol)) ? {language: @opts} : @opts.to_h
15
+ end
16
+
17
+ def call
18
+ coder = HTMLEntities.new
19
+ decoded_source = coder.decode(source)
20
+ language = opts.fetch(:language, "ruby")
21
+ formatter = LookbookFormatter.new(language: language, **opts)
22
+ lexer = Rouge::Lexer.find(language.to_s) || Rouge::Lexer.find("plaintext")
23
+ formatter.format(lexer.lex(decoded_source)).html_safe
24
+ end
25
+
26
+ class LookbookFormatter < Rouge::Formatters::HTML
27
+ def initialize(**opts)
28
+ @opts = opts
29
+ @highlight_lines = opts[:highlight_lines].to_a || []
30
+ @start_line = opts[:start_line] || 1
31
+ @language = opts[:language]
32
+ end
33
+
34
+ def stream(tokens, &block)
35
+ lines = token_lines(tokens)
36
+
37
+ yield "<div class='wrapper'>"
38
+
39
+ if @opts[:line_numbers]
40
+ yield "<div class='line-numbers'>"
41
+ lines.each.with_index do |line, i|
42
+ yield "<div class='line #{"highlighted" if highlighted?(i)}'><span class='line-number'>#{line_number(i)}</span></div>"
43
+ end
44
+ yield "</div>"
45
+ end
46
+
47
+ yield "<pre class='code highlight' data-lang='#{@language}'><code>"
48
+ lines.each.with_index do |line_tokens, i|
49
+ yield "<div class='line#{" highlighted" if highlighted?(i)}'>"
50
+ line_tokens.each do |token, value|
51
+ yield span(token, value)
52
+ end
53
+ yield "</div>"
54
+ end
55
+ yield "</code></pre>"
56
+
57
+ yield "</div>"
58
+ end
59
+
60
+ def highlighted?(i)
61
+ @highlight_lines.include?(i + 1)
62
+ end
63
+
64
+ def line_number(i)
65
+ @start_line + i
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,14 @@
1
+ module Lookbook
2
+ class CodeIndenter < Service
3
+ def initialize(source)
4
+ @source = source.to_s
5
+ end
6
+
7
+ def call
8
+ source = @source.chomp
9
+ last = source.split(/\r?\n/).last
10
+ indent = last ? last[/^([ \t]*)/, 1].length : 0
11
+ source.gsub(/^[ \t]{#{indent}}/, "")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Lookbook
2
+ class DataParser < Service
3
+ def initialize(input, fail_silently: false, fallback: nil)
4
+ @input = input
5
+ @fail_silently = fail_silently
6
+ @fallback = fallback
7
+ end
8
+
9
+ def call
10
+ result = @input.present? ? parse(@input) : @fallback
11
+ result.is_a?(Hash) ? result.deep_symbolize_keys : result
12
+ rescue => exception
13
+ @fail_silently ? @fallback : raise(exception)
14
+ end
15
+
16
+ protected
17
+
18
+ def parse(input)
19
+ raise ParserError.new "DataParser must be subclassed with a :parse method defined"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module Lookbook
2
+ class JsonParser < DataParser
3
+ def parse(input)
4
+ JSON.parse(input.to_s)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Lookbook
2
+ class YamlParser < DataParser
3
+ def parse(input)
4
+ YAML.safe_load(input.to_s)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,70 @@
1
+ module Lookbook
2
+ class DataResolver < Service
3
+ MATCHER = /(?!.*)/
4
+ MATCH_INDEX = 1
5
+
6
+ attr_reader :eval_context, :base_dir, :file, :fallback
7
+
8
+ def initialize(input, eval_context: nil, permit_eval: false, fail_silently: false, base_dir: Rails.root, file: nil, fallback: nil)
9
+ @input = input.to_s
10
+ @eval_context = eval_context
11
+ @permit_eval = permit_eval
12
+ @fail_silently = fail_silently
13
+ @fallback = fallback
14
+ @base_dir = base_dir.to_s
15
+ @file = file.to_s
16
+ end
17
+
18
+ def call
19
+ resolve extract(@input)
20
+ rescue => exception
21
+ Lookbook.logger.debug "Data resolution failed. (Input: '#{@input}')"
22
+ @fail_silently ? fallback : raise(exception)
23
+ end
24
+
25
+ def self.resolveable?(input)
26
+ input.to_s.match?(self::MATCHER)
27
+ end
28
+
29
+ protected
30
+
31
+ def extract(input)
32
+ match_data = input.match(self.class::MATCHER)
33
+ if match_data.nil?
34
+ raise_error "Invalid data '#{input}'"
35
+ else
36
+ match_data[self.class::MATCH_INDEX]
37
+ end
38
+ end
39
+
40
+ def resolve(input)
41
+ raise ParserError.new "OptionsResolver must be subclassed with a :resolve method defined"
42
+ end
43
+
44
+ def evaluate(input, fallback = @fallback)
45
+ if evaluatable?
46
+ begin
47
+ eval_context.instance_eval(input.to_s)
48
+ rescue => exception
49
+ raise_error "Could not evaluate statetment (#{exception.message})", exception
50
+ end
51
+ else
52
+ Lookbook.logger.debug "Data cannot be evaluated (Input: '#{input}')"
53
+ fallback
54
+ end
55
+ end
56
+
57
+ def raise_error(message, original_exception = nil)
58
+ raise ParserError.new message, original: original_exception, scope: "resolvers"
59
+ end
60
+
61
+ private
62
+
63
+ def evaluatable?
64
+ if !@permit_eval
65
+ raise_error "Runtime evaluation is not permitted"
66
+ end
67
+ eval_context.present?
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,10 @@
1
+ module Lookbook
2
+ class EvalResolver < DataResolver
3
+ MATCHER = /(\{\{\s?(.*)\s?\}\})$/
4
+ MATCH_INDEX = 2
5
+
6
+ def resolve(input)
7
+ evaluate(input)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module Lookbook
2
+ class FileResolver < DataResolver
3
+ MATCHER = /(\S+\.(json|yml))$/
4
+ MATCH_INDEX = 1
5
+
6
+ protected
7
+
8
+ def resolve(input)
9
+ path = resolve_path(input, base_dir)
10
+ content = read_file(path)
11
+
12
+ case path.extname
13
+ when ".json"
14
+ JsonParser.call(content)
15
+ when ".yml"
16
+ YamlParser.call(content)
17
+ end
18
+ end
19
+
20
+ def read_file(path)
21
+ File.exist?(path.to_s) ? File.read(path) : raise_error("The data file at '#{path}' could not be found")
22
+ end
23
+
24
+ def resolve_path(path, base_dir)
25
+ Pathname(path.start_with?(".") ? File.expand_path(path, base_dir) : Rails.root.join(path))
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ module Lookbook
2
+ class MethodResolver < DataResolver
3
+ MATCHER = /(:{1}([a-zA-Z_\d]+))$/
4
+ MATCH_INDEX = 2
5
+
6
+ def resolve(input)
7
+ evaluate(input)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module Lookbook
2
+ class YamlResolver < DataResolver
3
+ MATCHER = /((?:\{|\[)(.*?)(?:\]|\}))$/m
4
+ MATCH_INDEX = 1
5
+
6
+ def self.resolveable?(input)
7
+ input.to_s.match?(MATCHER) && YamlParser.call(input, fail_silently: true)
8
+ end
9
+
10
+ protected
11
+
12
+ def resolve(input)
13
+ YamlParser.call(input)
14
+ rescue Psych::SyntaxError => exception
15
+ raise_error "YAML parse error (#{exception}) in '#{file}'", exception
16
+ end
17
+ end
18
+ end