lookbook 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +47 -14
- data/app/components/lookbook/code/component.html.erb +1 -1
- data/app/components/lookbook/inspector_panel/component.rb +3 -5
- data/app/components/lookbook/nav/item/component.html.erb +1 -1
- data/app/components/lookbook/params/editor/component.rb +3 -10
- data/app/components/lookbook/params/field/component.html.erb +8 -8
- data/app/components/lookbook/params/field/component.rb +21 -72
- data/app/controllers/concerns/lookbook/targetable_concern.rb +156 -0
- data/app/controllers/concerns/lookbook/with_preview_controller_concern.rb +13 -0
- data/app/controllers/lookbook/application_controller.rb +13 -3
- data/app/controllers/lookbook/inspector_controller.rb +45 -0
- data/app/controllers/lookbook/page_controller.rb +11 -7
- data/app/controllers/lookbook/previews_controller.rb +4 -210
- data/app/helpers/lookbook/output_helper.rb +5 -5
- data/app/views/layouts/lookbook/skeleton.html.erb +3 -3
- data/app/views/lookbook/index.html.erb +12 -1
- data/app/views/lookbook/{previews → inspector}/inputs/_color.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/inputs/_range.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/inputs/_select.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/inputs/_text.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/inputs/_textarea.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/inputs/_toggle.html.erb +3 -3
- data/app/views/lookbook/{previews → inspector}/panels/_content.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/panels/_notes.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/panels/_output.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/panels/_params.html.erb +4 -4
- data/app/views/lookbook/{previews → inspector}/panels/_preview.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/panels/_source.html.erb +0 -0
- data/app/views/lookbook/{previews → inspector}/show.html.erb +4 -1
- data/config/app.yml +8 -1
- data/config/inputs.yml +12 -12
- data/config/panels.yml +5 -5
- data/config/routes.rb +5 -5
- data/config/tags.yml +4 -1
- data/lib/lookbook/engine.rb +101 -150
- data/lib/lookbook/file_watcher.rb +47 -0
- data/lib/lookbook/page.rb +15 -16
- data/lib/lookbook/param.rb +99 -0
- data/lib/lookbook/preview.rb +8 -1
- data/lib/lookbook/{preview_controller.rb → preview_actions.rb} +14 -3
- data/lib/lookbook/preview_example.rb +1 -1
- data/lib/lookbook/preview_group.rb +0 -4
- data/lib/lookbook/preview_parser.rb +53 -0
- data/lib/lookbook/process.rb +21 -0
- data/lib/lookbook/services/code/code_beautifier.rb +21 -0
- data/lib/lookbook/services/code/code_highlighter.rb +69 -0
- data/lib/lookbook/services/data/parsers/data_parser.rb +22 -0
- data/lib/lookbook/services/data/parsers/json_parser.rb +7 -0
- data/lib/lookbook/services/data/parsers/yaml_parser.rb +7 -0
- data/lib/lookbook/services/data/resolvers/data_resolver.rb +70 -0
- data/lib/lookbook/services/data/resolvers/eval_resolver.rb +10 -0
- data/lib/lookbook/services/data/resolvers/file_resolver.rb +28 -0
- data/lib/lookbook/services/data/resolvers/method_resolver.rb +10 -0
- data/lib/lookbook/services/data/resolvers/yaml_resolver.rb +18 -0
- data/lib/lookbook/services/markdown_renderer.rb +29 -0
- data/lib/lookbook/services/string_value_caster.rb +60 -0
- data/lib/lookbook/services/tags/tag_options_parser.rb +62 -0
- data/lib/lookbook/services/templates/action_view_annotations_handler.rb +21 -0
- data/lib/lookbook/services/templates/action_view_annotations_stripper.rb +15 -0
- data/lib/lookbook/services/templates/frontmatter_extractor.rb +28 -0
- data/lib/lookbook/services/templates/styles_extractor.rb +38 -0
- data/lib/lookbook/services/{search_param_builder.rb → urls/search_param_builder.rb} +1 -1
- data/lib/lookbook/services/{search_param_parser.rb → urls/search_param_parser.rb} +1 -1
- data/lib/lookbook/source_inspector.rb +26 -45
- data/lib/lookbook/stores/config_store.rb +7 -8
- data/lib/lookbook/stores/input_store.rb +7 -3
- data/lib/lookbook/stores/tag_store.rb +3 -5
- data/lib/lookbook/support/null_object.rb +10 -0
- data/lib/lookbook/support/service.rb +2 -2
- data/lib/lookbook/support/store.rb +1 -1
- data/lib/lookbook/support/utils/attribute_utils.rb +6 -1
- data/lib/lookbook/support/utils/path_utils.rb +6 -3
- data/lib/lookbook/tags/component_tag.rb +7 -0
- data/lib/lookbook/tags/custom_tag.rb +59 -0
- data/lib/lookbook/tags/display_tag.rb +15 -0
- data/lib/lookbook/tags/hidden_tag.rb +13 -0
- data/lib/lookbook/tags/id_tag.rb +7 -0
- data/lib/lookbook/tags/label_tag.rb +4 -0
- data/lib/lookbook/tags/logical_path.rb +4 -0
- data/lib/lookbook/tags/param_tag.rb +61 -0
- data/lib/lookbook/tags/position_tag.rb +16 -0
- data/lib/lookbook/tags/tag_provider.rb +18 -0
- data/lib/lookbook/tags/yard_tag.rb +62 -0
- data/lib/lookbook/utils.rb +0 -40
- data/lib/lookbook/version.rb +1 -1
- data/lib/lookbook/websocket.rb +60 -0
- data/lib/lookbook.rb +2 -1
- data/public/lookbook-assets/css/lookbook.css +30 -77
- data/public/lookbook-assets/css/lookbook.css.map +1 -1
- data/public/lookbook-assets/js/lookbook.js +7 -2
- data/public/lookbook-assets/js/lookbook.js.map +1 -1
- metadata +55 -26
- data/lib/lookbook/code_formatter.rb +0 -68
- data/lib/lookbook/markdown.rb +0 -22
- data/lib/lookbook/params.rb +0 -157
- data/lib/lookbook/parser.rb +0 -42
- data/lib/lookbook/tag.rb +0 -122
- data/lib/lookbook/tag_options.rb +0 -111
- data/lib/lookbook/tags.rb +0 -17
- data/lib/lookbook/template_parser.rb +0 -72
@@ -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,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,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
|
15
|
+
@file = file
|
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,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
|
+
path.start_with?(".") ? File.expand_path(path, base_dir) : Rails.root.join(path)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
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
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "redcarpet"
|
2
|
+
|
3
|
+
module Lookbook
|
4
|
+
class MarkdownRenderer < Service
|
5
|
+
attr_reader :text, :opts
|
6
|
+
|
7
|
+
def initialize(text, opts = {})
|
8
|
+
@text = text
|
9
|
+
@opts = opts.to_h
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
clean_text = ActionViewAnnotationsStripper.call(text)
|
14
|
+
md = Redcarpet::Markdown.new(LookbookMarkdownRenderer, opts)
|
15
|
+
md.render(clean_text).html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
class LookbookMarkdownRenderer < Redcarpet::Render::HTML
|
19
|
+
def block_code(code, language = "ruby")
|
20
|
+
line_numbers = language.to_s.end_with? "-numbered"
|
21
|
+
ApplicationController.render(Lookbook::Code::Component.new(**{
|
22
|
+
source: code,
|
23
|
+
language: language.to_s.chomp("-numbered"),
|
24
|
+
line_numbers: line_numbers
|
25
|
+
}), layout: nil)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "active_model"
|
2
|
+
|
3
|
+
module Lookbook
|
4
|
+
class StringValueCaster < Service
|
5
|
+
def initialize(value, type = "string")
|
6
|
+
@value = value.to_s
|
7
|
+
@type = type.to_s.downcase
|
8
|
+
@cast_method = :"cast_to_#{@type}"
|
9
|
+
|
10
|
+
unless respond_to?(@cast_method)
|
11
|
+
raise ArgumentError.new "'#{@type}' is not a valid value type to cast to."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
return @nil if @value.empty?
|
17
|
+
public_send(@cast_method)
|
18
|
+
rescue => exception
|
19
|
+
Lookbook.logger.debug "Failed to parse '#{@value}' into a '#{@type}' [#{exception}]"
|
20
|
+
raise exception
|
21
|
+
end
|
22
|
+
|
23
|
+
def cast_to_string
|
24
|
+
@value
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast_to_symbol
|
28
|
+
@value.delete_prefix(":").to_sym if @value.present?
|
29
|
+
end
|
30
|
+
|
31
|
+
def cast_to_hash
|
32
|
+
result = YamlParser.call(@value)
|
33
|
+
unless result.is_a?(Hash)
|
34
|
+
raise ParserError.new "'#{@value}' is not a YAML Hash"
|
35
|
+
end
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def cast_to_array
|
40
|
+
result = YamlParser.call(@value)
|
41
|
+
unless result.is_a?(Array)
|
42
|
+
raise ParserError.new "'#{@value}' is not a YAML Array"
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def cast_to_datetime
|
48
|
+
DateTime.parse(@value)
|
49
|
+
end
|
50
|
+
|
51
|
+
def active_model_cast
|
52
|
+
type_class = "ActiveModel::Type::#{@type.camelize}".constantize
|
53
|
+
type_class.new.cast(@value)
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :cast_to_boolean, :active_model_cast
|
57
|
+
alias_method :cast_to_integer, :active_model_cast
|
58
|
+
alias_method :cast_to_float, :active_model_cast
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Lookbook
|
2
|
+
class TagOptionsParser < Service
|
3
|
+
RESOLVERS = [
|
4
|
+
FileResolver,
|
5
|
+
MethodResolver,
|
6
|
+
EvalResolver,
|
7
|
+
YamlResolver
|
8
|
+
]
|
9
|
+
|
10
|
+
def initialize(input, resolver_opts = {})
|
11
|
+
@input = input.to_s.strip
|
12
|
+
@resolver_opts = resolver_opts
|
13
|
+
@fallback = resolver_opts.fetch(:fallback, {})
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
options_string, remaining_text = parse_input(@input)
|
18
|
+
resolved_options = resolver(options_string).call(options_string, **@resolver_opts)
|
19
|
+
options = prepare_options(resolved_options)
|
20
|
+
[options, remaining_text]
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def resolver(options_string)
|
26
|
+
if options_string.present?
|
27
|
+
handler = RESOLVERS.find { |r| r.resolveable?(options_string) }
|
28
|
+
if handler.nil?
|
29
|
+
Lookbook.logger.error "Invalid tag options string '#{options_string}'"
|
30
|
+
method(:fallback_resolver)
|
31
|
+
else
|
32
|
+
handler
|
33
|
+
end
|
34
|
+
else
|
35
|
+
method(:fallback_resolver)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def fallback_resolver(*args)
|
40
|
+
@fallback
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_input(input)
|
44
|
+
matchers.each_with_object(["", input]) do |matcher, result|
|
45
|
+
input.match(matcher) do |match_data|
|
46
|
+
result[0] = match_data[1] # options string
|
47
|
+
result[1] = input.gsub(matcher, "").strip # any remaining text
|
48
|
+
return result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def matchers
|
54
|
+
RESOLVERS.map { |r| r::MATCHER }
|
55
|
+
end
|
56
|
+
|
57
|
+
def prepare_options(options)
|
58
|
+
options = options.is_a?(Array) ? {choices: options} : options
|
59
|
+
options.is_a?(Hash) ? Store.new(options) : options
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Lookbook
|
2
|
+
class ActionViewAnnotationsHandler < Service
|
3
|
+
attr_reader :disable_annotations
|
4
|
+
|
5
|
+
def initialize(disable_annotations: true)
|
6
|
+
@disable_annotations = disable_annotations
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
if ActionView::Base.respond_to?(:annotate_rendered_view_with_filenames) && disable_annotations
|
11
|
+
original_value = ActionView::Base.annotate_rendered_view_with_filenames
|
12
|
+
ActionView::Base.annotate_rendered_view_with_filenames = false
|
13
|
+
res = yield
|
14
|
+
ActionView::Base.annotate_rendered_view_with_filenames = original_value
|
15
|
+
res
|
16
|
+
else
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Lookbook
|
2
|
+
class ActionViewAnnotationsStripper < Service
|
3
|
+
attr_reader :text
|
4
|
+
|
5
|
+
ANNOTATIONS_REGEX = /<!-- (BEGIN|END) (.*) -->/
|
6
|
+
|
7
|
+
def initialize(text)
|
8
|
+
@text = text.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
text.gsub(ANNOTATIONS_REGEX, "")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Lookbook
|
2
|
+
class FrontmatterExtractor < Service
|
3
|
+
FRONTMATTER_REGEX = /\A---(.|\n)*?---/
|
4
|
+
|
5
|
+
attr_reader :content
|
6
|
+
|
7
|
+
def initialize(content)
|
8
|
+
@content = content.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
frontmatter = extract_frontmatter(content)
|
13
|
+
rest = strip_frontmatter(content)
|
14
|
+
[frontmatter, rest]
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def extract_frontmatter(text)
|
20
|
+
matches = text.match(FRONTMATTER_REGEX)
|
21
|
+
matches ? YAML.safe_load(matches[0]).deep_symbolize_keys : {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def strip_frontmatter(text)
|
25
|
+
text.gsub(FRONTMATTER_REGEX, "").strip
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "css_parser"
|
2
|
+
|
3
|
+
module Lookbook
|
4
|
+
class StylesExtractor < Service
|
5
|
+
STYLE_TAGS_REGEX = /<style(?:\s[^>]*)?>((?:(?!<\/style>).)*)<\/style>/m
|
6
|
+
|
7
|
+
attr_reader :content
|
8
|
+
|
9
|
+
def initialize(content)
|
10
|
+
@content = content.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
styles = extract_styles(content)
|
15
|
+
rest = strip_styles(content)
|
16
|
+
[styles, rest]
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def extract_styles(text)
|
22
|
+
css_parser = ::CssParser::Parser.new
|
23
|
+
text.scan(STYLE_TAGS_REGEX).flatten.map(&:strip).each do |css|
|
24
|
+
css_parser.load_string!(css.strip)
|
25
|
+
end
|
26
|
+
|
27
|
+
styles = []
|
28
|
+
css_parser.each_selector do |selector, declarations, specificity|
|
29
|
+
styles << "#{selector} { #{declarations} }"
|
30
|
+
end
|
31
|
+
styles
|
32
|
+
end
|
33
|
+
|
34
|
+
def strip_styles(text)
|
35
|
+
text.gsub(STYLE_TAGS_REGEX, "").strip
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -11,85 +11,66 @@ module Lookbook
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def hidden?
|
14
|
-
@hidden ||=
|
15
|
-
code_object.tag(:hidden).text.strip != "false"
|
16
|
-
else
|
17
|
-
false
|
18
|
-
end
|
14
|
+
@hidden ||= tag_value(:hidden) || false
|
19
15
|
end
|
20
16
|
|
21
17
|
def id
|
22
|
-
@id ||=
|
23
|
-
generate_id(code_object&.tag(:id)&.text)
|
24
|
-
end
|
18
|
+
@id ||= tag_value(:id)
|
25
19
|
end
|
26
20
|
|
27
21
|
def label
|
28
|
-
@label ||=
|
22
|
+
@label ||= tag_value(:label)
|
29
23
|
end
|
30
24
|
|
31
25
|
def notes
|
32
|
-
@notes ||=
|
33
|
-
code_object.docstring.to_s.strip
|
34
|
-
end
|
26
|
+
@notes ||= code_object.docstring&.to_s&.strip
|
35
27
|
end
|
36
28
|
|
37
29
|
def group
|
38
|
-
@group ||= code_object
|
30
|
+
@group ||= code_object.group
|
39
31
|
end
|
40
32
|
|
41
33
|
def position
|
42
|
-
@position ||=
|
34
|
+
@position ||= tag_value(:position) || 10000
|
43
35
|
end
|
44
36
|
|
45
37
|
def components
|
46
|
-
@components ||=
|
47
|
-
code_object.tags(:component).map do |component|
|
48
|
-
component.text.constantize
|
49
|
-
end
|
50
|
-
else
|
51
|
-
[]
|
52
|
-
end
|
38
|
+
@components ||= Array(code_object.tags(:component)).map(&:value)
|
53
39
|
end
|
54
40
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
pairs = tags.map { KeyValueTagParser.call(_1.text) }
|
59
|
-
|
60
|
-
# dynamic params set at the entity level are
|
61
|
-
# not (yet) supported so filter them out.
|
62
|
-
pairs.select! { |pair| !pair[1].is_a?(Array) && !pair[1].is_a?(Hash) }
|
63
|
-
|
64
|
-
pairs.to_h.symbolize_keys
|
41
|
+
def logical_path
|
42
|
+
path = tag_value(:logical_path)
|
43
|
+
path&.delete_prefix("/")&.delete_suffix("/")
|
65
44
|
end
|
66
45
|
|
67
|
-
def
|
68
|
-
@
|
69
|
-
end
|
46
|
+
def display_options
|
47
|
+
return @display_options unless @display_options.nil?
|
70
48
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
default: parameter_defaults[param.name],
|
76
|
-
eval_scope: @eval_scope)
|
49
|
+
# Dynamic params set at the entity level are
|
50
|
+
# not (yet?) supported so filter them out.
|
51
|
+
display_tags = Array(code_object.tags(:display)).select do |tag|
|
52
|
+
!(tag.value.is_a?(Array) || tag.value.is_a?(Hash))
|
77
53
|
end
|
54
|
+
|
55
|
+
display_tags.map { |tag| [tag.key.to_sym, tag.value] }.to_h
|
78
56
|
end
|
79
57
|
|
80
58
|
def methods
|
81
|
-
@methods ||= code_object
|
59
|
+
@methods ||= code_object.meths
|
82
60
|
end
|
83
61
|
|
84
62
|
def tags(name = nil)
|
85
|
-
|
86
|
-
Lookbook::Tags.process_tags(tag_objects,
|
87
|
-
eval_scope: @eval_scope,
|
88
|
-
file: (code_object.files.first[0] if code_object.files.any?))
|
63
|
+
code_object.tags(name)
|
89
64
|
end
|
90
65
|
|
91
66
|
def tag(name = nil)
|
92
67
|
tags(name).first
|
93
68
|
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def tag_value(tag_name)
|
73
|
+
code_object.tag(tag_name).value if code_object.has_tag?(tag_name)
|
74
|
+
end
|
94
75
|
end
|
95
76
|
end
|