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
@@ -0,0 +1,326 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slideck
|
4
|
+
# Responsible for rendering slides
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Renderer
|
8
|
+
# The terminal cursor
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# renderer.cursor
|
12
|
+
#
|
13
|
+
# @return [TTY::Cursor]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
attr_reader :cursor
|
17
|
+
|
18
|
+
# Create a Renderer instance
|
19
|
+
#
|
20
|
+
# @param [Slideck::Converter] converter
|
21
|
+
# the markdown to terminal output converter
|
22
|
+
# @param [Strings::ANSI] ansi
|
23
|
+
# the ansi codes handler
|
24
|
+
# @param [TTY::Cursor] cursor
|
25
|
+
# the cursor navigation
|
26
|
+
# @param [Integer] width
|
27
|
+
# the screen width
|
28
|
+
# @param [Integer] height
|
29
|
+
# the screen height
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def initialize(converter, ansi, cursor, width: nil, height: nil)
|
33
|
+
@converter = converter
|
34
|
+
@ansi = ansi
|
35
|
+
@cursor = cursor
|
36
|
+
@width = width
|
37
|
+
@height = height
|
38
|
+
|
39
|
+
freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a Renderer with a new screen size
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# renderer.resize(200, 50)
|
46
|
+
#
|
47
|
+
# @param [Integer] width
|
48
|
+
# the screen width
|
49
|
+
# @param [Integer] height
|
50
|
+
# the screen height
|
51
|
+
#
|
52
|
+
# @return [Slideck::Renderer]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def resize(width, height)
|
56
|
+
self.class.new(@converter, @ansi, @cursor, width: width, height: height)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Render a slide
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# renderer.render(metadata, slide, 1, 5)
|
63
|
+
#
|
64
|
+
# @param [Slideck::Metadata] metadata
|
65
|
+
# the global metadata
|
66
|
+
# @param [Hash{Symbol => Hash, String}, nil] slide
|
67
|
+
# the current slide to render
|
68
|
+
# @param [Integer] current_num
|
69
|
+
# the current slide number
|
70
|
+
# @param [Integer] num_of_slides
|
71
|
+
# the number of slides
|
72
|
+
#
|
73
|
+
# @return [String]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def render(metadata, slide, current_num, num_of_slides)
|
77
|
+
slide_metadata = slide && slide[:metadata]
|
78
|
+
[].tap do |out|
|
79
|
+
out << render_content(metadata, slide) if slide
|
80
|
+
out << render_footer(metadata, slide_metadata)
|
81
|
+
out << render_pager(metadata, slide_metadata,
|
82
|
+
current_num, num_of_slides)
|
83
|
+
end.join
|
84
|
+
end
|
85
|
+
|
86
|
+
# Clear terminal screen
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# renderer.clear
|
90
|
+
#
|
91
|
+
# @return [String]
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def clear
|
95
|
+
cursor.clear_screen + cursor.move_to(0, 0)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Render slide content
|
101
|
+
#
|
102
|
+
# @param [Slideck::Metadata] metadata
|
103
|
+
# the global metadata
|
104
|
+
# @param [Hash{Symbol => Hash, String}] slide
|
105
|
+
# the slide to render
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
def render_content(metadata, slide)
|
111
|
+
alignment, margin, symbols, theme =
|
112
|
+
*select_metadata(metadata, slide[:metadata], :align, :margin,
|
113
|
+
:symbols, :theme)
|
114
|
+
converted = convert_markdown(slide[:content], margin, symbols, theme)
|
115
|
+
|
116
|
+
render_section(converted.lines, alignment, margin)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Render footer
|
120
|
+
#
|
121
|
+
# @param [Slideck::Metadata] metadata
|
122
|
+
# the global metadata
|
123
|
+
# @param [Slideck::Metadata] slide_metadata
|
124
|
+
# the slide metadata
|
125
|
+
#
|
126
|
+
# @return [String]
|
127
|
+
#
|
128
|
+
# @api private
|
129
|
+
def render_footer(metadata, slide_metadata)
|
130
|
+
footer_metadata = pick_metadata(metadata, slide_metadata, :footer)
|
131
|
+
return if (text = footer_metadata[:text]).empty?
|
132
|
+
|
133
|
+
alignment = footer_metadata[:align] || metadata.footer[:align]
|
134
|
+
margin, symbols, theme =
|
135
|
+
*select_metadata(metadata, slide_metadata, :margin, :symbols, :theme)
|
136
|
+
converted = convert_markdown(text, margin, symbols, theme).chomp
|
137
|
+
|
138
|
+
render_section(converted.lines, alignment, margin)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Render pager
|
142
|
+
#
|
143
|
+
# @param [Slideck::Metadata] metadata
|
144
|
+
# the global metadata
|
145
|
+
# @param [Slideck::Metadata] slide_metadata
|
146
|
+
# the slide metadata
|
147
|
+
# @param [Integer] current_num
|
148
|
+
# the current slide number
|
149
|
+
# @param [Integer] num_of_slides
|
150
|
+
# the number of slides
|
151
|
+
#
|
152
|
+
# @return [String]
|
153
|
+
#
|
154
|
+
# @api private
|
155
|
+
def render_pager(metadata, slide_metadata, current_num, num_of_slides)
|
156
|
+
pager_metadata = pick_metadata(metadata, slide_metadata, :pager)
|
157
|
+
return if (text = pager_metadata[:text]).empty?
|
158
|
+
|
159
|
+
alignment = pager_metadata[:align] || metadata.pager[:align]
|
160
|
+
margin, symbols, theme =
|
161
|
+
*select_metadata(metadata, slide_metadata, :margin, :symbols, :theme)
|
162
|
+
formatted_text = format(text, page: current_num, total: num_of_slides)
|
163
|
+
converted = convert_markdown(formatted_text, margin, symbols, theme).chomp
|
164
|
+
|
165
|
+
render_section(converted.lines, alignment, margin)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Select configuration(s) by name(s) from metadata
|
169
|
+
#
|
170
|
+
# @param [Slideck::Metadata] metadata
|
171
|
+
# the global metadata
|
172
|
+
# @param [Slideck::Metadata] slide_metadata
|
173
|
+
# the slide metadata
|
174
|
+
# @param [Array<Symbol>] names
|
175
|
+
# the configuration names
|
176
|
+
#
|
177
|
+
# @return [Array<Object>]
|
178
|
+
#
|
179
|
+
# @api private
|
180
|
+
def select_metadata(metadata, slide_metadata, *names)
|
181
|
+
names.each_with_object([]) do |name, selected|
|
182
|
+
selected << pick_metadata(metadata, slide_metadata, name)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Pick configuration by name from metadata
|
187
|
+
#
|
188
|
+
# @param [Slideck::Metadata] metadata
|
189
|
+
# the global metadata
|
190
|
+
# @param [Slideck::Metadata] slide_metadata
|
191
|
+
# the slide metadata
|
192
|
+
# @param [Symbol] name
|
193
|
+
# the configuration name
|
194
|
+
#
|
195
|
+
# @return [Hash, Slideck::Alignment, Slideck::Margin, String, Symbol]
|
196
|
+
#
|
197
|
+
# @api private
|
198
|
+
def pick_metadata(metadata, slide_metadata, name)
|
199
|
+
slide_metadata_item = slide_metadata && slide_metadata.send(name)
|
200
|
+
slide_metadata_item || metadata.send(name)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Render section with aligned lines
|
204
|
+
#
|
205
|
+
# @param [Array<String>] lines
|
206
|
+
# the lines to align
|
207
|
+
# @param [Slideck::Alignment] alignment
|
208
|
+
# the section alignment
|
209
|
+
# @param [Slideck::Margin] margin
|
210
|
+
# the slide margin
|
211
|
+
#
|
212
|
+
# @return [String]
|
213
|
+
#
|
214
|
+
# @api private
|
215
|
+
def render_section(lines, alignment, margin)
|
216
|
+
max_line = max_line_length(lines)
|
217
|
+
left = find_left_column(alignment.horizontal, margin, max_line)
|
218
|
+
top = find_top_row(alignment.vertical, margin, lines.size)
|
219
|
+
|
220
|
+
lines.map.with_index do |line, i|
|
221
|
+
cursor.move_to(left, top + i) + line
|
222
|
+
end.join
|
223
|
+
end
|
224
|
+
|
225
|
+
# Find a left column
|
226
|
+
#
|
227
|
+
# @param [String] alignment
|
228
|
+
# the horizontal alignment
|
229
|
+
# @param [Slideck::Margin] margin
|
230
|
+
# the slide margin
|
231
|
+
# @param [Integer] content_length
|
232
|
+
# the maximum content length
|
233
|
+
#
|
234
|
+
# @return [Integer]
|
235
|
+
#
|
236
|
+
# @api private
|
237
|
+
def find_left_column(alignment, margin, content_length)
|
238
|
+
case alignment
|
239
|
+
when "left"
|
240
|
+
margin.left
|
241
|
+
when "center"
|
242
|
+
margin.left + ((slide_width(margin) - content_length) / 2)
|
243
|
+
when "right"
|
244
|
+
margin.left + (slide_width(margin) - content_length)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Find a top row
|
249
|
+
#
|
250
|
+
# @param [String] alignment
|
251
|
+
# the vertical alignment
|
252
|
+
# @param [Slideck::Margin] margin
|
253
|
+
# the slide margin
|
254
|
+
# @param [Integer] num_of_lines
|
255
|
+
# the number of content lines
|
256
|
+
#
|
257
|
+
# @return [Integer]
|
258
|
+
#
|
259
|
+
# @api private
|
260
|
+
def find_top_row(alignment, margin, num_of_lines)
|
261
|
+
case alignment
|
262
|
+
when "top"
|
263
|
+
margin.top
|
264
|
+
when "center"
|
265
|
+
margin.top + ((slide_height(margin) - num_of_lines) / 2)
|
266
|
+
when "bottom"
|
267
|
+
margin.top + (slide_height(margin) - num_of_lines)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Convert markdown content to terminal output
|
272
|
+
#
|
273
|
+
# @param [String] content
|
274
|
+
# the content to convert to terminal output
|
275
|
+
# @param [Slideck::Margin] margin
|
276
|
+
# the slide margin
|
277
|
+
# @param [Hash, String, Symbol] symbols
|
278
|
+
# the converted content symbols
|
279
|
+
# @param [Hash{Symbol => Array, String, Symbol}] theme
|
280
|
+
# the converted content theme
|
281
|
+
#
|
282
|
+
# @return [String]
|
283
|
+
#
|
284
|
+
# @api private
|
285
|
+
def convert_markdown(content, margin, symbols, theme)
|
286
|
+
@converter.convert(
|
287
|
+
content, symbols: symbols, theme: theme, width: slide_width(margin))
|
288
|
+
end
|
289
|
+
|
290
|
+
# Find maximum line length
|
291
|
+
#
|
292
|
+
# @param [Array<String>] lines
|
293
|
+
# the lines to search through
|
294
|
+
#
|
295
|
+
# @return [Integer]
|
296
|
+
#
|
297
|
+
# @api private
|
298
|
+
def max_line_length(lines)
|
299
|
+
lines.map { |line| @ansi.sanitize(line).size }.max
|
300
|
+
end
|
301
|
+
|
302
|
+
# Calculate slide height
|
303
|
+
#
|
304
|
+
# @param [Slideck::Margin] margin
|
305
|
+
# the slide margin
|
306
|
+
#
|
307
|
+
# @return [Integer]
|
308
|
+
#
|
309
|
+
# @api private
|
310
|
+
def slide_height(margin)
|
311
|
+
@height - margin.top - margin.bottom
|
312
|
+
end
|
313
|
+
|
314
|
+
# Calculate slide width
|
315
|
+
#
|
316
|
+
# @param [Slideck::Margin] margin
|
317
|
+
# the slide margin
|
318
|
+
#
|
319
|
+
# @return [Integer]
|
320
|
+
#
|
321
|
+
# @api private
|
322
|
+
def slide_width(margin)
|
323
|
+
@width - margin.left - margin.right
|
324
|
+
end
|
325
|
+
end # Renderer
|
326
|
+
end # Slideck
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "listen"
|
4
|
+
require "strings-ansi"
|
5
|
+
require "strscan"
|
6
|
+
require "tty-cursor"
|
7
|
+
require "tty-markdown"
|
8
|
+
require "tty-reader"
|
9
|
+
require "yaml"
|
10
|
+
|
11
|
+
require_relative "alignment"
|
12
|
+
require_relative "converter"
|
13
|
+
require_relative "loader"
|
14
|
+
require_relative "margin"
|
15
|
+
require_relative "metadata"
|
16
|
+
require_relative "metadata_converter"
|
17
|
+
require_relative "metadata_defaults"
|
18
|
+
require_relative "metadata_parser"
|
19
|
+
require_relative "metadata_wrapper"
|
20
|
+
require_relative "parser"
|
21
|
+
require_relative "presenter"
|
22
|
+
require_relative "renderer"
|
23
|
+
require_relative "tracker"
|
24
|
+
require_relative "transformer"
|
25
|
+
|
26
|
+
module Slideck
|
27
|
+
# Parse and display slides
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
class Runner
|
31
|
+
# Create a Runner instance
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# Slideck::Runner.new(TTY::Screen, $stdin, $stdout, {})
|
35
|
+
#
|
36
|
+
# @param [TTY::Screen] screen
|
37
|
+
# the terminal screen size
|
38
|
+
# @param [IO] input
|
39
|
+
# the input stream
|
40
|
+
# @param [IO] output
|
41
|
+
# the output stream
|
42
|
+
# @param [Hash] env
|
43
|
+
# the environment variables
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def initialize(screen, input, output, env)
|
47
|
+
@screen = screen
|
48
|
+
@input = input
|
49
|
+
@output = output
|
50
|
+
@env = env
|
51
|
+
end
|
52
|
+
|
53
|
+
# Run the slides in a terminal
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# runner.run("slides.md", color: :always, watch: true)
|
57
|
+
#
|
58
|
+
# @param [String] filename
|
59
|
+
# the filename with slides
|
60
|
+
# @param [String, Symbol] color
|
61
|
+
# the color display out of always, auto or never
|
62
|
+
# @param [Boolean] watch
|
63
|
+
# whether to watch for changes in a filename
|
64
|
+
#
|
65
|
+
# @return [void]
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def run(filename, color: nil, watch: nil)
|
69
|
+
transformer = build_transformer
|
70
|
+
presenter = build_presenter(color) { transformer.read(filename) }
|
71
|
+
|
72
|
+
if watch
|
73
|
+
listener = build_listener(filename) { presenter.reload.render }
|
74
|
+
listener.start
|
75
|
+
end
|
76
|
+
|
77
|
+
presenter.start
|
78
|
+
ensure
|
79
|
+
listener && listener.stop
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Build transformer
|
85
|
+
#
|
86
|
+
# @return [Slideck::Transformer]
|
87
|
+
#
|
88
|
+
# @api private
|
89
|
+
def build_transformer
|
90
|
+
loader = Loader.new(::File)
|
91
|
+
Transformer.new(loader, build_parser, build_metadata_wrapper)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Build parser
|
95
|
+
#
|
96
|
+
# @return [Slideck::Parser]
|
97
|
+
#
|
98
|
+
# @api private
|
99
|
+
def build_parser
|
100
|
+
metadata_parser = MetadataParser.new(
|
101
|
+
::YAML, permitted_classes: [Symbol], symbolize_names: true)
|
102
|
+
Parser.new(::StringScanner, metadata_parser)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Build metadata wrapper
|
106
|
+
#
|
107
|
+
# @return [Slideck::MetadataWrapper]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
def build_metadata_wrapper
|
111
|
+
metadata_converter = MetadataConverter.new(Alignment, Margin)
|
112
|
+
metadata_defaults = MetadataDefaults.new(Alignment, Margin)
|
113
|
+
MetadataWrapper.new(Metadata, metadata_converter, metadata_defaults)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Build presenter
|
117
|
+
#
|
118
|
+
# @param [String, Symbol] color
|
119
|
+
# the color display out of always, auto or never
|
120
|
+
# @param [Proc] reloader
|
121
|
+
# the metadata and slides reloader
|
122
|
+
#
|
123
|
+
# @return [Slideck::Presenter]
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
def build_presenter(color, &reloader)
|
127
|
+
reader = TTY::Reader.new(input: @input, output: @output, env: @env,
|
128
|
+
interrupt: :exit)
|
129
|
+
converter = Converter.new(TTY::Markdown, color: color)
|
130
|
+
renderer = Renderer.new(converter, Strings::ANSI, TTY::Cursor,
|
131
|
+
width: @screen.width, height: @screen.height)
|
132
|
+
tracker = Tracker.for(0)
|
133
|
+
Presenter.new(reader, renderer, tracker, @screen, @output, &reloader)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Build a listener for changes in a filename
|
137
|
+
#
|
138
|
+
# @param [String] filename
|
139
|
+
# the filename with slides
|
140
|
+
#
|
141
|
+
# @return [Listen::Listener]
|
142
|
+
#
|
143
|
+
# @api private
|
144
|
+
def build_listener(filename)
|
145
|
+
watched_dir = File.expand_path(File.dirname(filename))
|
146
|
+
watched_file = File.expand_path(filename)
|
147
|
+
Listen.to(watched_dir) do |changed_files, _, _|
|
148
|
+
yield if changed_files.include?(watched_file)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end # Runner
|
152
|
+
end # Slideck
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slideck
|
4
|
+
# Responsible for tracking current slide number
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Tracker
|
8
|
+
# Create a Tracker instance with the current slide set to zero
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# Slideck::Tracker.for(11)
|
12
|
+
#
|
13
|
+
# @param [Integer] total
|
14
|
+
# the total number of slides
|
15
|
+
#
|
16
|
+
# @return [Slideck::Tracker]
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def self.for(total)
|
20
|
+
new(0, total)
|
21
|
+
end
|
22
|
+
|
23
|
+
# The current slide number
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# tracker.current
|
27
|
+
#
|
28
|
+
# @return [Integer]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
attr_reader :current
|
32
|
+
|
33
|
+
# The total number of slides
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# tracker.total
|
37
|
+
#
|
38
|
+
# @return [Integer]
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
attr_reader :total
|
42
|
+
|
43
|
+
# Create a Tracker instance
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# Slideck::Tracker.new(0, 11)
|
47
|
+
#
|
48
|
+
# @param [Integer] current
|
49
|
+
# the current slide number
|
50
|
+
# @param [Integer] total
|
51
|
+
# the total number of slides
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def initialize(current, total)
|
55
|
+
@current = current
|
56
|
+
@total = total
|
57
|
+
|
58
|
+
freeze
|
59
|
+
end
|
60
|
+
|
61
|
+
# Move to the next slide
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# tracker = tracker.next
|
65
|
+
#
|
66
|
+
# @return [Slideck::Tracker]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def next
|
70
|
+
return self if current >= total - 1
|
71
|
+
|
72
|
+
self.class.new(current + 1, total)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Move to the previous slide
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# tracker = tracker.previous
|
79
|
+
#
|
80
|
+
# @return [Slideck::Tracker]
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def previous
|
84
|
+
return self if current.zero?
|
85
|
+
|
86
|
+
self.class.new(current - 1, total)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Move to the first slide
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# tracker = tracker.first
|
93
|
+
#
|
94
|
+
# @return [Slideck::Tracker]
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
def first
|
98
|
+
self.class.new(0, total)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Move to the last slide
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# tracker = tracker.last
|
105
|
+
#
|
106
|
+
# @return [Slideck::Tracker]
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def last
|
110
|
+
self.class.new(total - 1, total)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Go to a specific slide number
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# tracker = tracker.go_to(5)
|
117
|
+
#
|
118
|
+
# @param [Integer] slide_no
|
119
|
+
# the slide number
|
120
|
+
#
|
121
|
+
# @return [Slideck::Tracker]
|
122
|
+
#
|
123
|
+
# @api public
|
124
|
+
def go_to(slide_no)
|
125
|
+
return self if slide_no < 0 || total - 1 < slide_no
|
126
|
+
|
127
|
+
self.class.new(slide_no, total)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Resize to the new total
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# tracker = tracker.resize(10)
|
134
|
+
#
|
135
|
+
# @param [Integer] new_total
|
136
|
+
# the new total
|
137
|
+
#
|
138
|
+
# @return [Slideck::Tracker]
|
139
|
+
#
|
140
|
+
# @api public
|
141
|
+
def resize(new_total)
|
142
|
+
return self if new_total < 0 || total == new_total
|
143
|
+
|
144
|
+
self.class.new(reset_current(new_total), new_total)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Reset current
|
150
|
+
#
|
151
|
+
# @param [Integer] new_total
|
152
|
+
# the new total
|
153
|
+
#
|
154
|
+
# @return [Integer]
|
155
|
+
#
|
156
|
+
# @api private
|
157
|
+
def reset_current(new_total)
|
158
|
+
if current < new_total
|
159
|
+
current
|
160
|
+
elsif new_total.zero?
|
161
|
+
0
|
162
|
+
else
|
163
|
+
new_total - 1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end # Tracker
|
167
|
+
end # Slideck
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slideck
|
4
|
+
# Responsible for transforming file content into metadata and slides
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Transformer
|
8
|
+
# Create a Transformer instance
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# Transformer.new(loader, parser, metadata_wrapper)
|
12
|
+
#
|
13
|
+
# @param [Slideck::Loader] loader
|
14
|
+
# the file loader
|
15
|
+
# @param [Slideck::Parser] parser
|
16
|
+
# the file content parser
|
17
|
+
# @param [Slideck::MetadataWrapper] metadata_wrapper
|
18
|
+
# the metadata wrapper
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
def initialize(loader, parser, metadata_wrapper)
|
22
|
+
@loader = loader
|
23
|
+
@parser = parser
|
24
|
+
@metadata_wrapper = metadata_wrapper
|
25
|
+
end
|
26
|
+
|
27
|
+
# Read metadata and slides from a file
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# transformer.read("slides.md")
|
31
|
+
#
|
32
|
+
# @param [String] filename
|
33
|
+
# the filename to read metadata and slides from
|
34
|
+
#
|
35
|
+
# @return [Array<Slideck::Metadata, Array<Hash>>]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def read(filename)
|
39
|
+
@metadata_wrapper.wrap(@parser.parse(@loader.load(filename)))
|
40
|
+
end
|
41
|
+
end # Transformer
|
42
|
+
end # Slideck
|
data/lib/slideck.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-screen"
|
4
|
+
|
5
|
+
require_relative "slideck/cli"
|
6
|
+
require_relative "slideck/errors"
|
7
|
+
require_relative "slideck/runner"
|
8
|
+
require_relative "slideck/version"
|
9
|
+
|
10
|
+
# Present Markdown-powered slide decks in a terminal
|
11
|
+
module Slideck
|
12
|
+
# Run slides deck
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# Slideck.run
|
16
|
+
#
|
17
|
+
# @param [Array<String>] cmd_args
|
18
|
+
# the command arguments
|
19
|
+
# @param [Hash{String => String}] env
|
20
|
+
# the environment variables
|
21
|
+
#
|
22
|
+
# @return [void]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def self.run(cmd_args = ARGV, env = ENV)
|
26
|
+
runner = Runner.new(TTY::Screen, $stdin, $stdout, env)
|
27
|
+
cli = CLI.new(runner, $stdout, $stderr)
|
28
|
+
cli.start(cmd_args, env)
|
29
|
+
end
|
30
|
+
end # Slideck
|