repubmark 0.0.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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +46 -0
  3. data/.rubocop_todo.yml +129 -0
  4. data/.yardopts +4 -0
  5. data/LICENSE +21 -0
  6. data/README.md +21 -0
  7. data/Rakefile +60 -0
  8. data/examples/config.yml +71 -0
  9. data/examples/full.chapters.json +22 -0
  10. data/examples/full.gmi +44 -0
  11. data/examples/full.html +114 -0
  12. data/examples/full.repub +91 -0
  13. data/examples/full.summary.txt +2 -0
  14. data/examples/hello.chapters.json +3 -0
  15. data/examples/hello.gmi +1 -0
  16. data/examples/hello.html +5 -0
  17. data/examples/hello.repub +3 -0
  18. data/examples/hello.summary.txt +1 -0
  19. data/exe/repubmark +46 -0
  20. data/lib/repubmark/config.rb +53 -0
  21. data/lib/repubmark/elems/abbrev.rb +52 -0
  22. data/lib/repubmark/elems/annotation.rb +48 -0
  23. data/lib/repubmark/elems/article.rb +96 -0
  24. data/lib/repubmark/elems/base.rb +104 -0
  25. data/lib/repubmark/elems/blockquote.rb +80 -0
  26. data/lib/repubmark/elems/canvas.rb +104 -0
  27. data/lib/repubmark/elems/caption.rb +69 -0
  28. data/lib/repubmark/elems/chapter.rb +148 -0
  29. data/lib/repubmark/elems/code_block.rb +70 -0
  30. data/lib/repubmark/elems/code_inline.rb +35 -0
  31. data/lib/repubmark/elems/figure.rb +73 -0
  32. data/lib/repubmark/elems/figures.rb +47 -0
  33. data/lib/repubmark/elems/footnote.rb +87 -0
  34. data/lib/repubmark/elems/footnotes_category.rb +48 -0
  35. data/lib/repubmark/elems/fraction.rb +31 -0
  36. data/lib/repubmark/elems/iframe.rb +62 -0
  37. data/lib/repubmark/elems/joint.rb +176 -0
  38. data/lib/repubmark/elems/link.rb +56 -0
  39. data/lib/repubmark/elems/list.rb +84 -0
  40. data/lib/repubmark/elems/list_item.rb +147 -0
  41. data/lib/repubmark/elems/note.rb +40 -0
  42. data/lib/repubmark/elems/paragraph.rb +42 -0
  43. data/lib/repubmark/elems/power.rb +34 -0
  44. data/lib/repubmark/elems/quote.rb +53 -0
  45. data/lib/repubmark/elems/section.rb +58 -0
  46. data/lib/repubmark/elems/separator.rb +17 -0
  47. data/lib/repubmark/elems/special.rb +37 -0
  48. data/lib/repubmark/elems/text.rb +43 -0
  49. data/lib/repubmark/highlight.rb +85 -0
  50. data/lib/repubmark/titled_ref.rb +16 -0
  51. data/lib/repubmark/version.rb +6 -0
  52. data/lib/repubmark.rb +125 -0
  53. data/tests/examples.rb +51 -0
  54. metadata +129 -0
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Joint < Base
6
+ parents :Caption, :Quote
7
+
8
+ def initialize(parent)
9
+ super parent
10
+
11
+ @raw1 = nil
12
+ @base = nil
13
+ @raw2 = nil
14
+ @notes = []
15
+ @raw3 = nil
16
+
17
+ @context = nil
18
+ @context_note = false
19
+ end
20
+
21
+ #################
22
+ # Basic methods #
23
+ #################
24
+
25
+ def word_count = components.sum(&:word_count)
26
+
27
+ def to_summary_plain = components.map(&:to_summary_plain).join.freeze
28
+
29
+ def to_html = components.map(&:to_html).join.freeze
30
+
31
+ def to_gemtext = components.map(&:to_gemtext).join.freeze
32
+
33
+ ########################
34
+ # Builder methods: Raw #
35
+ ########################
36
+
37
+ def raw(str)
38
+ text = Text.new self, str
39
+ if @notes.any?
40
+ raise 'Joint raw text already exists' if @raw3
41
+
42
+ @raw3 = text
43
+ elsif @base
44
+ raise 'Joint raw text already exists' if @raw2
45
+
46
+ @raw2 = text
47
+ else
48
+ raise 'Joint raw text already exists' if @raw1
49
+
50
+ @raw1 = text
51
+ end
52
+ end
53
+
54
+ #########################
55
+ # Builder methods: Base #
56
+ #########################
57
+
58
+ def abbrev(abbrev, transcript) = base Abbrev.new self, abbrev, transcript
59
+
60
+ def bold(str) = base Text.new self, str, bold: true
61
+
62
+ def code_inline(str) = base CodeInline.new self, str
63
+
64
+ def ellipsis = base Special.new self, :ellipsis
65
+
66
+ def fraction(top, bottom) = base Fraction.new self, top, bottom
67
+
68
+ def italic(str) = base Text.new self, str, italic: true
69
+
70
+ def link(text, uri) = base Link.new self, text, uri
71
+
72
+ def link_italic(text, uri) = base Link.new self, text, uri, italic: true
73
+
74
+ def mdash = base Special.new self, :mdash
75
+
76
+ def power(base, exponent) = base Power.new self, base, exponent
77
+
78
+ def quote(str = nil)
79
+ quote = Quote.new self
80
+ case [!!str, block_given?]
81
+ when [true, false] then quote.text str
82
+ when [false, true] then yield quote
83
+ else
84
+ raise 'Invalid args'
85
+ end
86
+ base quote
87
+ end
88
+
89
+ def quote_italic(str)
90
+ quote = Quote.new self
91
+ quote.italic str
92
+ base quote
93
+ end
94
+
95
+ def section(*args) = base Section.new self, *args
96
+
97
+ def text(str) = base Text.new self, str
98
+
99
+ ##########################
100
+ # Builder methods: Notes #
101
+ ##########################
102
+
103
+ def context(index, category, slug)
104
+ raise 'Context already given' if @context
105
+
106
+ @context = [index, category, slug]
107
+ nil
108
+ end
109
+
110
+ def context_note
111
+ raise 'No context given' if @context.nil?
112
+ raise 'Context already noted' if @context_note
113
+
114
+ @context_note = true
115
+ ref_note(*@context)
116
+ end
117
+
118
+ def ref_note(index, category, slug)
119
+ note Note.new self, index, category, slug
120
+ end
121
+
122
+ private
123
+
124
+ def components
125
+ raise 'No context note' if @context && !@context_note
126
+
127
+ [@raw1, @base, @raw2, *@notes, @raw3].compact
128
+ end
129
+
130
+ def base(elem)
131
+ raise 'Joint base already exists' if @base
132
+ raise 'Joint notes already exist' if @notes.any?
133
+
134
+ @base = elem
135
+ nil
136
+ end
137
+
138
+ def note(elem)
139
+ raise 'Joint base does not exists' if @base.nil?
140
+
141
+ @notes << elem
142
+ nil
143
+ end
144
+
145
+ module ForwardingBuilders
146
+ def joint = raise NotImplementedError, "#{self.class}#joint"
147
+
148
+ def abbrev(*args) = joint { |joint| joint.abbrev(*args) }
149
+
150
+ def bold(*args) = joint { |joint| joint.bold(*args) }
151
+
152
+ def code_inline(*args) = joint { |joint| joint.code_inline(*args) }
153
+
154
+ def ellipsis(*args) = joint { |joint| joint.ellipsis(*args) }
155
+
156
+ def fraction(*args) = joint { |joint| joint.fraction(*args) }
157
+
158
+ def italic(*args) = joint { |joint| joint.italic(*args) }
159
+
160
+ def link(*args) = joint { |joint| joint.link(*args) }
161
+
162
+ def link_italic(*args) = joint { |joint| joint.link_italic(*args) }
163
+
164
+ def mdash(*args) = joint { |joint| joint.mdash(*args) }
165
+
166
+ def power(*args) = joint { |joint| joint.power(*args) }
167
+
168
+ def quote_italic(*args) = joint { |joint| joint.quote_italic(*args) }
169
+
170
+ def section(*args) = joint { |joint| joint.section(*args) }
171
+
172
+ def text(*args) = joint { |joint| joint.text(*args) }
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Link < Text
6
+ parents :Joint
7
+
8
+ SCHEMES = %w[http https].freeze
9
+
10
+ def initialize(parent, str, uri, **kwargs)
11
+ super parent, str, **kwargs
12
+
13
+ self.uri = uri
14
+ validate_uri!
15
+ end
16
+
17
+ #################
18
+ # Basic methods #
19
+ #################
20
+
21
+ def to_html
22
+ "<a href=\"#{build_uri('.html')}\">#{str_to_html}</a>".freeze
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :uri
28
+
29
+ def uri=(uri)
30
+ @uri = URI.parse(uri).freeze
31
+ end
32
+
33
+ def validate_uri!
34
+ raise 'Expected normalized URI' unless uri.normalize == uri
35
+ raise 'Expected no userinfo' unless uri.userinfo.nil?
36
+
37
+ validate_uri_http_absolute!
38
+ end
39
+
40
+ def validate_uri_http_absolute!
41
+ return unless uri.is_a?(URI::HTTP) && uri.absolute?
42
+
43
+ raise 'Invalid scheme' unless SCHEMES.include? uri.scheme
44
+ raise 'Expected hostname' unless uri.hostname
45
+ end
46
+
47
+ def build_uri(ext)
48
+ if uri.is_a?(URI::MailTo) || uri.absolute?
49
+ uri
50
+ else
51
+ own_url "#{uri}#{ext}"
52
+ end.to_s.freeze
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class List < Base
6
+ parents :Canvas, :ListItem
7
+
8
+ def initialize(parent, links:, ordered:)
9
+ super parent
10
+
11
+ @links = !!links
12
+ @ordered = !!ordered
13
+
14
+ @items = []
15
+ end
16
+
17
+ #################
18
+ # Basic methods #
19
+ #################
20
+
21
+ def word_count = @items.sum(&:word_count)
22
+
23
+ def to_summary_plain = @items.map(&:to_summary_plain).join(' ').freeze
24
+
25
+ def to_html
26
+ [
27
+ "<#{tag_name}>\n",
28
+ *@items.map(&:to_html),
29
+ "</#{tag_name}>\n",
30
+ ].join.freeze
31
+ end
32
+
33
+ def to_gemtext = @items.map(&:to_gemtext).join.freeze
34
+
35
+ ##################
36
+ # Helper methods #
37
+ ##################
38
+
39
+ def level = parent.instance_of?(ListItem) ? parent.level + 1 : 0
40
+
41
+ def items_count = @items.count
42
+
43
+ def unicode_decor
44
+ return if level <= 1
45
+
46
+ "#{parent.unicode_decor_parent}#{parent.last? ? '⠀ ' : '│ '}"
47
+ end
48
+
49
+ ###################
50
+ # Builder methods #
51
+ ###################
52
+
53
+ def item(...) = @links ? item_links(...) : item_nolinks(...)
54
+
55
+ private
56
+
57
+ def parent=(parent)
58
+ unless parent.instance_of?(Canvas) || parent.instance_of?(ListItem)
59
+ raise TypeError,
60
+ "Expected #{Canvas} or #{ListItem}, got #{parent.class}"
61
+ end
62
+
63
+ @parent = parent
64
+ end
65
+
66
+ def tag_name = @ordered ? 'ol' : 'ul'
67
+
68
+ def item_nolinks
69
+ list_item = ListItem.new self, @items.count
70
+ @items << list_item
71
+ yield list_item
72
+ nil
73
+ end
74
+
75
+ def item_links(ref_url, ref_title = nil)
76
+ titled_ref = TitledRef.new ref_url, ref_title
77
+ list_item = ListItem.new self, @items.count, titled_ref
78
+ @items << list_item
79
+ yield list_item if block_given?
80
+ nil
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class ListItem < Base
6
+ extend Forwardable
7
+
8
+ parents :List
9
+
10
+ attr_reader :index, :titled_ref
11
+
12
+ def initialize(parent, index, titled_ref = nil)
13
+ super parent
14
+
15
+ self.index = index
16
+ self.titled_ref = titled_ref
17
+
18
+ @caption = Caption.new self
19
+ @sublist = nil
20
+ end
21
+
22
+ #################
23
+ # Basic methods #
24
+ #################
25
+
26
+ def word_count = @caption.word_count + (@sublist&.word_count || 0)
27
+
28
+ def to_summary_plain
29
+ [
30
+ @caption.to_summary_plain,
31
+ @sublist&.to_summary_plain,
32
+ ].compact.join("\n").freeze
33
+ end
34
+
35
+ def to_html
36
+ [
37
+ "<li>\n",
38
+ build_ref(:html),
39
+ @caption.to_html,
40
+ @sublist&.to_html,
41
+ "</li>\n",
42
+ ].join.freeze
43
+ end
44
+
45
+ def to_gemtext
46
+ [
47
+ if titled_ref
48
+ "#{build_ref(:gemtext)}#{@caption.to_gemtext}".strip
49
+ else
50
+ "* #{unicode_decor_own}#{@caption.to_gemtext}".strip
51
+ end,
52
+ @sublist&.to_gemtext,
53
+ ].join("\n").freeze
54
+ end
55
+
56
+ ##################
57
+ # Helper methods #
58
+ ##################
59
+
60
+ def level = parent.level
61
+
62
+ def last? = index >= parent.items_count - 1
63
+
64
+ def unicode_decor_own
65
+ "#{parent.unicode_decor}#{last? ? '└' : '├'} " unless level.zero?
66
+ end
67
+
68
+ def unicode_decor_parent = parent.unicode_decor
69
+
70
+ ###################
71
+ # Builder methods #
72
+ ###################
73
+
74
+ def subolist
75
+ raise 'No nested link lists' if titled_ref
76
+ raise 'Sublist already exists' if @sublist
77
+
78
+ @sublist = List.new self, links: false, ordered: true
79
+ yield @sublist
80
+ nil
81
+ end
82
+
83
+ def subulist
84
+ raise 'No nested link lists' if titled_ref
85
+ raise 'Sublist already exists' if @sublist
86
+
87
+ @sublist = List.new self, links: false, ordered: false
88
+ yield @sublist
89
+ nil
90
+ end
91
+
92
+ def respond_to_missing?(method_name, _include_private)
93
+ @caption.respond_to?(method_name) || super
94
+ end
95
+
96
+ def method_missing(method_name, ...)
97
+ if @caption.respond_to? method_name
98
+ raise 'Caption after sublist' if @sublist
99
+
100
+ @caption.public_send(method_name, ...)
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def parent=(parent)
109
+ unless parent.instance_of? List
110
+ raise TypeError, "Expected #{List}, got #{parent.class}"
111
+ end
112
+
113
+ @parent = parent
114
+ end
115
+
116
+ def index=(index)
117
+ index = Integer index
118
+ raise 'Invalid index' if index.negative?
119
+
120
+ @index = index
121
+ end
122
+
123
+ def titled_ref=(titled_ref)
124
+ return @titled_ref = nil if titled_ref.nil?
125
+
126
+ unless titled_ref.instance_of? TitledRef
127
+ raise TypeError, "Expected #{TitledRef}, got #{titled_ref.class}"
128
+ end
129
+
130
+ @titled_ref = titled_ref
131
+ end
132
+
133
+ def build_ref(format)
134
+ return if titled_ref.nil?
135
+
136
+ case format
137
+ when :html
138
+ "<a href=\"#{titled_ref.url}\">#{titled_ref.title}</a>\n".freeze
139
+ when :gemtext
140
+ "=> #{titled_ref.url} #{unicode_decor_own}#{titled_ref.title} ".freeze
141
+ else
142
+ raise 'Invalid format'
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Note < Base
6
+ parents :Joint
7
+
8
+ def initialize(parent, index, category, slug)
9
+ super parent
10
+ @index = index
11
+ @category = category
12
+ @slug = slug
13
+ end
14
+
15
+ #################
16
+ # Basic methods #
17
+ #################
18
+
19
+ def to_html
20
+ [
21
+ '<sup>',
22
+ %(<a href="##{CGI.escape_html(anchor)}">),
23
+ %([#{CGI.escape_html(@index.to_s)}]),
24
+ '</a>',
25
+ '</sup>',
26
+ ].join.freeze
27
+ end
28
+
29
+ def to_gemtext = "⁽#{index_unicode_sup}⁾".freeze
30
+
31
+ private
32
+
33
+ def anchor = "#@category-#@slug"
34
+
35
+ def index_unicode_sup
36
+ @index_unicode_sup ||= Repubmark.unicode_sup @index
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Paragraph < Base
6
+ parents :Canvas
7
+
8
+ def initialize(parent)
9
+ super parent
10
+ @caption = Caption.new self
11
+ end
12
+
13
+ #################
14
+ # Basic methods #
15
+ #################
16
+
17
+ def word_count = @caption.word_count
18
+
19
+ def to_summary_plain = @caption.to_summary_plain
20
+
21
+ def to_html = "<p>\n#{@caption.to_html}</p>\n".freeze
22
+
23
+ def to_gemtext = "#{@caption.to_gemtext}\n".freeze
24
+
25
+ ###################
26
+ # Builder methods #
27
+ ###################
28
+
29
+ def respond_to_missing?(method_name, _include_private)
30
+ @caption.respond_to?(method_name) || super
31
+ end
32
+
33
+ def method_missing(method_name, ...)
34
+ if @caption.respond_to?(method_name)
35
+ @caption.public_send(method_name, ...)
36
+ else
37
+ super
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Power < Base
6
+ parents :Joint
7
+
8
+ def initialize(parent, base, exponent)
9
+ super parent
10
+
11
+ @base = Integer base
12
+ @exponent = Integer exponent
13
+ end
14
+
15
+ #################
16
+ # Basic methods #
17
+ #################
18
+
19
+ def word_count = 1
20
+
21
+ def to_summary_plain = "#@base#{unicode_exponent}".freeze
22
+
23
+ def to_html = "#@base<sup>#@exponent</sup>".freeze
24
+
25
+ def to_gemtext = "#@base#{unicode_exponent}".freeze
26
+
27
+ private
28
+
29
+ def unicode_exponent
30
+ @unicode_exponent ||= Repubmark.unicode_sup @exponent
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Quote < Base
6
+ include Joint::ForwardingBuilders
7
+
8
+ parents :Caption, :Joint, :Quote
9
+
10
+ def initialize(...)
11
+ super
12
+ @items = []
13
+ end
14
+
15
+ #################
16
+ # Basic methods #
17
+ #################
18
+
19
+ def word_count = @items.sum(&:word_count)
20
+
21
+ def to_summary_plain
22
+ "«#{@items.map(&:to_summary_plain).join(' ')}»".freeze
23
+ end
24
+
25
+ def to_html = "&laquo;#{@items.map(&:to_html).join("\n")}&raquo;".freeze
26
+
27
+ def to_gemtext = "«#{@items.map(&:to_gemtext).join(' ')}»".freeze
28
+
29
+ ###################
30
+ # Builder methods #
31
+ ###################
32
+
33
+ def joint
34
+ joint = Joint.new self
35
+ yield joint
36
+ @items << joint
37
+ nil
38
+ end
39
+
40
+ def quote(str = nil)
41
+ quote = Quote.new self
42
+ case [!!str, block_given?]
43
+ when [true, false] then quote.text str
44
+ when [false, true] then yield quote
45
+ else
46
+ raise 'Invalid args'
47
+ end
48
+ @items << quote
49
+ nil
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Section < Base
6
+ parents :Joint
7
+
8
+ SECT_HTML = '&sect;'
9
+ SECT_UNICODE = '§'
10
+
11
+ attr_reader :count, :text
12
+
13
+ def initialize(parent, *args)
14
+ super parent
15
+
16
+ case args.length
17
+ when 1
18
+ self.count = 1
19
+ self.text = args[0]
20
+ when 2
21
+ self.count, self.text = args
22
+ else
23
+ raise ArgumentError, 'Expected 1 or 2 arguments'
24
+ end
25
+ end
26
+
27
+ #################
28
+ # Basic methods #
29
+ #################
30
+
31
+ def word_count = count_words @text
32
+
33
+ def to_summary_plain = "#{SECT_UNICODE * count}#{text}".freeze
34
+
35
+ def to_html = "#{SECT_HTML * count}#{text}".freeze
36
+
37
+ def to_gemtext = "#{SECT_UNICODE * count}#{text}".freeze
38
+
39
+ private
40
+
41
+ def count=(count)
42
+ unless count.instance_of? Integer
43
+ raise TypeError, "Expected #{Integer}, got #{count.class}"
44
+ end
45
+ raise 'Expected positive count' unless count.positive?
46
+
47
+ @count = count
48
+ end
49
+
50
+ def text=(text)
51
+ text = String(text).strip.freeze
52
+ raise 'Expected non-empty text' if text.empty?
53
+
54
+ @text = text
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Repubmark
4
+ module Elems
5
+ class Separator < Base
6
+ parents :Canvas
7
+
8
+ #################
9
+ # Basic methods #
10
+ #################
11
+
12
+ def to_html = "<hr/>\n"
13
+
14
+ def to_gemtext = "---\n"
15
+ end
16
+ end
17
+ end