slideck 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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