tty-markdown-meinac 0.7.2

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.
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Markdown
5
+ # Responsible for decorating text with theme element styles
6
+ #
7
+ # @api private
8
+ class Decorator
9
+ # The newline character
10
+ #
11
+ # @return [String]
12
+ #
13
+ # @api private
14
+ NEWLINE = "\n"
15
+ private_constant :NEWLINE
16
+
17
+ # Create a {TTY::Markdown::Decorator} instance
18
+ #
19
+ # @example
20
+ # decorator = TTY::Markdown::Decorator.new(pastel, theme)
21
+ #
22
+ # @param [Pastel] pastel
23
+ # the pastel
24
+ # @param [TTY::Markdown::Theme] theme
25
+ # the theme
26
+ #
27
+ # @api public
28
+ def initialize(pastel, theme)
29
+ @pastel = pastel
30
+ @theme = theme
31
+ end
32
+
33
+ # Detect whether text decoration is enabled
34
+ #
35
+ # @example
36
+ # decorator.enabled?
37
+ #
38
+ # @return [Boolean]
39
+ #
40
+ # @api public
41
+ def enabled?
42
+ @pastel.enabled?
43
+ end
44
+
45
+ # Decorate text with theme element styles
46
+ #
47
+ # @example
48
+ # decorator.decorate("TTY Toolkit", :strong)
49
+ #
50
+ # @param [String] text
51
+ # the text
52
+ # @param [Symbol] name
53
+ # the theme element name
54
+ #
55
+ # @return [String]
56
+ #
57
+ # @api public
58
+ def decorate(text, name)
59
+ @pastel.decorate(text, *@theme[name])
60
+ end
61
+
62
+ # Decorate each text line with theme element styles
63
+ #
64
+ # @example
65
+ # decorator.decorate_each_line("TTY\nToolkit", :strong)
66
+ #
67
+ # @param [String] text
68
+ # the text
69
+ # @param [Symbol] name
70
+ # the theme element name
71
+ #
72
+ # @return [String]
73
+ #
74
+ # @api public
75
+ def decorate_each_line(text, name)
76
+ text.lines.map do |line|
77
+ decorate(line.chomp, name)
78
+ end.join(NEWLINE)
79
+ end
80
+ end # Decorator
81
+ end # Markdown
82
+ end # TTY
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Markdown
5
+ # Responsible for indicating a generic error
6
+ #
7
+ # @api public
8
+ class Error < StandardError
9
+ end # Error
10
+ end # Markdown
11
+ end # TTY
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Markdown
5
+ # Responsible for formatting code snippets with standard terminal colors
6
+ #
7
+ # @api private
8
+ class Formatter
9
+ # Create a {TTY::Markdown::Formatter} instance
10
+ #
11
+ # @example
12
+ # formatter = TTY::Markdown::Formatter.new(decorator)
13
+ #
14
+ # @param [TTY::Markdown::Decorator] decorator
15
+ # the decorator
16
+ #
17
+ # @api public
18
+ def initialize(decorator)
19
+ @decorator = decorator
20
+ end
21
+
22
+ # Format the Rouge lexer tokens
23
+ #
24
+ # @example
25
+ # formatter.format(tokens)
26
+ #
27
+ # @param [Enumerator] tokens
28
+ # the Rouge lexer tokens
29
+ #
30
+ # @return [String]
31
+ #
32
+ # @api public
33
+ def format(tokens)
34
+ code = tokens.map { |_token, value| value }.join
35
+ @decorator.decorate_each_line(code, :code)
36
+ end
37
+ end # Formatter
38
+ end # Markdown
39
+ end # TTY
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rouge"
4
+
5
+ require_relative "formatter"
6
+
7
+ module TTY
8
+ class Markdown
9
+ # Responsible for highlighting terminal code snippets
10
+ #
11
+ # @api private
12
+ class Highlighter
13
+ # Create a {TTY::Markdown::Highlighter} instance
14
+ #
15
+ # @example
16
+ # highlighter = TTY::Markdown::Highlighter.new(decorator)
17
+ #
18
+ # @param [TTY::Markdown::Decorator] decorator
19
+ # the decorator
20
+ # @param [Integer] mode
21
+ # the color mode
22
+ #
23
+ # @api public
24
+ def initialize(decorator, mode: 256)
25
+ @decorator = decorator
26
+ @mode = mode
27
+ end
28
+
29
+ # Highlight the code snippet
30
+ #
31
+ # @example
32
+ # highlighter.highlight("puts 'TTY Toolkit'", "ruby")
33
+ #
34
+ # @param [String] code
35
+ # the code snippet
36
+ # @param [String, nil] language
37
+ # the code language
38
+ #
39
+ # @return [String]
40
+ #
41
+ # @api public
42
+ def highlight(code, language = nil)
43
+ return code unless @decorator.enabled?
44
+
45
+ lexer = select_lexer(code, language)
46
+ formatter.format(lexer.lex(code))
47
+ end
48
+
49
+ private
50
+
51
+ # Select a lexer
52
+ #
53
+ # @param [String] code
54
+ # the code snippet
55
+ # @param [String, nil] language
56
+ # the code language
57
+ #
58
+ # @return [Rouge::Lexer]
59
+ #
60
+ # @api private
61
+ def select_lexer(code, language)
62
+ Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
63
+ end
64
+
65
+ # Select a formatter
66
+ #
67
+ # @return [Rouge::Formatter, TTY::Markdown::Formatter]
68
+ #
69
+ # @api private
70
+ def formatter
71
+ @formatter ||=
72
+ if @mode < 256
73
+ Formatter.new(@decorator)
74
+ elsif @mode == 256
75
+ Rouge::Formatters::Terminal256.new
76
+ else
77
+ Rouge::Formatters::TerminalTruecolor.new
78
+ end
79
+ end
80
+ end # Highlighter
81
+ end # Markdown
82
+ end # TTY
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "kramdown/parser/kramdown"
4
+
5
+ module TTY
6
+ class Markdown
7
+ # Responsible for parsing standard and extended Markdown syntax
8
+ #
9
+ # @api private
10
+ class Parser < Kramdown::Parser::Kramdown
11
+ # The fenced code block pattern
12
+ #
13
+ # @return [Regexp]
14
+ #
15
+ # @api private
16
+ FENCED_CODEBLOCK_MATCH = /
17
+ ^ {0,3}(([~`]){3,})\s*?((\S+?)(?:\?\S*)?)?\s*?\n
18
+ (.*?)
19
+ ^ {0,3}\1\2*\s*?\n
20
+ /mx.freeze
21
+
22
+ # The fenced code block start pattern
23
+ #
24
+ # @return [Regexp]
25
+ #
26
+ # @api private
27
+ FENCED_CODEBLOCK_START = /^ {0,3}[~`]{3,}/.freeze
28
+ private_constant :FENCED_CODEBLOCK_START
29
+
30
+ define_parser(:codeblock_fenced_extension, FENCED_CODEBLOCK_START, nil,
31
+ :parse_codeblock_fenced)
32
+
33
+ # Create a {TTY::Markdown::Parser} instance
34
+ #
35
+ # @example
36
+ # parser = TTY::Markdown::Parser.new("# TTY Toolkit", {})
37
+ #
38
+ # @param [String] source
39
+ # the Markdown source
40
+ # @param [Hash] options
41
+ # the parsing options
42
+ #
43
+ # @api private
44
+ def initialize(source, options)
45
+ super
46
+ replace_fenced_codeblock_parser
47
+ end
48
+
49
+ private
50
+
51
+ # Replace the fenced code block parser
52
+ #
53
+ # @return [void]
54
+ #
55
+ # @api private
56
+ def replace_fenced_codeblock_parser
57
+ @block_parsers[
58
+ @block_parsers.index(:codeblock_fenced)
59
+ ] = :codeblock_fenced_extension
60
+ end
61
+ end # Parser
62
+ end # Markdown
63
+ end # TTY
64
+
65
+ # Add the TTY::Markdown::Parser to the available Kramdown parsers
66
+ Kramdown::Parser.const_set(:TTYMarkdown, TTY::Markdown::Parser)
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "error"
4
+
5
+ module TTY
6
+ class Markdown
7
+ # Responsible for storing the symbols configuration
8
+ #
9
+ # @api private
10
+ class Symbols
11
+ # The ASCII name
12
+ #
13
+ # @return [String]
14
+ #
15
+ # @api private
16
+ ASCII = "ascii"
17
+ private_constant :ASCII
18
+
19
+ # The name to the ASCII symbol hash
20
+ #
21
+ # @return [Hash{Symbol => String}]
22
+ #
23
+ # @api private
24
+ NAME_TO_ASCII = {
25
+ arrow: "->",
26
+ bar: "|",
27
+ bottom_center: "+",
28
+ bottom_right: "+",
29
+ bottom_left: "+",
30
+ bracket_left: "[",
31
+ bracket_right: "]",
32
+ bullet: "*",
33
+ diamond: "*",
34
+ hash: "#",
35
+ hellip: "...",
36
+ laquo: "<<",
37
+ laquo_space: "<< ",
38
+ ldquo: "\"",
39
+ line: "-",
40
+ lsquo: "\"",
41
+ mdash: "--",
42
+ mid_center: "+",
43
+ mid_left: "+",
44
+ mid_right: "+",
45
+ ndash: "-",
46
+ paren_left: "(",
47
+ paren_right: ")",
48
+ pipe: "|",
49
+ raquo: ">>",
50
+ raquo_space: " >>",
51
+ rdquo: "\"",
52
+ rsquo: "\"",
53
+ top_center: "+",
54
+ top_left: "+",
55
+ top_right: "+"
56
+ }.freeze
57
+ private_constant :NAME_TO_ASCII
58
+
59
+ # The name to the Unicode symbol hash
60
+ #
61
+ # @return [Hash{Symbol => String}]
62
+ #
63
+ # @api private
64
+ NAME_TO_UNICODE = {
65
+ arrow: "»",
66
+ bar: "┃",
67
+ bottom_center: "┴",
68
+ bottom_right: "┘",
69
+ bottom_left: "└",
70
+ bracket_left: "[",
71
+ bracket_right: "]",
72
+ bullet: "●",
73
+ diamond: "◈",
74
+ hash: "#",
75
+ hellip: "…",
76
+ laquo: "«",
77
+ laquo_space: "« ",
78
+ ldquo: "“",
79
+ line: "─",
80
+ lsquo: "‘",
81
+ mdash: "\u2014",
82
+ mid_center: "┼",
83
+ mid_left: "├",
84
+ mid_right: "┤",
85
+ ndash: "-",
86
+ paren_left: "(",
87
+ paren_right: ")",
88
+ pipe: "│",
89
+ raquo: "»",
90
+ raquo_space: " »",
91
+ rdquo: "”",
92
+ rsquo: "’",
93
+ top_center: "┬",
94
+ top_left: "┌",
95
+ top_right: "┐"
96
+ }.freeze
97
+ private_constant :NAME_TO_UNICODE
98
+
99
+ # The Unicode name
100
+ #
101
+ # @return [String]
102
+ #
103
+ # @api private
104
+ UNICODE = "unicode"
105
+ private_constant :UNICODE
106
+
107
+ # Create a {TTY::Markdown::Symbols} instance
108
+ #
109
+ # @example
110
+ # symbols = TTY::Markdown::Symbols.from(:ascii)
111
+ #
112
+ # @example
113
+ # symbols = TTY::Markdown::Symbols.from({
114
+ # base: :ascii,
115
+ # override: {
116
+ # arrow: "=>"
117
+ # }
118
+ # })
119
+ #
120
+ # @param [Hash, String, Symbol] symbols
121
+ # the symbols configuration
122
+ #
123
+ # @return [TTY::Markdown::Symbols]
124
+ #
125
+ # @raise [TTY::Markdown::Error]
126
+ # when the symbols value is invalid
127
+ #
128
+ # @api public
129
+ def self.from(symbols)
130
+ new(validate_names(build_symbols(symbols)))
131
+ end
132
+
133
+ # Build the symbols hash
134
+ #
135
+ # @param [Hash, String, Symbol] symbols
136
+ # the symbols configuration
137
+ #
138
+ # @return [Hash{Symbol => String}]
139
+ #
140
+ # @raise [TTY::Markdown::Error]
141
+ # when the symbols value is invalid
142
+ #
143
+ # @api private
144
+ def self.build_symbols(symbols)
145
+ case symbols
146
+ when String, Symbol
147
+ select_symbols(symbols)
148
+ when Hash
149
+ base_symbols = select_symbols(symbols.fetch(:base, UNICODE))
150
+ base_symbols.merge(symbols[:override].to_h)
151
+ else
152
+ raise_value_error(symbols)
153
+ end
154
+ end
155
+ private_class_method :build_symbols
156
+
157
+ # Validate the symbols names
158
+ #
159
+ # @param [Hash{Symbol => String}] value
160
+ # the symbols value
161
+ #
162
+ # @return [Hash{Symbol => String}]
163
+ #
164
+ # @raise [TTY::Markdown::Error]
165
+ # when the symbol name is invalid
166
+ #
167
+ # @api private
168
+ def self.validate_names(value)
169
+ unknown_names = value.keys - NAME_TO_ASCII.keys
170
+ return value if unknown_names.empty?
171
+
172
+ raise_name_error(*unknown_names)
173
+ end
174
+ private_class_method :validate_names
175
+
176
+ # Select either ASCII or Unicode symbols
177
+ #
178
+ # @param [String, Symbol] name
179
+ # the symbols name
180
+ #
181
+ # @return [Hash{Symbol => String}]
182
+ #
183
+ # @raise [TTY::Markdown::Error]
184
+ # when the symbols name is invalid
185
+ #
186
+ # @api private
187
+ def self.select_symbols(name)
188
+ case name.to_s
189
+ when ASCII then NAME_TO_ASCII
190
+ when UNICODE then NAME_TO_UNICODE
191
+ else raise_symbols_name_error(name)
192
+ end
193
+ end
194
+ private_class_method :select_symbols
195
+
196
+ # Raise the symbols value error
197
+ #
198
+ # @param [Object] value
199
+ # the symbols value
200
+ #
201
+ # @return [void]
202
+ #
203
+ # @raise [TTY::Markdown::Error]
204
+ # when the symbols value is invalid
205
+ #
206
+ # @api private
207
+ def self.raise_value_error(value)
208
+ raise Error, "invalid symbols: #{value.inspect}. " \
209
+ "Use a hash with base and override keys or a symbol."
210
+ end
211
+ private_class_method :raise_value_error
212
+
213
+ # Raise the symbol name error
214
+ #
215
+ # @param [Array<Symbol>] names
216
+ # the symbols names
217
+ #
218
+ # @return [void]
219
+ #
220
+ # @raise [TTY::Markdown::Error]
221
+ # when the symbol name is invalid
222
+ #
223
+ # @api private
224
+ def self.raise_name_error(*names)
225
+ raise Error, "invalid symbol name#{"s" if names.size > 1}: " \
226
+ "#{names.map(&:inspect).join(", ")}."
227
+ end
228
+ private_class_method :raise_name_error
229
+
230
+ # Raise the symbols name error
231
+ #
232
+ # @param [String, Symbol] name
233
+ # the symbols name
234
+ #
235
+ # @return [void]
236
+ #
237
+ # @raise [TTY::Markdown::Error]
238
+ # when the symbols name is invalid
239
+ #
240
+ # @api private
241
+ def self.raise_symbols_name_error(name)
242
+ raise Error, "invalid symbols name: #{name.inspect}. " \
243
+ "Use the :#{ASCII} or :#{UNICODE} name."
244
+ end
245
+ private_class_method :raise_symbols_name_error
246
+
247
+ # Create a {TTY::Markdown::Symbols} instance
248
+ #
249
+ # @param [Hash{Symbol => String}] symbols
250
+ # the symbols configuration
251
+ #
252
+ # @api private
253
+ def initialize(symbols)
254
+ @symbols = symbols
255
+ end
256
+ private_class_method :new
257
+
258
+ # Retrieve a symbol by name
259
+ #
260
+ # @example
261
+ # symbols[:arrow]
262
+ #
263
+ # @param [Symbol] name
264
+ # the symbol name
265
+ #
266
+ # @return [String, nil]
267
+ #
268
+ # @api public
269
+ def [](name)
270
+ @symbols[name.to_sym]
271
+ end
272
+
273
+ # Wrap the content in brackets
274
+ #
275
+ # @example
276
+ # symbols.wrap_in_brackets("TTY Toolkit")
277
+ #
278
+ # @param [String] content
279
+ # the content
280
+ #
281
+ # @return [String]
282
+ #
283
+ # @api public
284
+ def wrap_in_brackets(content)
285
+ "#{self[:bracket_left]}#{content}#{self[:bracket_right]}"
286
+ end
287
+
288
+ # Wrap the content in parentheses
289
+ #
290
+ # @example
291
+ # symbols.wrap_in_parentheses("TTY Toolkit")
292
+ #
293
+ # @param [String] content
294
+ # the content
295
+ #
296
+ # @return [String]
297
+ #
298
+ # @api public
299
+ def wrap_in_parentheses(content)
300
+ "#{self[:paren_left]}#{content}#{self[:paren_right]}"
301
+ end
302
+ end # Symbols
303
+ end # Markdown
304
+ end # TTY