lipsttyck 0.1.0

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