slideck 0.1.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.
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-option"
4
+
5
+ module Slideck
6
+ # Responsible for parsing command line inputs
7
+ #
8
+ # @api private
9
+ class CLI
10
+ include TTY::Option
11
+
12
+ usage do
13
+ no_command
14
+
15
+ desc "Present Markdown-powered slide decks in the terminal"
16
+
17
+ desc "Controls:",
18
+ " First f, ^",
19
+ " Go to 1..n+g",
20
+ " Last t, $",
21
+ " Next n, l, Right, Spacebar",
22
+ " Prev p, h, Left, Backspace",
23
+ " Reload r, Ctrl+l",
24
+ " Quit q, Esc"
25
+
26
+ example "Start presentation",
27
+ "$ #{program} slides.md"
28
+ end
29
+
30
+ argument :file
31
+
32
+ option :color do
33
+ long "--color WHEN"
34
+ default "auto"
35
+ permit %w[always auto never]
36
+ desc "When to color output"
37
+ end
38
+
39
+ flag :debug do
40
+ short "-d"
41
+ long "--debug"
42
+ desc "Run in debug mode"
43
+ end
44
+
45
+ flag :help do
46
+ short "-h"
47
+ long "--help"
48
+ desc "Print usage"
49
+ end
50
+
51
+ flag :no_color do
52
+ long "--no-color"
53
+ desc "Do not color output. Identical to --color=never"
54
+ end
55
+
56
+ flag :version do
57
+ short "-v"
58
+ long "--version"
59
+ desc "Print version"
60
+ end
61
+
62
+ flag :watch do
63
+ short "-w"
64
+ long "--watch"
65
+ desc "Watch for changes in the file with slides"
66
+ end
67
+
68
+ # The error exit code
69
+ #
70
+ # @return [Integer]
71
+ #
72
+ # @api private
73
+ CODE_ERROR = 1
74
+ private_constant :CODE_ERROR
75
+
76
+ # The success exit code
77
+ #
78
+ # @return [Integer]
79
+ #
80
+ # @api private
81
+ CODE_SUCCESS = 0
82
+ private_constant :CODE_SUCCESS
83
+
84
+ # The pattern to detect color option
85
+ #
86
+ # @return [Regexp]
87
+ #
88
+ # @api private
89
+ COLOR_OPTION_PATTERN = /^--color/.freeze
90
+ private_constant :COLOR_OPTION_PATTERN
91
+
92
+ # The no color environment variable name
93
+ #
94
+ # @return [String]
95
+ #
96
+ # @api private
97
+ NO_COLOR_ENV_NAME = "NO_COLOR"
98
+ private_constant :NO_COLOR_ENV_NAME
99
+
100
+ # Create a CLI instance
101
+ #
102
+ # @example
103
+ # Slideck::CLI.new(runner, $stdout, $stderr)
104
+ #
105
+ # @param [Slideck::Runner] runner
106
+ # the runner used to display slides
107
+ # @param [IO] output
108
+ # the output stream
109
+ # @param [IO] error_output
110
+ # the error output stream
111
+ #
112
+ # @api public
113
+ def initialize(runner, output, error_output)
114
+ @runner = runner
115
+ @output = output
116
+ @error_output = error_output
117
+ end
118
+
119
+ # Start presenting slides deck
120
+ #
121
+ # @example
122
+ # cli.start(["slides.md"], {})
123
+ #
124
+ # @param [Array<String>] cmd_args
125
+ # the command arguments
126
+ # @param [Hash{String => String}] env
127
+ # the environment variables
128
+ #
129
+ # @raise [SystemExit]
130
+ #
131
+ # @return [void]
132
+ #
133
+ # @api public
134
+ def start(cmd_args, env)
135
+ parse(cmd_args, env)
136
+
137
+ print_version || print_help || print_errors
138
+
139
+ rescue_errors do
140
+ @runner.run(params[:file],
141
+ color: color(no_color_env?(cmd_args, env)),
142
+ watch: params[:watch])
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ # Print version
149
+ #
150
+ # @raise [SystemExit]
151
+ #
152
+ # @return [nil]
153
+ #
154
+ # @api private
155
+ def print_version
156
+ return unless params[:version]
157
+
158
+ @output.puts VERSION
159
+ exit_success
160
+ end
161
+
162
+ # Print help
163
+ #
164
+ # @raise [SystemExit]
165
+ #
166
+ # @return [nil]
167
+ #
168
+ # @api private
169
+ def print_help
170
+ return unless params[:help] || file_missing?
171
+
172
+ @output.print help
173
+ exit_success
174
+ end
175
+
176
+ # Check whether a file is missing
177
+ #
178
+ # @return [Boolean]
179
+ #
180
+ # @api private
181
+ def file_missing?
182
+ params.errors.size == 1 && params[:file].nil?
183
+ end
184
+
185
+ # Print parsing errors
186
+ #
187
+ # @raise [SystemExit]
188
+ #
189
+ # @return [nil]
190
+ #
191
+ # @api private
192
+ def print_errors
193
+ return unless params.errors.any?
194
+
195
+ @error_output.puts params.errors.summary
196
+ exit_error
197
+ end
198
+
199
+ # Rescue errors
200
+ #
201
+ # @yield a block with error rescue
202
+ #
203
+ # @raise [SystemExit]
204
+ #
205
+ # @return [void]
206
+ #
207
+ # @api private
208
+ def rescue_errors
209
+ yield
210
+ rescue Slideck::Error => err
211
+ raise err if params[:debug]
212
+
213
+ @error_output.puts "Error: #{err}"
214
+ exit_error
215
+ end
216
+
217
+ # Check whether no color environment variable is present
218
+ #
219
+ # @param [Array<String>] cmd_args
220
+ # the command arguments
221
+ # @param [Hash{String => String}] env
222
+ # the environment variables
223
+ #
224
+ # @return [Boolean]
225
+ #
226
+ # @api private
227
+ def no_color_env?(cmd_args, env)
228
+ color_option_given = cmd_args.grep(COLOR_OPTION_PATTERN).any?
229
+ no_color_env_given = !env[NO_COLOR_ENV_NAME].to_s.empty?
230
+
231
+ !color_option_given && no_color_env_given
232
+ end
233
+
234
+ # Read when to color output
235
+ #
236
+ # @param [Boolean] no_color_env
237
+ # the no color environment variable presence
238
+ #
239
+ # @return [String, Symbol]
240
+ #
241
+ # @api private
242
+ def color(no_color_env)
243
+ params[:no_color] || no_color_env ? :never : params[:color]
244
+ end
245
+
246
+ # Exit with the error code
247
+ #
248
+ # @raise [SystemExit]
249
+ #
250
+ # @return [void]
251
+ #
252
+ # @api private
253
+ def exit_error
254
+ exit CODE_ERROR
255
+ end
256
+
257
+ # Exit with the success code
258
+ #
259
+ # @raise [SystemExit]
260
+ #
261
+ # @return [void]
262
+ #
263
+ # @api private
264
+ def exit_success
265
+ exit CODE_SUCCESS
266
+ end
267
+ end # CLI
268
+ end # Slideck
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slideck
4
+ # Responsible for converting Markdown into terminal output
5
+ #
6
+ # @api private
7
+ class Converter
8
+ # The allowed color display modes
9
+ #
10
+ # @return [Array<String>]
11
+ #
12
+ # @api private
13
+ COLOR_DISPLAY_MODES = %w[always auto never].freeze
14
+ private_constant :COLOR_DISPLAY_MODES
15
+
16
+ # Create a Converter instance
17
+ #
18
+ # @example
19
+ # Slideck::Converter.new(TTY::Markdown, color: false)
20
+ #
21
+ # @param [TTY::Markdown] markdown_parser
22
+ # the markdown parser
23
+ # @param [String, Symbol] color
24
+ # the color display out of always, auto or never
25
+ #
26
+ # @api public
27
+ def initialize(markdown_parser, color: nil)
28
+ @markdown_parser = markdown_parser
29
+ @color = validate_color(color)
30
+ end
31
+
32
+ # Convert content into terminal output
33
+ #
34
+ # @example
35
+ # converter.convert("#Title", width: 80)
36
+ #
37
+ # @param [String] content
38
+ # the content to convert
39
+ # @param [Hash, String, Symbol] symbols
40
+ # the converted content symbols
41
+ # @param [Hash{Symbol => Array, String, Symbol}] theme
42
+ # the converted content theme
43
+ # @param [Integer] width
44
+ # the slide width
45
+ #
46
+ # @return [String]
47
+ #
48
+ # @api public
49
+ def convert(content, symbols: nil, theme: nil, width: nil)
50
+ @markdown_parser.parse(
51
+ content, color: @color, symbols: symbols, theme: theme, width: width)
52
+ end
53
+
54
+ private
55
+
56
+ # Validate color display mode
57
+ #
58
+ # @param [Object] value
59
+ # the value to validate
60
+ #
61
+ # @raise [Slideck::InvalidArgumentError]
62
+ #
63
+ # @return [String, Symbol]
64
+ #
65
+ # @api private
66
+ def validate_color(value)
67
+ return value if COLOR_DISPLAY_MODES.include?(value.to_s)
68
+
69
+ raise_invalid_color_error(value)
70
+ end
71
+
72
+ # Raise an error for an invalid color
73
+ #
74
+ # @param [Object] value
75
+ # the invalid value
76
+ #
77
+ # @raise [Slideck::InvalidArgumentError]
78
+ #
79
+ # @return [void]
80
+ #
81
+ # @api private
82
+ def raise_invalid_color_error(value)
83
+ raise InvalidArgumentError,
84
+ "invalid value for color: #{value.inspect}.\n" \
85
+ "The color needs to be one of always, auto or never."
86
+ end
87
+ end # Converter
88
+ end # Slideck
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slideck
4
+ # Raised to signal an error condition
5
+ #
6
+ # @api public
7
+ Error = Class.new(StandardError)
8
+
9
+ # Raised when unable to read slides from a location
10
+ #
11
+ # @api public
12
+ ReadError = Class.new(Error)
13
+
14
+ # Raised when argument is invalid
15
+ #
16
+ # @api public
17
+ InvalidArgumentError = Class.new(Error)
18
+
19
+ # Raised when metadata key is invalid
20
+ #
21
+ # @api public
22
+ class InvalidMetadataKeyError < Error
23
+ MESSAGE = "unknown '%<keys>s' configuration %<name>s\n" \
24
+ "Available keys are: %<meta_keys>s"
25
+
26
+ # Create an InvalidMetadataKeyError instance
27
+ #
28
+ # @example
29
+ # metadata_keys = %i[align footer pager]
30
+ # Slideck::InvalidMetadataKeyError.new(metadata_keys, %i[invalid])
31
+ #
32
+ # @param [Array<Symbol>] metadata_keys
33
+ # the allowed metadata keys
34
+ # @param [Array<String>] keys
35
+ # the invalid metadata keys
36
+ #
37
+ # @api public
38
+ def initialize(metadata_keys, keys)
39
+ super(format(MESSAGE,
40
+ keys: keys.join(", "),
41
+ name: pluralize("key", keys.size),
42
+ meta_keys: metadata_keys.map(&:inspect).join(", ")))
43
+ end
44
+
45
+ private
46
+
47
+ # Pluralize a noun
48
+ #
49
+ # @param [String] noun
50
+ # the noun to pluralize
51
+ # @param [Integer] count
52
+ # the count of items
53
+ #
54
+ # @return [String]
55
+ #
56
+ # @api private
57
+ def pluralize(noun, count = 1)
58
+ "#{noun}#{"s" unless count == 1}"
59
+ end
60
+ end # InvalidMetadataKeyError
61
+ end # Slideck
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slideck
4
+ # Responsible for loading slides
5
+ #
6
+ # @api private
7
+ class Loader
8
+ # Create a Loader instance
9
+ #
10
+ # @example
11
+ # Slideck::Loader.new(File)
12
+ #
13
+ # @param [#read] read_handler
14
+ # the read handler for slides
15
+ #
16
+ # @api public
17
+ def initialize(read_handler)
18
+ @read_handler = read_handler
19
+ end
20
+
21
+ # Load slides from a location
22
+ #
23
+ # @example
24
+ # loader.load("slides.md")
25
+ #
26
+ # @param [String] location
27
+ # the location of the slides to load
28
+ #
29
+ # @raise [Slideck::ReadError]
30
+ #
31
+ # @return [String]
32
+ # the slides content
33
+ #
34
+ # @api public
35
+ def load(location)
36
+ if location.nil?
37
+ raise ReadError, "the location for the slides must be given"
38
+ end
39
+
40
+ @read_handler.read(location)
41
+ rescue SystemCallError => err
42
+ raise ReadError, err.message
43
+ end
44
+ end # Loader
45
+ end # Slideck