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