ngage 0.0.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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/exe/ngage +55 -0
  4. data/lib/ngage.rb +3 -0
  5. data/lib/ngage/jekyll.rb +204 -0
  6. data/lib/ngage/jekyll/cleaner.rb +111 -0
  7. data/lib/ngage/jekyll/collection.rb +235 -0
  8. data/lib/ngage/jekyll/command.rb +103 -0
  9. data/lib/ngage/jekyll/commands/build.rb +93 -0
  10. data/lib/ngage/jekyll/commands/clean.rb +45 -0
  11. data/lib/ngage/jekyll/commands/doctor.rb +173 -0
  12. data/lib/ngage/jekyll/commands/help.rb +34 -0
  13. data/lib/ngage/jekyll/commands/new.rb +157 -0
  14. data/lib/ngage/jekyll/commands/new_theme.rb +42 -0
  15. data/lib/ngage/jekyll/commands/serve.rb +354 -0
  16. data/lib/ngage/jekyll/commands/serve/live_reload_reactor.rb +122 -0
  17. data/lib/ngage/jekyll/commands/serve/livereload_assets/livereload.js +1183 -0
  18. data/lib/ngage/jekyll/commands/serve/servlet.rb +203 -0
  19. data/lib/ngage/jekyll/commands/serve/websockets.rb +81 -0
  20. data/lib/ngage/jekyll/configuration.rb +391 -0
  21. data/lib/ngage/jekyll/converter.rb +54 -0
  22. data/lib/ngage/jekyll/converters/identity.rb +41 -0
  23. data/lib/ngage/jekyll/converters/markdown.rb +116 -0
  24. data/lib/ngage/jekyll/converters/markdown/kramdown_parser.rb +122 -0
  25. data/lib/ngage/jekyll/converters/smartypants.rb +70 -0
  26. data/lib/ngage/jekyll/convertible.rb +253 -0
  27. data/lib/ngage/jekyll/deprecator.rb +50 -0
  28. data/lib/ngage/jekyll/document.rb +503 -0
  29. data/lib/ngage/jekyll/drops/collection_drop.rb +20 -0
  30. data/lib/ngage/jekyll/drops/document_drop.rb +69 -0
  31. data/lib/ngage/jekyll/drops/drop.rb +209 -0
  32. data/lib/ngage/jekyll/drops/excerpt_drop.rb +15 -0
  33. data/lib/ngage/jekyll/drops/jekyll_drop.rb +32 -0
  34. data/lib/ngage/jekyll/drops/site_drop.rb +56 -0
  35. data/lib/ngage/jekyll/drops/static_file_drop.rb +14 -0
  36. data/lib/ngage/jekyll/drops/unified_payload_drop.rb +26 -0
  37. data/lib/ngage/jekyll/drops/url_drop.rb +89 -0
  38. data/lib/ngage/jekyll/entry_filter.rb +127 -0
  39. data/lib/ngage/jekyll/errors.rb +20 -0
  40. data/lib/ngage/jekyll/excerpt.rb +180 -0
  41. data/lib/ngage/jekyll/external.rb +76 -0
  42. data/lib/ngage/jekyll/filters.rb +390 -0
  43. data/lib/ngage/jekyll/filters/date_filters.rb +110 -0
  44. data/lib/ngage/jekyll/filters/grouping_filters.rb +64 -0
  45. data/lib/ngage/jekyll/filters/url_filters.rb +68 -0
  46. data/lib/ngage/jekyll/frontmatter_defaults.rb +233 -0
  47. data/lib/ngage/jekyll/generator.rb +5 -0
  48. data/lib/ngage/jekyll/hooks.rb +106 -0
  49. data/lib/ngage/jekyll/layout.rb +62 -0
  50. data/lib/ngage/jekyll/liquid_extensions.rb +22 -0
  51. data/lib/ngage/jekyll/liquid_renderer.rb +63 -0
  52. data/lib/ngage/jekyll/liquid_renderer/file.rb +56 -0
  53. data/lib/ngage/jekyll/liquid_renderer/table.rb +98 -0
  54. data/lib/ngage/jekyll/log_adapter.rb +151 -0
  55. data/lib/ngage/jekyll/mime.types +825 -0
  56. data/lib/ngage/jekyll/page.rb +185 -0
  57. data/lib/ngage/jekyll/page_without_a_file.rb +14 -0
  58. data/lib/ngage/jekyll/plugin.rb +92 -0
  59. data/lib/ngage/jekyll/plugin_manager.rb +115 -0
  60. data/lib/ngage/jekyll/publisher.rb +23 -0
  61. data/lib/ngage/jekyll/reader.rb +154 -0
  62. data/lib/ngage/jekyll/readers/collection_reader.rb +22 -0
  63. data/lib/ngage/jekyll/readers/data_reader.rb +75 -0
  64. data/lib/ngage/jekyll/readers/layout_reader.rb +70 -0
  65. data/lib/ngage/jekyll/readers/page_reader.rb +25 -0
  66. data/lib/ngage/jekyll/readers/post_reader.rb +72 -0
  67. data/lib/ngage/jekyll/readers/static_file_reader.rb +25 -0
  68. data/lib/ngage/jekyll/readers/theme_assets_reader.rb +51 -0
  69. data/lib/ngage/jekyll/regenerator.rb +195 -0
  70. data/lib/ngage/jekyll/related_posts.rb +52 -0
  71. data/lib/ngage/jekyll/renderer.rb +266 -0
  72. data/lib/ngage/jekyll/site.rb +476 -0
  73. data/lib/ngage/jekyll/static_file.rb +169 -0
  74. data/lib/ngage/jekyll/stevenson.rb +60 -0
  75. data/lib/ngage/jekyll/tags/highlight.rb +108 -0
  76. data/lib/ngage/jekyll/tags/include.rb +226 -0
  77. data/lib/ngage/jekyll/tags/link.rb +40 -0
  78. data/lib/ngage/jekyll/tags/post_url.rb +104 -0
  79. data/lib/ngage/jekyll/theme.rb +73 -0
  80. data/lib/ngage/jekyll/theme_builder.rb +121 -0
  81. data/lib/ngage/jekyll/url.rb +160 -0
  82. data/lib/ngage/jekyll/utils.rb +370 -0
  83. data/lib/ngage/jekyll/utils/ansi.rb +57 -0
  84. data/lib/ngage/jekyll/utils/exec.rb +26 -0
  85. data/lib/ngage/jekyll/utils/internet.rb +37 -0
  86. data/lib/ngage/jekyll/utils/platforms.rb +82 -0
  87. data/lib/ngage/jekyll/utils/thread_event.rb +31 -0
  88. data/lib/ngage/jekyll/utils/win_tz.rb +75 -0
  89. data/lib/ngage/site_template/.gitignore +5 -0
  90. data/lib/ngage/site_template/404.html +25 -0
  91. data/lib/ngage/site_template/_config.yml +47 -0
  92. data/lib/ngage/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -0
  93. data/lib/ngage/site_template/about.markdown +18 -0
  94. data/lib/ngage/site_template/index.markdown +6 -0
  95. data/lib/ngage/theme_template/CODE_OF_CONDUCT.md.erb +74 -0
  96. data/lib/ngage/theme_template/Gemfile +4 -0
  97. data/lib/ngage/theme_template/LICENSE.txt.erb +21 -0
  98. data/lib/ngage/theme_template/README.md.erb +52 -0
  99. data/lib/ngage/theme_template/_layouts/default.html +1 -0
  100. data/lib/ngage/theme_template/_layouts/page.html +5 -0
  101. data/lib/ngage/theme_template/_layouts/post.html +5 -0
  102. data/lib/ngage/theme_template/example/_config.yml.erb +1 -0
  103. data/lib/ngage/theme_template/example/_post.md +12 -0
  104. data/lib/ngage/theme_template/example/index.html +14 -0
  105. data/lib/ngage/theme_template/example/style.scss +7 -0
  106. data/lib/ngage/theme_template/gitignore.erb +6 -0
  107. data/lib/ngage/theme_template/theme.gemspec.erb +19 -0
  108. data/lib/ngage/version.rb +5 -0
  109. metadata +328 -0
