lipsttyck 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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/lipsttyck.rb +488 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 355f25e88c6e6339f99cd72b9e8e0f92daba450c
4
+ data.tar.gz: 327f0af5863376af7712c857bc22be09f46b6361
5
+ SHA512:
6
+ metadata.gz: d55db29fab201556d7239d5497cdfb6de72a915b97cb0268e9c8d0eaaabfe10be24c681afbc4ea72d2ff1ea177b8f161c918903d8f6600fca87bdcfc872e6988
7
+ data.tar.gz: 0c2ee3928f776d146e61a5b5cb31384734b306902c13f274b2e42832836873f4b16a1f12e3212b7047abeb2e2ce9afe28665b5f6038d1067b3c7fa54265a057c
data/lib/lipsttyck.rb ADDED
@@ -0,0 +1,488 @@
1
+ require "open3"
2
+ require "pty"
3
+
4
+ ##
5
+ # LipsTTYck provides a collection of utilities for formatting shell output. It
6
+ # parses a simple markup syntax for adding color formatting, right-justification
7
+ # of text, headers, and horizontal dividers.
8
+ #
9
+ # @author Nick Williams
10
+ # @version 0.1.0
11
+ #
12
+ class LipsTTYck
13
+ # Logging/Output Levels
14
+ LOG_LEVEL_NEVER = 0
15
+ LOG_LEVEL_FAILURE = 1
16
+ LOG_LEVEL_ALWAYS = 2
17
+
18
+ # Templates
19
+ @@templatePad = "\n\n"
20
+ @@templatePrefix = ""
21
+ @@templatePadding = " "
22
+ @@templateIndentOn = "#@@templatePrefix#@@templatePadding"
23
+ @@templateIndentOff = ""
24
+ @@templateH1 = "#@@templatePrefix@gray(================================================================================)"
25
+ @@templateH2 = "#@@templatePrefix@gray(--------------------------------------------------------------------------------)"
26
+ @@templateDiv = "#@@templateH2"
27
+ @@templateBlockquote = "| "
28
+ @@templateEnd = "#@@templateH2"
29
+ @@templateSuccess = "[ @green(OK) ]" # "@green(✓)", "@green(✔)"︎
30
+ @@templateFailure = "[ @red(FAIL) ]" # "@red(✗)", "@red(✘)"
31
+ @@templateSkip = "[ @blue(SKIP) ]" # "@blue(~)", "@blue(⋯)"
32
+ @@templateColorPrompt = "@blue"
33
+ @@templateColorStdOut = "@gray"
34
+ @@templateColorStdErr = "@red"
35
+
36
+ ##
37
+ # Initializes a new LipsTTYck instance.
38
+ #
39
+ # @param [Hash] config optional configuration settings with which to initialize (overriding any defaults)
40
+ #
41
+ # @return [LipsTTYck]
42
+ #
43
+ def initialize(config = {})
44
+ # Config
45
+ @config = {
46
+ "margin" => 80,
47
+ "marginTemplateOverrides" => true,
48
+ "showStdOut" => LOG_LEVEL_NEVER,
49
+ "showStdErr" => LOG_LEVEL_FAILURE
50
+ }
51
+
52
+ @config.merge!(config)
53
+
54
+ # Flags
55
+ @entryQueued = false
56
+
57
+ # Caches
58
+ @cacheLast = ""
59
+ @cacheLine = ""
60
+
61
+ # Margin-Sensitive Template Overrides
62
+ if(@config['marginTemplateOverrides'])
63
+ # H1 Override
64
+ @@templateH1 = "#@@templatePrefix@gray("
65
+
66
+ @config['margin'].times do
67
+ @@templateH1 += "="
68
+ end
69
+
70
+ @@templateH1 += ")"
71
+
72
+ # H2 Override
73
+ @@templateH2 = "#@@templatePrefix@gray("
74
+
75
+ @config['margin'].times do
76
+ @@templateH2 += "-"
77
+ end
78
+
79
+ @@templateH2 += ")"
80
+
81
+ # DIV Override
82
+ @@templateDiv = "#@@templateH2"
83
+
84
+ # END Override
85
+ @@templateEnd = "#@@templateH2"
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Outputs the specified text, processing any detected markup.
91
+ #
92
+ # @param [String] text the text to be displayed
93
+ # @param [Boolean] trailingNewline whether or not to automatically append a trailing newline character
94
+ # @param [Boolean] autoIndent whether or not to automatically apply indentation
95
+ # @param [Boolean] rightJustified whether or not to right-align the text according to the configured margin width
96
+ #
97
+ # @return void
98
+ #
99
+ def out(text, trailingNewline = true, autoIndent = true, rightJustified = false)
100
+ formatted = "#{text}"
101
+ unformatted = "#{text}"
102
+ templateIndent = autoIndent ? @@templateIndentOn : @@templateIndentOff
103
+ line = "#@cacheLine"
104
+
105
+ # Apply Indentation
106
+ formatted.gsub!(/\n/m, "\n#{templateIndent}")
107
+
108
+ # Apply Markup Formatting
109
+ case formatted
110
+ when /@H1(?: (.*))?/im
111
+ formatted = "#@@templateH1\n#{templateIndent}@green(#$1)\n#@@templateH1"
112
+
113
+ # Handle Queued Entry
114
+ if @entryQueued
115
+ formatted = "#@@templatePad#{formatted}"
116
+ @entryQueued = false
117
+ end
118
+
119
+ when /@H2(?: (.*))?/im
120
+ stripped = $1
121
+
122
+ if @cacheLast =~ /^@H[12](.*)/i
123
+ formatted="#{templateIndent}@yellow(#{stripped})\n#@@templateH2"
124
+ else
125
+ formatted="#@@templateH2\n#{templateIndent}@yellow(#{stripped})\n#@@templateH2"
126
+ end
127
+
128
+ # Handle Queued Entry
129
+ if @entryQueued
130
+ formatted = "#@@templatePad#{formatted}"
131
+ @entryQueued = false
132
+ end
133
+
134
+ when /@DIV(?: (.*))?/im
135
+ if $1
136
+ formatted = "#@@templateDiv\n#$1"
137
+ else
138
+ formatted = "#@@templateDiv"
139
+ end
140
+
141
+ when /@BLOCKQUOTE\((.*)(?<!\\)\)/im
142
+ formatted = "#$1".gsub(/\n/m, "\n#@@templateBlockquote")
143
+
144
+ when /@SUCCESS(?: (.*))?/im
145
+ formatted = "#@@templateSuccess#@@templatePadding"
146
+
147
+ when /@FAILURE(?: (.*))?/im
148
+ formatted = "#@@templateFailure#@@templatePadding"
149
+
150
+ when /@SKIP(?: (.*))?/im
151
+ formatted = "#@@templateSkip#@@templatePadding"
152
+
153
+ when /@ENTRY(?: (.*))?/im
154
+ @entryQueued = true
155
+ return
156
+
157
+ when /@EXIT(?: (.*))?/im
158
+ # Handle Queued Entry
159
+ if @entryQueued
160
+ @entryQueued = false
161
+ else
162
+ # Handle Exit w/ Heading
163
+ if @cacheLast =~ /^@H[12].*/im
164
+ # Handle Double Newline (After Heading)
165
+ if @@templatePad[0,2] == "\n"
166
+ formatted = @@templatePad[2,-1]
167
+ else
168
+ formatted = "#@@templatePad"
169
+ end
170
+ else
171
+ # Handle Exit w/ Text
172
+ if formatted.length > 5
173
+ formatted = "#{templateIndent}" << formatted[6..-1] << "\n#@@templateEnd#@@templatePad"
174
+ else
175
+ formatted = "#@@templateEnd#@@templatePad"
176
+ end
177
+ end
178
+
179
+ @entryQueued = false
180
+ end
181
+ else
182
+ formatted = "#{templateIndent}" + formatted.gsub(/\\@/, "@")
183
+
184
+ # Handle Queued Entry
185
+ if @entryQueued
186
+ formatted = "#@@templatePad#@@templateDiv\n#{formatted}"
187
+ @entryQueued = false
188
+ end
189
+ end
190
+
191
+ stripped = "#{formatted}"
192
+
193
+ # Apply Color Formatting
194
+ if formatted.include? "@"
195
+ stripped = formatted.gsub(/(?<!\\)@[A-Za-z0-9\-_]*\((.*?)(?<!\\)\)/, '\1')
196
+
197
+ formatted = formatted.gsub(/(?<!\\)@none\((.*?)\)/im, "\033[0m" << '\1' << "\033[0m")
198
+ .gsub(/(?<!\\)@black\((.*?)(?<!\\)\)/im, "\033[0;30m" << '\1' << "\033[0m")
199
+ .gsub(/(?<!\\)@white\((.*?)(?<!\\)\)/im, "\033[1;37m" << '\1' << "\033[0m")
200
+ .gsub(/(?<!\\)@blue\((.*?)(?<!\\)\)/im, "\033[0;34m" << '\1' << "\033[0m")
201
+ .gsub(/(?<!\\)@blue_lt\((.*?)(?<!\\)\)/im, "\033[1;34m" << '\1' << "\033[0m")
202
+ .gsub(/(?<!\\)@green\((.*?)(?<!\\)\)/im, "\033[0;32m" << '\1' << "\033[0m")
203
+ .gsub(/(?<!\\)@green_lt\((.*?)(?<!\\)\)/im, "\033[1;32m" << '\1' << "\033[0m")
204
+ .gsub(/(?<!\\)@cyan\((.*?)(?<!\\)\)/im, "\033[0;36m" << '\1' << "\033[0m")
205
+ .gsub(/(?<!\\)@cyan_lt\((.*?)(?<!\\)\)/im, "\033[1;36m" << '\1' << "\033[0m")
206
+ .gsub(/(?<!\\)@red\((.*?)(?<!\\)\)/im, "\033[0;31m" << '\1' << "\033[0m")
207
+ .gsub(/(?<!\\)@red_lt\((.*?)(?<!\\)\)/im, "\033[1;31m" << '\1' << "\033[0m")
208
+ .gsub(/(?<!\\)@purple\((.*?)(?<!\\)\)/im, "\033[0;35m" << '\1' << "\033[0m")
209
+ .gsub(/(?<!\\)@purple_lt\((.*?)(?<!\\)\)/im, "\033[1;35m" << '\1' << "\033[0m")
210
+ .gsub(/(?<!\\)@yellow\((.*?)(?<!\\)\)/im, "\033[0;33m" << '\1' << "\033[0m")
211
+ .gsub(/(?<!\\)@yellow_lt\((.*?)(?<!\\)\)/im, "\033[1;33m" << '\1' << "\033[0m")
212
+ .gsub(/(?<!\\)@gray\((.*?)(?<!\\)\)/im, "\033[1;30m" << '\1' << "\033[0m")
213
+ .gsub(/(?<!\\)@gray_lt\((.*?)(?<!\\)\)/im, "\033[0;37m" << '\1' << "\033[0m")
214
+ end
215
+
216
+ # Strip Escaping Slashes
217
+ escapedPattern = /\\([@\(\)])/im
218
+
219
+ stripped.gsub!(escapedPattern, "\\1")
220
+ formatted.gsub!(escapedPattern, "\\1")
221
+
222
+ # Apply Right-Justification
223
+ if rightJustified
224
+ paddingSize = (@config['margin'] - line.length) - stripped.length
225
+ padding = "%#{paddingSize}s" % ""
226
+
227
+ formatted = "#{padding}#{formatted}"
228
+ end
229
+
230
+ # Apply Trailing Newline
231
+ if trailingNewline
232
+ formatted = "#{formatted}\n"
233
+ end
234
+
235
+ # Output Formatted String
236
+ print "#{formatted}"
237
+
238
+ # Cache Unformatted Version
239
+ @cacheLast = unformatted
240
+
241
+ # Cache Current Line
242
+ if !trailingNewline
243
+ @cacheLine = @cacheLine << stripped
244
+ else
245
+ @cacheLine = ""
246
+ end
247
+ end
248
+
249
+ ##
250
+ # Prompts for input, first outputting the specified text.
251
+ #
252
+ # @param [String] label the label to be used when prompting
253
+ # @param [mixed] value the existing value if available
254
+ # @param [mixed] default a default fallback value if the response is blank
255
+ # @param [Boolean] verbose whether or not to always prompt, regardless of a passed value
256
+ #
257
+ # @return [mixed] the result of the prompt
258
+ #
259
+ def in(label, value = nil, default = nil, verbose = false)
260
+ # Don't continue if verbosity is off and value is already set.
261
+ if !verbose && !self.class.unassigned(value)
262
+ return
263
+ end
264
+
265
+ # Default value, falls back to existing value if possible.
266
+ if default.nil? && !self.class.unassigned(value)
267
+ default = value
268
+ end
269
+
270
+ # Prompt for input.
271
+ prompt = "#{label}"
272
+
273
+ if default.nil?
274
+ prompt += ": "
275
+ else
276
+ prompt += " \\\(#{default}\\\): "
277
+ end
278
+
279
+ out("#@@templateColorPrompt(#{prompt})", false)
280
+
281
+ value = $stdin.gets.chomp
282
+
283
+ # Fall back default value if input is empty.
284
+ if self.class.unassigned(value)
285
+ value = default
286
+ end
287
+
288
+ return value
289
+ end
290
+
291
+ ##
292
+ # Prompts for a boolean response (y/n).
293
+ #
294
+ # @param [String] label the label to be used when prompting
295
+ # @param [Boolean] value the existing value if available
296
+ # @param [Boolean] default a default fallback value if the response is blank
297
+ # @param [Boolean] verbose whether or not to always prompt, regardless of a passed value
298
+ #
299
+ # @return [Boolean] the result of the confirmation prompt
300
+ #
301
+ def confirm(label, value = nil, default = true, verbose = false)
302
+ # Don't continue if verbosity is off and value is already set.
303
+ if !verbose && !self.class.unassigned(value)
304
+ return
305
+ end
306
+
307
+ # Default value, falls back to existing value if possible.
308
+ if default.nil? && !self.class.unassigned(value)
309
+ default = value
310
+ end
311
+
312
+ # Prompt for input.
313
+ prompt = "#{label}"
314
+
315
+ if default
316
+ prompt += " \\\(Y/n\\\): "
317
+ else
318
+ prompt += " \\\(y/N\\\): "
319
+ end
320
+
321
+ out("#@@templateColorPrompt(#{prompt})", false)
322
+
323
+ response = $stdin.gets.chomp
324
+
325
+ # Fall back default value if input is empty.
326
+ if self.class.unassigned(response)
327
+ value = default
328
+ else
329
+ if response.casecmp("y") == 0
330
+ value = true
331
+ elsif response.casecmp("n") == 0
332
+ value = false
333
+ else
334
+ out "Invalid response \"#{response}\""
335
+ value = confirm(label, value, default, verbose)
336
+ end
337
+ end
338
+
339
+ return value
340
+ end
341
+
342
+ ##
343
+ # Attempts to execute the specified shell command(s), capturing STDIN and
344
+ # STDOUT for optional output to the user.
345
+ #
346
+ # @param [String] label a label to use when prompting for input
347
+ # @param [Array] commands one or more commands to be executed
348
+ #
349
+ # @return [Boolean] whetehr or not all commands were executed successfully
350
+ #
351
+ def attempt(label, commands)
352
+ # Setup
353
+ commandOutputLogs = [];
354
+ failed = false
355
+
356
+ # Convert single command to an array.
357
+ if !commands.kind_of?(Array)
358
+ commands = [commands]
359
+ end
360
+
361
+ # Output label.
362
+ self.out(label, false)
363
+
364
+ # Execute each command.
365
+ commands.each do|command|
366
+ # Output progress indicator for current command.
367
+ self.out(".", false, false)
368
+
369
+ Open3.popen3(command) do |stdin, stdout, stderr, waitThread|
370
+ commandOutputLogs << {
371
+ :stdout => stdout.eof? ? nil : stdout.read,
372
+ :stderr => stderr.eof? ? nil : stderr.read
373
+ }
374
+
375
+ if waitThread.value.to_i > 0
376
+ failed = true
377
+ break
378
+ end
379
+ end
380
+ end
381
+
382
+ # Display the result of the attempted commands.
383
+ if failed
384
+ self.out("@FAILURE", true, false, true)
385
+ else
386
+ self.out("@SUCCESS", true, false, true)
387
+ end
388
+
389
+ # Output I/O streams according to config.
390
+ if @config['showStdOut'] == LOG_LEVEL_ALWAYS || failed && @config['showStdOut'] == LOG_LEVEL_FAILURE || @config['showStdErr'] == LOG_LEVEL_ALWAYS || failed && @config['showStdErr'] == LOG_LEVEL_FAILURE
391
+ lastIndex = commandOutputLogs.length - 1
392
+
393
+ self.out("")
394
+
395
+ commandOutputLogs.each_index do |i|
396
+ # STDOUT
397
+ if (@config['showStdOut'] == LOG_LEVEL_ALWAYS) || (failed && @config['showStdOut'] == LOG_LEVEL_FAILURE && i == lastIndex)
398
+ blockquote(commandOutputLogs[i][:stdout], @@templateColorStdOut)
399
+ end
400
+
401
+ # STDERR
402
+ if (@config['showStdErr']) == LOG_LEVEL_ALWAYS || (failed && @config['showStdErr'] == LOG_LEVEL_FAILURE && i == lastIndex)
403
+ blockquote(commandOutputLogs[i][:stderr], @@templateColorStdErr)
404
+ end
405
+ end
406
+ end
407
+
408
+ return !failed
409
+ end
410
+
411
+ ##
412
+ # Executes the specified command, passing any output to the respective
413
+ # output streams.
414
+ #
415
+ # @param [Array] commands one or more commands to be executed
416
+ #
417
+ # @return void
418
+ #
419
+ def passthru(commands, colorStdOut = "@gray", colorStdErr = "@red", colorStdIn = "@blue")
420
+ # Convert single command to an array.
421
+ if !commands.kind_of?(Array)
422
+ commands = [commands]
423
+ end
424
+
425
+ commands.each do |command|
426
+ PTY.spawn(command) do |stdout, stdin, pid|
427
+ # self.out("#{colorStdIn}(> " + self.class.escape(command) + ")", true, false)
428
+ stdout.each_line { |line| self.out("#{colorStdOut}(| " + self.class.escape(line.gsub!(/\n/m, "")) + ")") }
429
+ end
430
+ end
431
+ end
432
+
433
+ ##
434
+ # Outputs an indented block of text.
435
+ #
436
+ # @param [String] lines the text to be displayed
437
+ # @param [String] prefix optional string to prepend to each line
438
+ # @param [String] postfix optional string to append to each line
439
+ #
440
+ # @return void
441
+ #
442
+ def blockquote(lines, color = "@gray")
443
+ if lines
444
+ # Escape special characters.
445
+ lines = self.class.escape(lines)
446
+
447
+ lines.each_line do |line|
448
+ self.out("#{color}(| " + line.gsub!(/(\n)$/, "") + ")")
449
+ end
450
+
451
+ self.out("")
452
+ end
453
+ end
454
+
455
+ private
456
+
457
+ ##
458
+ # Checks if the specified variable is empty or nil.
459
+ #
460
+ # @param [mixed] the value to be checked
461
+ #
462
+ # @return [Boolean] whether or not the value should be considered unassigned
463
+ #
464
+ def self.unassigned(var)
465
+ result = false
466
+
467
+ result |= ((var.respond_to? :empty?) && var.empty?)
468
+ result |= ((var.respond_to? :nil?) && var.nil?)
469
+
470
+ return result
471
+ end
472
+
473
+ ##
474
+ # Escapes all special characters otherwise interpreted as formatting markup.
475
+ #
476
+ # @param [String] text the text to be escaped
477
+ #
478
+ # @return [String] the escaped text
479
+ #
480
+ def self.escape(text)
481
+ # Escape special characters.
482
+ text.gsub!(/@/, "\\@")
483
+ text.gsub!(/\(/, "\\(")
484
+ text.gsub!(/\)/, "\\)")
485
+
486
+ return text
487
+ end
488
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lipsttyck
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A library for formatting terminal output using a simplified markup.
14
+ email: projects.lipsttyck@nickawilliams.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/lipsttyck.rb
20
+ homepage: https://github.com/RogWilco/lipsttyck
21
+ licenses:
22
+ - BSD-3-Clause
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.0.6
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: A library for formatting terminal output.
44
+ test_files: []