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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +555 -0
- data/README.md +475 -0
- data/exe/slideck +5 -0
- data/lib/slideck/alignment.rb +201 -0
- data/lib/slideck/cli.rb +268 -0
- data/lib/slideck/converter.rb +88 -0
- data/lib/slideck/errors.rb +61 -0
- data/lib/slideck/loader.rb +45 -0
- data/lib/slideck/margin.rb +345 -0
- data/lib/slideck/metadata.rb +153 -0
- data/lib/slideck/metadata_converter.rb +95 -0
- data/lib/slideck/metadata_defaults.rb +84 -0
- data/lib/slideck/metadata_parser.rb +188 -0
- data/lib/slideck/metadata_wrapper.rb +66 -0
- data/lib/slideck/parser.rb +139 -0
- data/lib/slideck/presenter.rb +272 -0
- data/lib/slideck/renderer.rb +326 -0
- data/lib/slideck/runner.rb +152 -0
- data/lib/slideck/tracker.rb +167 -0
- data/lib/slideck/transformer.rb +42 -0
- data/lib/slideck/version.rb +5 -0
- data/lib/slideck.rb +30 -0
- metadata +203 -0
data/lib/slideck/cli.rb
ADDED
@@ -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
|