@@ -0,0 +1,370 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Utils
5
+ extend self
6
+ autoload :Ansi, "jekyll/utils/ansi"
7
+ autoload :Exec, "jekyll/utils/exec"
8
+ autoload :Internet, "jekyll/utils/internet"
9
+ autoload :Platforms, "jekyll/utils/platforms"
10
+ autoload :ThreadEvent, "jekyll/utils/thread_event"
11
+ autoload :WinTZ, "jekyll/utils/win_tz"
12
+
13
+ # Constants for use in #slugify
14
+ SLUGIFY_MODES = %w(raw default pretty ascii latin).freeze
15
+ SLUGIFY_RAW_REGEXP = Regexp.new('\\s+').freeze
16
+ SLUGIFY_DEFAULT_REGEXP = Regexp.new("[^[:alnum:]]+").freeze
17
+ SLUGIFY_PRETTY_REGEXP = Regexp.new("[^[:alnum:]._~!$&'()+,;=@]+").freeze
18
+ SLUGIFY_ASCII_REGEXP = Regexp.new("[^[A-Za-z0-9]]+").freeze
19
+
20
+ # Takes an indented string and removes the preceding spaces on each line
21
+
22
+ def strip_heredoc(str)
23
+ str.gsub(%r!^[ \t]{#{(str.scan(%r!^[ \t]*(?=\S)!).min || "").size}}!, "")
24
+ end
25
+
26
+ # Takes a slug and turns it into a simple title.
27
+
28
+ def titleize_slug(slug)
29
+ slug.split("-").map!(&:capitalize).join(" ")
30
+ end
31
+
32
+ # Non-destructive version of deep_merge_hashes! See that method.
33
+ #
34
+ # Returns the merged hashes.
35
+ def deep_merge_hashes(master_hash, other_hash)
36
+ deep_merge_hashes!(master_hash.dup, other_hash)
37
+ end
38
+
39
+ # Merges a master hash with another hash, recursively.
40
+ #
41
+ # master_hash - the "parent" hash whose values will be overridden
42
+ # other_hash - the other hash whose values will be persisted after the merge
43
+ #
44
+ # This code was lovingly stolen from some random gem:
45
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
46
+ #
47
+ # Thanks to whoever made it.
48
+ def deep_merge_hashes!(target, overwrite)
49
+ merge_values(target, overwrite)
50
+ merge_default_proc(target, overwrite)
51
+ duplicate_frozen_values(target)
52
+
53
+ target
54
+ end
55
+
56
+ def mergable?(value)
57
+ value.is_a?(Hash) || value.is_a?(Drops::Drop)
58
+ end
59
+
60
+ def duplicable?(obj)
61
+ case obj
62
+ when nil, false, true, Symbol, Numeric
63
+ false
64
+ else
65
+ true
66
+ end
67
+ end
68
+
69
+ # Read array from the supplied hash favouring the singular key
70
+ # and then the plural key, and handling any nil entries.
71
+ #
72
+ # hash - the hash to read from
73
+ # singular_key - the singular key
74
+ # plural_key - the plural key
75
+ #
76
+ # Returns an array
77
+ def pluralized_array_from_hash(hash, singular_key, plural_key)
78
+ [].tap do |array|
79
+ value = value_from_singular_key(hash, singular_key)
80
+ value ||= value_from_plural_key(hash, plural_key)
81
+ array << value
82
+ end.flatten.compact
83
+ end
84
+
85
+ def value_from_singular_key(hash, key)
86
+ hash[key] if hash.key?(key) || (hash.default_proc && hash[key])
87
+ end
88
+
89
+ def value_from_plural_key(hash, key)
90
+ if hash.key?(key) || (hash.default_proc && hash[key])
91
+ val = hash[key]
92
+ case val
93
+ when String
94
+ val.split
95
+ when Array
96
+ val.compact
97
+ end
98
+ end
99
+ end
100
+
101
+ def transform_keys(hash)
102
+ result = {}
103
+ hash.each_key do |key|
104
+ result[yield(key)] = hash[key]
105
+ end
106
+ result
107
+ end
108
+
109
+ # Apply #to_sym to all keys in the hash
110
+ #
111
+ # hash - the hash to which to apply this transformation
112
+ #
113
+ # Returns a new hash with symbolized keys
114
+ def symbolize_hash_keys(hash)
115
+ transform_keys(hash) { |key| key.to_sym rescue key }
116
+ end
117
+
118
+ # Apply #to_s to all keys in the Hash
119
+ #
120
+ # hash - the hash to which to apply this transformation
121
+ #
122
+ # Returns a new hash with stringified keys
123
+ def stringify_hash_keys(hash)
124
+ transform_keys(hash) { |key| key.to_s rescue key }
125
+ end
126
+
127
+ # Parse a date/time and throw an error if invalid
128
+ #
129
+ # input - the date/time to parse
130
+ # msg - (optional) the error message to show the user
131
+ #
132
+ # Returns the parsed date if successful, throws a FatalException
133
+ # if not
134
+ def parse_date(input, msg = "Input could not be parsed.")
135
+ Time.parse(input).localtime
136
+ rescue ArgumentError
137
+ raise Errors::InvalidDateError, "Invalid date '#{input}': #{msg}"
138
+ end
139
+
140
+ # Determines whether a given file has
141
+ #
142
+ # Returns true if the YAML front matter is present.
143
+ # rubocop: disable PredicateName
144
+ def has_yaml_header?(file)
145
+ !!(File.open(file, "rb", &:readline) =~ %r!\A---\s*\r?\n!)
146
+ rescue EOFError
147
+ false
148
+ end
149
+
150
+ # Determine whether the given content string contains Liquid Tags or Vaiables
151
+ #
152
+ # Returns true is the string contains sequences of `{%` or `{{`
153
+ def has_liquid_construct?(content)
154
+ return false if content.nil? || content.empty?
155
+
156
+ content.include?("{%") || content.include?("{{")
157
+ end
158
+ # rubocop: enable PredicateName
159
+
160
+ # Slugify a filename or title.
161
+ #
162
+ # string - the filename or title to slugify
163
+ # mode - how string is slugified
164
+ # cased - whether to replace all uppercase letters with their
165
+ # lowercase counterparts
166
+ #
167
+ # When mode is "none", return the given string.
168
+ #
169
+ # When mode is "raw", return the given string,
170
+ # with every sequence of spaces characters replaced with a hyphen.
171
+ #
172
+ # When mode is "default" or nil, non-alphabetic characters are
173
+ # replaced with a hyphen too.
174
+ #
175
+ # When mode is "pretty", some non-alphabetic characters (._~!$&'()+,;=@)
176
+ # are not replaced with hyphen.
177
+ #
178
+ # When mode is "ascii", some everything else except ASCII characters
179
+ # a-z (lowercase), A-Z (uppercase) and 0-9 (numbers) are not replaced with hyphen.
180
+ #
181
+ # When mode is "latin", the input string is first preprocessed so that
182
+ # any letters with accents are replaced with the plain letter. Afterwards,
183
+ # it follows the "default" mode of operation.
184
+ #
185
+ # If cased is true, all uppercase letters in the result string are
186
+ # replaced with their lowercase counterparts.
187
+ #
188
+ # Examples:
189
+ # slugify("The _config.yml file")
190
+ # # => "the-config-yml-file"
191
+ #
192
+ # slugify("The _config.yml file", "pretty")
193
+ # # => "the-_config.yml-file"
194
+ #
195
+ # slugify("The _config.yml file", "pretty", true)
196
+ # # => "The-_config.yml file"
197
+ #
198
+ # slugify("The _config.yml file", "ascii")
199
+ # # => "the-config-yml-file"
200
+ #
201
+ # slugify("The _config.yml file", "latin")
202
+ # # => "the-config-yml-file"
203
+ #
204
+ # Returns the slugified string.
205
+ def slugify(string, mode: nil, cased: false)
206
+ mode ||= "default"
207
+ return nil if string.nil?
208
+
209
+ unless SLUGIFY_MODES.include?(mode)
210
+ return cased ? string : string.downcase
211
+ end
212
+
213
+ # Drop accent marks from latin characters. Everything else turns to ?
214
+ if mode == "latin"
215
+ I18n.config.available_locales = :en if I18n.config.available_locales.empty?
216
+ string = I18n.transliterate(string)
217
+ end
218
+
219
+ slug = replace_character_sequence_with_hyphen(string, :mode => mode)
220
+
221
+ # Remove leading/trailing hyphen
222
+ slug.gsub!(%r!^\-|\-$!i, "")
223
+
224
+ slug.downcase! unless cased
225
+ slug
226
+ end
227
+
228
+ # Add an appropriate suffix to template so that it matches the specified
229
+ # permalink style.
230
+ #
231
+ # template - permalink template without trailing slash or file extension
232
+ # permalink_style - permalink style, either built-in or custom
233
+ #
234
+ # The returned permalink template will use the same ending style as
235
+ # specified in permalink_style. For example, if permalink_style contains a
236
+ # trailing slash (or is :pretty, which indirectly has a trailing slash),
237
+ # then so will the returned template. If permalink_style has a trailing
238
+ # ":output_ext" (or is :none, :date, or :ordinal) then so will the returned
239
+ # template. Otherwise, template will be returned without modification.
240
+ #
241
+ # Examples:
242
+ # add_permalink_suffix("/:basename", :pretty)
243
+ # # => "/:basename/"
244
+ #
245
+ # add_permalink_suffix("/:basename", :date)
246
+ # # => "/:basename:output_ext"
247
+ #
248
+ # add_permalink_suffix("/:basename", "/:year/:month/:title/")
249
+ # # => "/:basename/"
250
+ #
251
+ # add_permalink_suffix("/:basename", "/:year/:month/:title")
252
+ # # => "/:basename"
253
+ #
254
+ # Returns the updated permalink template
255
+ def add_permalink_suffix(template, permalink_style)
256
+ template = template.dup
257
+
258
+ case permalink_style
259
+ when :pretty
260
+ template << "/"
261
+ when :date, :ordinal, :none
262
+ template << ":output_ext"
263
+ else
264
+ template << "/" if permalink_style.to_s.end_with?("/")
265
+ template << ":output_ext" if permalink_style.to_s.end_with?(":output_ext")
266
+ end
267
+
268
+ template
269
+ end
270
+
271
+ # Work the same way as Dir.glob but seperating the input into two parts
272
+ # ('dir' + '/' + 'pattern') to make sure the first part('dir') does not act
273
+ # as a pattern.
274
+ #
275
+ # For example, Dir.glob("path[/*") always returns an empty array,
276
+ # because the method fails to find the closing pattern to '[' which is ']'
277
+ #
278
+ # Examples:
279
+ # safe_glob("path[", "*")
280
+ # # => ["path[/file1", "path[/file2"]
281
+ #
282
+ # safe_glob("path", "*", File::FNM_DOTMATCH)
283
+ # # => ["path/.", "path/..", "path/file1"]
284
+ #
285
+ # safe_glob("path", ["**", "*"])
286
+ # # => ["path[/file1", "path[/folder/file2"]
287
+ #
288
+ # dir - the dir where glob will be executed under
289
+ # (the dir will be included to each result)
290
+ # patterns - the patterns (or the pattern) which will be applied under the dir
291
+ # flags - the flags which will be applied to the pattern
292
+ #
293
+ # Returns matched pathes
294
+ def safe_glob(dir, patterns, flags = 0)
295
+ return [] unless Dir.exist?(dir)
296
+
297
+ pattern = File.join(Array(patterns))
298
+ return [dir] if pattern.empty?
299
+
300
+ Dir.chdir(dir) do
301
+ Dir.glob(pattern, flags).map { |f| File.join(dir, f) }
302
+ end
303
+ end
304
+
305
+ # Returns merged option hash for File.read of self.site (if exists)
306
+ # and a given param
307
+ def merged_file_read_opts(site, opts)
308
+ merged = (site ? site.file_read_opts : {}).merge(opts)
309
+ if merged[:encoding] && !merged[:encoding].start_with?("bom|")
310
+ merged[:encoding] = "bom|#{merged[:encoding]}"
311
+ end
312
+ if merged["encoding"] && !merged["encoding"].start_with?("bom|")
313
+ merged["encoding"] = "bom|#{merged["encoding"]}"
314
+ end
315
+ merged
316
+ end
317
+
318
+ private
319
+
320
+ def merge_values(target, overwrite)
321
+ target.merge!(overwrite) do |_key, old_val, new_val|
322
+ if new_val.nil?
323
+ old_val
324
+ elsif mergable?(old_val) && mergable?(new_val)
325
+ deep_merge_hashes(old_val, new_val)
326
+ else
327
+ new_val
328
+ end
329
+ end
330
+ end
331
+
332
+ def merge_default_proc(target, overwrite)
333
+ if target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil?
334
+ target.default_proc = overwrite.default_proc
335
+ end
336
+ end
337
+
338
+ def duplicate_frozen_values(target)
339
+ target.each do |key, val|
340
+ target[key] = val.dup if val.frozen? && duplicable?(val)
341
+ end
342
+ end
343
+
344
+ # Replace each character sequence with a hyphen.
345
+ #
346
+ # See Utils#slugify for a description of the character sequence specified
347
+ # by each mode.
348
+ def replace_character_sequence_with_hyphen(string, mode: "default")
349
+ replaceable_char =
350
+ case mode
351
+ when "raw"
352
+ SLUGIFY_RAW_REGEXP
353
+ when "pretty"
354
+ # "._~!$&'()+,;=@" is human readable (not URI-escaped) in URL
355
+ # and is allowed in both extN and NTFS.
356
+ SLUGIFY_PRETTY_REGEXP
357
+ when "ascii"
358
+ # For web servers not being able to handle Unicode, the safe
359
+ # method is to ditch anything else but latin letters and numeric
360
+ # digits.
361
+ SLUGIFY_ASCII_REGEXP
362
+ else
363
+ SLUGIFY_DEFAULT_REGEXP
364
+ end
365
+
366
+ # Strip according to the mode
367
+ string.gsub(replaceable_char, "-")
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,57 @@
1
+ # Frozen-string-literal: true
2
+
3
+ module Jekyll
4
+ module Utils
5
+ module Ansi
6
+ extend self
7
+
8
+ ESCAPE = format("%c", 27)
9
+ MATCH = %r!#{ESCAPE}\[(?:\d+)(?:;\d+)*(j|k|m|s|u|A|B|G)|\e\(B\e\[m!ix
10
+ COLORS = {
11
+ :red => 31,
12
+ :green => 32,
13
+ :black => 30,
14
+ :magenta => 35,
15
+ :yellow => 33,
16
+ :white => 37,
17
+ :blue => 34,
18
+ :cyan => 36,
19
+ }.freeze
20
+
21
+ # Strip ANSI from the current string. It also strips cursor stuff,
22
+ # well some of it, and it also strips some other stuff that a lot of
23
+ # the other ANSI strippers don't.
24
+
25
+ def strip(str)
26
+ str.gsub MATCH, ""
27
+ end
28
+
29
+ #
30
+
31
+ def has?(str)
32
+ !!(str =~ MATCH)
33
+ end
34
+
35
+ # Reset the color back to the default color so that you do not leak any
36
+ # colors when you move onto the next line. This is probably normally
37
+ # used as part of a wrapper so that we don't leak colors.
38
+
39
+ def reset(str = "")
40
+ @ansi_reset ||= format("%c[0m", 27)
41
+ "#{@ansi_reset}#{str}"
42
+ end
43
+
44
+ # SEE: `self::COLORS` for a list of methods. They are mostly
45
+ # standard base colors supported by pretty much any xterm-color, we do
46
+ # not need more than the base colors so we do not include them.
47
+ # Actually... if I'm honest we don't even need most of the
48
+ # base colors.
49
+
50
+ COLORS.each do |color, num|
51
+ define_method color do |str|
52
+ "#{format("%c", 27)}[#{num}m#{str}#{reset}"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Jekyll
6
+ module Utils
7
+ module Exec
8
+ extend self
9
+
10
+ # Runs a program in a sub-shell.
11
+ #
12
+ # *args - a list of strings containing the program name and arguments
13
+ #
14
+ # Returns a Process::Status and a String of output in an array in
15
+ # that order.
16
+ def run(*args)
17
+ stdin, stdout, stderr, process = Open3.popen3(*args)
18
+ out = stdout.read.strip
19
+ err = stderr.read.strip
20
+
21
+ [stdin, stdout, stderr].each(&:close)
22
+ [process.value, out + err]
23
+ end
24
+ end
25
+ end
26
+ end