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
@@ -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
|