marko 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +12 -0
  3. data/.rubocop.yml +45 -0
  4. data/CHANGELOG.md +54 -1
  5. data/Dockerfile +11 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +24 -35
  8. data/Rakefile +2 -6
  9. data/exe/marko +4 -20
  10. data/lib/basic.rb +27 -0
  11. data/lib/marko/chain.rb +44 -0
  12. data/lib/marko/cli.rb +119 -121
  13. data/lib/marko/config.rb +30 -20
  14. data/lib/marko/errors.rb +38 -0
  15. data/lib/marko/model/markup.rb +33 -0
  16. data/lib/marko/model/topic.rb +126 -0
  17. data/lib/marko/model/tree_node.rb +34 -0
  18. data/lib/marko/model.rb +10 -0
  19. data/lib/marko/parser/metadata.rb +28 -0
  20. data/lib/marko/parser/source.rb +52 -0
  21. data/lib/marko/parser/topic.rb +33 -0
  22. data/lib/marko/parser.rb +8 -19
  23. data/lib/marko/renderers/artifact.rb +29 -0
  24. data/lib/marko/renderers/content.rb +37 -0
  25. data/lib/marko/renderers/link.rb +19 -0
  26. data/lib/marko/renderers/metadata.rb +34 -0
  27. data/lib/marko/renderers/nested_list.rb +21 -0
  28. data/lib/marko/renderers/nested_tree.rb +22 -0
  29. data/lib/marko/renderers/renderer.rb +17 -0
  30. data/lib/marko/renderers/title.rb +16 -0
  31. data/lib/marko/renderers/topic.rb +24 -0
  32. data/lib/marko/renderers/url.rb +16 -0
  33. data/lib/marko/renderers.rb +17 -0
  34. data/lib/marko/scanner.rb +39 -0
  35. data/lib/marko/tasks/assemble.rb +52 -0
  36. data/lib/marko/tasks/compile.rb +19 -0
  37. data/lib/marko/tasks/load.rb +13 -0
  38. data/lib/marko/tasks/parse.rb +27 -0
  39. data/lib/marko/tasks/scan.rb +18 -0
  40. data/lib/marko/tasks/validate.rb +32 -0
  41. data/lib/marko/tasks.rb +13 -0
  42. data/lib/marko/validators/lost_index.rb +21 -0
  43. data/lib/marko/validators/lost_links.rb +25 -0
  44. data/lib/marko/validators/lost_parent.rb +21 -0
  45. data/lib/marko/validators/non_unique_id.rb +23 -0
  46. data/lib/marko/validators.rb +11 -0
  47. data/lib/marko/version.rb +1 -3
  48. data/lib/marko.rb +11 -35
  49. metadata +47 -54
  50. data/Gemfile +0 -10
  51. data/Gemfile.lock +0 -22
  52. data/lib/assets/demo/README.md +0 -13
  53. data/lib/assets/demo/src/fr/assemble.md +0 -27
  54. data/lib/assets/demo/src/fr/compile.md +0 -25
  55. data/lib/assets/demo/src/fr/markup.md +0 -111
  56. data/lib/assets/demo/src/fr/storage.md +0 -16
  57. data/lib/assets/demo/src/fr/treenode.md +0 -34
  58. data/lib/assets/demo/src/index.md +0 -34
  59. data/lib/assets/demo/src/intro.md +0 -98
  60. data/lib/assets/demo/src/ui/cli.md +0 -26
  61. data/lib/assets/demo/src/ui/gem.md +0 -14
  62. data/lib/assets/demo/src/ur/uc.create.project.md +0 -8
  63. data/lib/assets/demo/src/ur/uc.general.flow.md +0 -14
  64. data/lib/assets/init/README.md +0 -61
  65. data/lib/assets/init/Rakefile +0 -100
  66. data/lib/assets/init/tt/artifact.md.tt +0 -3
  67. data/lib/marko/artifact.rb +0 -3
  68. data/lib/marko/assembler.rb +0 -82
  69. data/lib/marko/compiler.rb +0 -16
  70. data/lib/marko/gadgets/pluggable.rb +0 -55
  71. data/lib/marko/gadgets/sentry.rb +0 -66
  72. data/lib/marko/gadgets/service.rb +0 -52
  73. data/lib/marko/gadgets.rb +0 -3
  74. data/lib/marko/loader.rb +0 -38
  75. data/lib/marko/markup/compiler.rb +0 -36
  76. data/lib/marko/markup/decorator.rb +0 -65
  77. data/lib/marko/markup/macro.rb +0 -176
  78. data/lib/marko/markup/parser.rb +0 -122
  79. data/lib/marko/markup/storage.rb +0 -100
  80. data/lib/marko/markup/validator.rb +0 -101
  81. data/lib/marko/markup.rb +0 -24
  82. data/lib/marko/services/assemble.rb +0 -16
  83. data/lib/marko/services/compile.rb +0 -30
  84. data/lib/marko/services.rb +0 -2
  85. data/lib/marko/storage.rb +0 -36
  86. data/lib/marko/tree_node.rb +0 -128
  87. data/lib/marko/validator.rb +0 -19
  88. data/marko.gemspec +0 -44
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Marko
4
- # Factory module for guarding method arguments
5
- #
6
- # @example
7
- #
8
- # ShortString = Sentry.new(:str, "must be String[3..100]"
9
- # ) {|v| v.is_a?(String) && v.size.between?(3,100)}
10
- #
11
- # ShortString.(str) => "str"
12
- # ShortString.(nil) => ArgumentError ":str must be String[3..100]"
13
- # ShortString.error(nil) => ":str must be String[3..100]"
14
- # ShortString.error!(nil) => ArgumentError":str must be String[3..100]"
15
- # ShortString.(nil, :name) => ArgumentError ":name must be String[3..100]"
16
- # ShortString.(nil, 'John Doe', 'Ups!') => ArgumentError ":John Doe Ups!"
17
- #
18
- module Sentry
19
-
20
- # creates a new Sentry
21
- # @param key [Symbol|String] key for error message
22
- # @param msg [String] error message
23
- # @param blk [&block] validation block that should return boolen
24
- # @return [Sentry] based on key, msg, and validation block
25
- def self.new(key, msg, &blk)
26
- # origin ;)
27
- argerror = ->(val, msg, cnd) {
28
- fail ArgumentError, msg unless cnd
29
- val
30
- }
31
- Module.new do
32
- include Sentry
33
- extend self
34
-
35
- @key = argerror.(key, ":key must be Symbol|String",
36
- key.is_a?(String) || key.is_a?(Symbol))
37
- @msg = argerror.(msg, ":msg must be String", msg.is_a?(String))
38
- @blk = argerror.(blk, "&blk must be provided", block_given?)
39
- end
40
- end
41
-
42
- # returns error message for invalid :val
43
- # @param val [Object] value to be validated
44
- # @param key [Symbol|String] key for error message
45
- # @param msg [String] optional error message
46
- # @return [String] error message for invalid :val or nil when :val is valid
47
- def error(val, key = @key, msg = @msg)
48
- ":#{key} #{msg}" unless @blk.(val)
49
- end
50
-
51
- # guards :val
52
- # @todo @see Yard!
53
- # @param val [Object] value to be returned if it valid
54
- # @param key [Symbol|String] key for error message
55
- # @param msg [String] optional error message
56
- # @return [Object] valid :val or raieses ArgumentError when invalid
57
- def error!(val, key = @key, msg = @msg)
58
- return val if @blk.(val)
59
- fail ArgumentError, ":#{key} #{msg}", caller[0..-1]
60
- end
61
-
62
- # @todo how do describe in Yard?
63
- alias :call :error!
64
- end
65
-
66
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Marko
4
- # Service like ServiceObject, Command, etc.
5
- #
6
- # @example just call without parameters
7
- # class BoldService < Service
8
- # def call
9
- # 42
10
- # end
11
- # end
12
- #
13
- # @example with parameters
14
- # class PlusService < Service
15
- # def initialize(a, b)
16
- # @a, @b = a, b
17
- # end
18
- #
19
- # def call
20
- # 42
21
- # end
22
- # end
23
- #
24
- class Service
25
- Failure = Class.new(StandardError)
26
-
27
- class << self
28
- def call(*args, **kwargs, &block)
29
- new(*args, **kwargs, &block).call
30
- end
31
-
32
- def inherited(klass)
33
- klass.const_set(:Failure, Class.new(klass::Failure))
34
- super
35
- end
36
- end
37
-
38
- private_class_method :new
39
-
40
- def initialize(*args, **kwargs, &block)
41
- @args = args
42
- @kwargs = kwargs
43
- @block = block
44
- end
45
-
46
- def call
47
- fail Failure, "#{self.class.name}#call must be overrided"
48
- end
49
-
50
- end
51
-
52
- end
data/lib/marko/gadgets.rb DELETED
@@ -1,3 +0,0 @@
1
- require_relative "gadgets/sentry"
2
- require_relative "gadgets/pluggable"
3
- require_relative "gadgets/service"
data/lib/marko/loader.rb DELETED
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "gadgets"
4
- require_relative "config"
5
-
6
- module Marko
7
-
8
- # The strategy class for loading sources from repository
9
- class Loader < Service
10
- # load markup sources, parse and return TreeNode buffer
11
- #
12
- # @example
13
- # fn = proc{|event, paylod| ... }
14
- # buffer, errors = loader.(&fn)
15
- # fail "Failed" if errors.any?
16
- # # procced ...
17
- #
18
- # @param block [&block] aka proc {|event, payload| ..}
19
- # @return [Array<TreeNode>, Array<String>] where
20
- # the first item is buffer and the second is array<error>
21
- def call
22
- buffer = []
23
- errors = []
24
- parser = ParserPlug.plugged
25
- storage = StoragePlug.plugged
26
- storage.sources.each do |source|
27
- @block.(:source, source) if @block
28
- content = storage.content(source)
29
- buff, errs = parser.(content, source, &@block)
30
- buffer.concat(buff)
31
- errors.concat(errs)
32
- @block.(:errors, errs) if @block
33
- end
34
- [buffer.flatten, errors]
35
- end
36
- end
37
-
38
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "erb"
4
- require_relative "../compiler"
5
- require_relative "../config"
6
-
7
- module Marko
8
- module Markup
9
-
10
- class Compiler < Marko::Compiler
11
-
12
- # @see Marko::Compliler#call
13
- def call(tree, template, filename, &block)
14
- super(tree, template, filename, &block)
15
- compile
16
- end
17
-
18
- protected
19
-
20
- def compile
21
- storage = StoragePlug.plugged
22
- erbgen = ERB.new(@template, trim_mode: '-')
23
- payload = @tree.map{|n| Decorator.new(n)}
24
- storage.write(@filename){|f|
25
- payload.each{|node|
26
- @node = node
27
- text = erbgen.result(binding)
28
- f.puts text
29
- }
30
- }
31
- @filename
32
- end
33
- end
34
-
35
- end
36
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "delegate"
4
-
5
- module Marko
6
- module Markup
7
-
8
- class Decorator < SimpleDelegator
9
-
10
- def initialize(obj)
11
- super(obj)
12
- @macroproc = MacroProcPlug.plugged
13
- end
14
-
15
- def find_node(ref)
16
- obj = super(ref)
17
- return nil unless obj
18
- self.class.new(obj)
19
- end
20
-
21
- def url
22
- id.downcase
23
- .gsub(/\W{1,}/, '-')
24
- .gsub(/^-/, '')
25
- .gsub(/-$/, '')
26
- .then{"##{_1}"}
27
- end
28
-
29
- def ref
30
- "[#{title}](#{url})"
31
- end
32
-
33
- def title
34
- str = super
35
- str = ".#{id.split(/\./).last}" if str.empty?
36
- str
37
- end
38
-
39
- def header
40
- return "% #{title}\n" if root?
41
- "#{'#' * nesting_level} #{title.strip} {#{url}}\n"
42
- end
43
-
44
- def meta
45
- hsh = super.dup
46
- hsh[:id] = id # full id will be there
47
- hsh.delete(:order_index)
48
- hsh.delete(:parent)
49
- hsh.delete(:origin)
50
- len = hsh.keys.map(&:length).max
51
- [].tap{|ary|
52
- ary << "key | value"
53
- ary << "--- | -----"
54
- hsh.each{|k,v| ary << "#{k} | #{v}"}
55
- }.join(?\n) + ?\n
56
- end
57
-
58
- def body
59
- text = @macroproc.process(super, self)
60
- text.strip + ?\n
61
- end
62
- end
63
-
64
- end
65
- end
@@ -1,176 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "decorator"
4
- require_relative "../gadgets"
5
-
6
- module Marko
7
- module Markup
8
-
9
- # @todo confusing circular depenencies
10
- # List/Tree/Link Macros -> Decorator
11
- # Decorator#body -> MacroProcessor
12
-
13
- # Base class for macro substitutions
14
- # @example
15
- # class Todo < Macro
16
- # @pattern = /@@todo[^\n]*\n/
17
- # def subs(sample, obj = nil)
18
- # # code that returns <substitution>
19
- # # for the sample parameter
20
- # end
21
- # end
22
- #
23
- # text = "bla-bla-bla @@todo foo\nbla-bla-bla"
24
- # Todo.new.gsub!(text)
25
- # # => "bla-bla-bla <substitution for @@todo foo>"
26
- #
27
- class Macro
28
- def self.pattern
29
- @pattern
30
- end
31
-
32
- # @return [Regexp] pattern to process
33
- def pattern
34
- self.class.pattern
35
- end
36
-
37
- # substitutes all occured #pattern it text
38
- def gsub!(text, obj = nil)
39
- fn = subfn(text, obj)
40
- text.scan(pattern).each(&fn)
41
- end
42
-
43
- protected
44
-
45
- # build substitution for the text sample
46
- # @param sample [String] sample for substitution
47
- # @param obj [Object] might be used for substitution
48
- # @return [String] substitution for text
49
- def subs(sample, obj)
50
- fail '#subs must be overridden'
51
- end
52
-
53
- def subfn(source, obj)
54
- fn = proc{|source, obj, sample|
55
- source.sub!(sample, subs(sample, obj))
56
- }.curry
57
- fn.(source, obj)
58
- end
59
- end
60
-
61
- class MLink < Macro
62
- @pattern = /\[\[[\w\.]*\]\]/
63
-
64
- # the macro requires obj [Decorator]
65
- def subs(sample, node)
66
- capture = /\[\[([\w\.]*)\]\]/
67
- ref = sample
68
- .match(capture)
69
- .captures.first
70
- obj = node.find_node(ref)
71
- obj ? obj.ref : "[#{ref}](#lost-link)"
72
- end
73
- end
74
-
75
- class MList < Macro
76
- @pattern = /@@list/
77
-
78
- def subs(sample, node)
79
- # @todo require sentry Decorator
80
- # MustbeTreeNode.(node)
81
- node.items
82
- .map{|n| d = Decorator.new(n); "- #{d.ref}" }
83
- .join(?\n) + ?\n
84
- end
85
- end
86
-
87
- class MTree < Macro
88
- @pattern = /@@tree/
89
-
90
- def subs(sample, node)
91
- level = node.nesting_level + 1
92
- node.to_a.drop(1)
93
- .inject([]){|memo, n| memo << Decorator.new(n)}
94
- .map{|n| "#{' ' * (n.nesting_level - level)}- #{n.ref}" }
95
- .join(?\n) + ?\n
96
- end
97
- end
98
-
99
- # @todo there is no sense to have such macro for releases
100
- # instead it might be helpful to have some custom
101
- # todo command that will create a file with nodes
102
- # and list of todo for each node
103
- #
104
- # class MTodo < Macro
105
- # @pattern = /@@todo[^\n]*\n/
106
- #
107
- # def subs(sample, obj = nil)
108
- # capture = /@@todo([^\n]*)\n/
109
- # payload = sample.match(capture)
110
- # .captures
111
- # .first
112
- # .strip
113
- # "__TODO__ #{payload}\n"
114
- # end
115
- # end
116
-
117
- # inline @@todo macro
118
- # @todo remove line with \n when it starts from @@todo
119
- class MTodo < Macro
120
- @pattern = /.*@@todo.*$/
121
-
122
- def subs(sample, obj = nil)
123
- cap = /(.*)@@todo.*$/
124
- m = sample.match(cap)
125
- m[1].strip + sample.gsub(pattern, '')
126
- end
127
- end
128
-
129
- class MSkip < Macro
130
- @pattern = /@@skip.*$/m
131
-
132
- def subs(sample, obj = nil)
133
- ''
134
- end
135
- end
136
-
137
- MustbeMacro = Sentry.new(:macro, "must be Macro"
138
- ) {|v| v.is_a? Macro }
139
-
140
- # Macro processor
141
- # @example
142
- # processor = MacroProcessor.new
143
- # processor << Toc.new
144
- # processor << Todo.new
145
- # processor.('bla bla @@todo, bla bla @@list')
146
- # # => 'bla bla <substitute @@todo>, bla bla..'
147
- class MacroProcessor
148
- extend Marko::Pluggable
149
-
150
- def initialize
151
- @macros = {}
152
- self.<<(MList.new)
153
- self.<<(MTree.new)
154
- self.<<(MLink.new)
155
- self.<<(MTodo.new)
156
- self.<<(MSkip.new)
157
- end
158
-
159
- def <<(macro)
160
- MustbeMacro.(macro)
161
- @macros[macro.pattern] = macro
162
- macro
163
- end
164
-
165
- def process(text, obj)
166
- fail 'No macro registered' unless @macros.any?
167
- String.new(text).tap {|str|
168
- @macros.values.each{|m| m.gsub!(str, obj) }
169
- }
170
- end
171
-
172
- alias :call :process
173
- end
174
-
175
- end
176
- end
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../parser"
4
- require_relative "../tree_node"
5
-
6
- module Marko
7
- module Markup
8
-
9
- class Parser < Marko::Parser
10
-
11
- # @see Parser#call
12
- def call(content, source, &block)
13
- @source = source
14
- parse(content, &block)
15
- end
16
-
17
- protected
18
-
19
- def parse(content, &block)
20
- result, errors = [], [], []
21
- scan_treenode(content)
22
- .map{|origin|
23
- begin
24
- parse_treenode(origin.markup)
25
- .tap{|n| n[:origin] = origin }
26
- rescue => e
27
- errmsg = "wrong markup #{origin} #{e.message}\n"
28
- errors << errmsg
29
- nil
30
- end
31
- }
32
- .compact
33
- .each{|node|
34
- parent = find_parent(result, node[:origin].level)
35
- unless parent
36
- origin = node[:origin].to_s
37
- errors << "wrong header #{origin}\n"
38
- parent = result # it goes to the root!
39
- end
40
- parent << node
41
- }
42
-
43
- [result, errors]
44
- end
45
-
46
- def find_parent(ary, level)
47
- return ary if level == 1
48
- parent = ary.last
49
- l = 1
50
- while parent && (l+1) < level
51
- parent = parent.last
52
- l += 1
53
- end
54
- parent
55
- end
56
-
57
- Origin = Struct.new(:origin, :lineno, :markup) do
58
- def header
59
- @header ||= markup.lines.first
60
- end
61
-
62
- def level
63
- @level ||= header.scan(/^#*/).first.size
64
- end
65
-
66
- def to_s
67
- "#{origin}:#{lineno.to_s.rjust(2,'0')} >> #{header}".strip
68
- end
69
- end
70
-
71
- # @return [Array<Origin>]
72
- def scan_treenode(text)
73
- quote, buffer, lineno = false, [], 0
74
- origin = proc{
75
- Origin.new(@source, lineno - buffer.size + 1, buffer.join(''))
76
- }
77
-
78
- [].tap{|ary|
79
- text.each_line do |line|
80
- if line =~ /^#/ && !quote && buffer.any?
81
- ary << origin.()
82
- buffer.clear
83
- end
84
- lineno += 1
85
- buffer << line
86
- quote = !quote if line.start_with?('```')
87
- end
88
- ary << origin.() if buffer.any?
89
- }
90
- end
91
-
92
- def parse_treenode(text)
93
- head, *tail = text.lines
94
- m = head.match(/^(#+)(.*)/)
95
- title = m[2]&.strip || ''
96
-
97
- m = tail.join.match(/^({{([\s\S]*?)}})?(.*)?$/m)
98
- meta = parse_metadata(m[2]&.strip || '')
99
- body = m[3]&.strip || ''
100
-
101
- TreeNode.new(title, body, **meta)
102
- end
103
-
104
- def parse_metadata(text)
105
- return {} if text.empty?
106
- atrbfn = method(:parse_attribute).to_proc
107
- text
108
- .split(/[;,\n]/)
109
- .map(&:strip)
110
- .reject(&:empty?)
111
- .map(&atrbfn)
112
- .to_h
113
- end
114
-
115
- def parse_attribute(text)
116
- atr, val = text.split(?:)
117
- [atr.strip.to_sym, val&.strip || 'true']
118
- end
119
- end
120
-
121
- end
122
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
- require "psych"
3
- require 'fileutils'
4
- require "securerandom"
5
- require_relative "../storage"
6
-
7
- module Marko
8
- module Markup
9
-
10
- # File Storage
11
- class Storage < Marko::Storage
12
- include FileUtils
13
-
14
- Failure = Class.new(StandardError)
15
-
16
- # @see Storage#create
17
- def punch(storage)
18
- fail Failure, "Directory already exists" if Dir.exist?(storage)
19
- Dir.mkdir(storage)
20
- Dir.chdir(storage) {
21
- marko_directories.each{|dir| Dir.mkdir dir }
22
- src = File.join(Marko.root, 'lib', 'assets', 'init', '.')
23
- cp_r src, Dir.pwd
24
- }
25
- end
26
-
27
- # create demo project
28
- def punch_demo
29
- demo = File.join(Dir.home, DEMO)
30
- unless Dir.exist?(demo)
31
- punch(demo)
32
- assets = File.join(Marko.root, 'lib', 'assets', 'demo', '.')
33
- cp_r assets, demo
34
- end
35
- demo
36
- end
37
-
38
- # @see Storage#sources
39
- def sources
40
- marko_home!
41
- ptrn = File.join(SOURCE, '**', '*.md')
42
- Dir.glob ptrn
43
- end
44
-
45
- # @see Storage#content
46
- def content(source)
47
- marko_home!
48
- File.read(source)
49
- end
50
-
51
- def write(filename, content = '')
52
- backup(filename)
53
- File.open(filename, 'w') do |f|
54
- f.puts(content) unless content.empty?
55
- yield(f) if block_given?
56
- end
57
- end
58
-
59
- def marko_home?
60
- marko_directories.all?{ Dir.exist? _1 }
61
- end
62
-
63
- # @see Marko::Strorage#artifact
64
- def artifact
65
- return Psych.load_file(ARTIFACT).freeze if File.exist?(ARTIFACT)
66
- art = Artifact.new(SecureRandom.uuid,
67
- 'Marko Artifact',
68
- 'tt/artifact.md.tt',
69
- 'tt/marko-artifact.md'
70
- )
71
- File.write(ARTIFACT, Psych.dump(art))
72
- art.freeze
73
- end
74
-
75
- protected
76
-
77
- ARTIFACT = 'marko.yml'.freeze
78
- SOURCE = 'src'.freeze
79
- BINARY = 'bin'.freeze
80
- SAMPLE = 'tt'.freeze
81
- ASSETS = File.join(BINARY, 'assets').freeze
82
- DEMO = 'marko_demo'.freeze
83
-
84
- def marko_directories
85
- [SOURCE, BINARY, ASSETS, SAMPLE]
86
- end
87
-
88
-
89
- def marko_home!
90
- fail Failure, "Marko project required!" unless marko_home?
91
- end
92
-
93
- def backup(filename)
94
- cp(filename, filename + ?~) if File.exist?(filename)
95
- end
96
-
97
- end
98
-
99
- end
100
- end