epitools 0.5.122 → 0.5.128

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fee320e00e56ea94488a469e34add3705b207b5da4e01b255cd0b643122b1b9
4
- data.tar.gz: 61181faf99b9ac4c5e98339a7a99b2233914877f25c376999b09ab05657ad444
3
+ metadata.gz: 55db8684ff41f2465660f1b532823595eda3c5ffadcd15e43538c29a5ea5ef20
4
+ data.tar.gz: e866e2ebc77c18506142528d9de6da16db99a03f31f1caed72960329439d2a8e
5
5
  SHA512:
6
- metadata.gz: a475668151f44db4b2025d6de6138eeed1686c9f44d0addb836b5ea406b672ed325121822081990eefa030941e70d496aca6aa5c51138d70494c4bab262f7fc3
7
- data.tar.gz: 5ee5027d1ff20292ae0956623f9a630a398416c80ec3a62321ac55e7b34b9e2ca0ba3bfda904f90684c30c20ddf93fda0bade1966a0698b605c8fa7d2d425cf1
6
+ metadata.gz: 4d3c32dd1040271ae4eaf4e6d4a190d0d3518ceff7eb91a668ab243a306410460bc6336262e12f2d8db2b6e5f5ad87ae91c13e3eff23a2f31b4fb999b671cf29
7
+ data.tar.gz: '086ea2d99290c419af5cecd34bcfd367517429bee37f2ff0a404178b8ae50f46ba2fd68b147f456bd0ea946b89665f592c463c47b4423f6d953d317b3cc5ea5f'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.122
1
+ 0.5.128
@@ -1,7 +1,6 @@
1
1
  ## Standard library
2
2
 
3
3
  autoload :Set, 'set'
4
- autoload :URI, 'uri'
5
4
  autoload :Zlib, 'zlib'
6
5
  autoload :FileUtils, 'fileutils'
7
6
  autoload :Tempfile, 'tempfile'
@@ -23,6 +22,10 @@ autoload :CSV, 'csv'
23
22
  autoload :SDBM, 'sdbm'
24
23
  autoload :StringScanner, 'strscan'
25
24
 
25
+ autoreq :URI do
26
+ require 'epitools/core_ext/uri'
27
+ end
28
+
26
29
  module Digest
27
30
  autoload :SHA1, 'digest/sha1'
28
31
  autoload :SHA2, 'digest/sha2'
@@ -80,8 +83,13 @@ autoload :SemanticVersion, 'epitools/semantic_version'
80
83
  autoload :Matrix, 'epitools/core_ext/matrix'
81
84
  autoreq(:Vector) { Matrix }
82
85
 
83
- module Epi; autoload :Slop, 'epitools/slop'; end
84
- autoreq(:Slop) { Slop = Epi::Slop }
86
+ ## Bundled slop
87
+ module Epi
88
+ autoload :Slop, 'epitools/slop'
89
+ end
90
+ autoreq(:Slop) do
91
+ Slop = Epi::Slop
92
+ end
85
93
 
86
94
  ## Gems (common)
87
95
 
@@ -230,3 +230,20 @@ def notify_send(title, body=nil, icon: nil, time: 5)
230
230
  fork { system *cmd }
231
231
  end
232
232
 
233
+ def curl(url)
234
+ curl_open(url).read
235
+ end
236
+
237
+ def curl_open(url, headers={})
238
+ # headers["User-Agent"] ||= "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/85 Version/11.1.1 Safari/605.1.15"
239
+ cmd = ["curl", "-LSs"]
240
+ headers.each { |k,v| cmd += ["-H", "#{k}: #{v}"] }
241
+ cmd << url
242
+ IO.popen(cmd)
243
+ rescue Errno::ENOENT
244
+ raise "Error: 'curl' isn't installed."
245
+ end
246
+
247
+ def curl_json(url)
248
+ JSON.parse(curl(url))
249
+ end
@@ -5,16 +5,23 @@ require 'epitools'
5
5
  require 'epitools/core_ext/object'
6
6
  require 'epitools/core_ext/class'
7
7
  require 'epitools/core_ext/module'
8
+
8
9
  require 'epitools/core_ext/string'
10
+
9
11
  require 'epitools/core_ext/array'
10
- require 'epitools/core_ext/file'
11
- require 'epitools/core_ext/io'
12
12
  require 'epitools/core_ext/enumerable'
13
13
  require 'epitools/core_ext/hash'
14
+
14
15
  require 'epitools/core_ext/numbers'
15
16
  require 'epitools/core_ext/truthiness'
16
17
  require 'epitools/core_ext/range'
17
18
  require 'epitools/core_ext/time'
19
+
20
+ require 'epitools/core_ext/argv'
21
+ require 'epitools/core_ext/file'
22
+ require 'epitools/core_ext/io'
23
+ require 'epitools/core_ext/uri'
24
+
18
25
  require 'epitools/core_ext/misc'
19
26
 
20
27
  ##############################################################################
@@ -0,0 +1,59 @@
1
+ class << ARGV
2
+
3
+ def parse!
4
+ unless @opts or @args
5
+ @opts, @args = partition { |arg| arg[/^--?\w{1,2}/].nil? }
6
+ @opts = @opts.map { |opt| key, val = opt.strip(/^-+/).split("=") }.to_ostruct
7
+ if @opts.help
8
+ @help_handler.call
9
+ exit
10
+ end
11
+ end
12
+
13
+ [@opts, @args]
14
+ end
15
+
16
+ def parse
17
+ parse!
18
+ [@opts, @args]
19
+ end
20
+
21
+ def help?
22
+ !!@opts["help"]
23
+ end
24
+
25
+ def opts
26
+ parse! unless @opts
27
+ @opts
28
+ end
29
+
30
+ def args
31
+ @args ? @args : opts && @args
32
+ end
33
+
34
+ def paths
35
+ map(&:to_Path)
36
+ end
37
+
38
+ def paths_R
39
+ recursive_proc = proc do |paths|
40
+ paths.map { |path| path.dir? ? the_expander.(path.ls_R) : path }
41
+ end
42
+
43
+ recursive_proc.(paths)
44
+ end
45
+ alias_method :recursive_paths, :paths_R
46
+
47
+ def on_help(&block)
48
+ @help_handler = block
49
+ end
50
+
51
+ def regexes(escaped: true, case_sensitive: false)
52
+ if case_sensitive
53
+ map { |arg| /#{escaped ? Regexp.escape(arg) : arg}/ } # NO 'i'
54
+ else
55
+ map { |arg| /#{escaped ? Regexp.escape(arg) : arg}/i }
56
+ end
57
+ end
58
+
59
+ end
@@ -324,6 +324,33 @@ class Array
324
324
  super.to_a
325
325
  end
326
326
 
327
- end
327
+ module ToCSV
328
+ #
329
+ # Convert this enumerable into a CSV string (nb: enumerable must contain either all Hashes or all Arrays)
330
+ #
331
+ def to_csv(delimiter=",")
332
+ types = count_by(&:class)
333
+
334
+ unless types.size == 1 and (types[Array] > 0 or types[Hash] > 0)
335
+ raise "Error: this array must contain nothing but arrays, or nothing but hashes (actually contains: #{types.inspect})"
336
+ end
337
+
338
+ options = {}
339
+ options[:col_sep] = delimiter
340
+ options[:headers] = flat_map(&:keys).uniq if types[Hash] > 0
328
341
 
342
+ CSV.generate(nil, **options) do |csv|
343
+ each { |obj| csv << obj }
344
+ end
345
+ end
329
346
 
347
+ #
348
+ # Like #to_csv, but with tab-separated CSV fields
349
+ #
350
+ def to_tsv
351
+ to_csv("\t")
352
+ end
353
+ end
354
+
355
+ prepend ToCSV # dirty hack to stop the CSV module from clobbering the to_csv method
356
+ end
@@ -1,4 +1,7 @@
1
+ require 'epitools/core_ext/array'
2
+
1
3
  module Enumerable
4
+ include Array::ToCSV
2
5
 
3
6
  #
4
7
  # 'true' if the Enumerable has no elements
@@ -128,79 +128,6 @@ end
128
128
 
129
129
 
130
130
 
131
- module URI
132
-
133
- #
134
- # Return a Hash of the variables in the query string
135
- #
136
- def params
137
- query ? query.to_params : {}
138
- end
139
-
140
- #
141
- # URIs are strings, dammit!
142
- #
143
- def to_str
144
- to_s
145
- end
146
-
147
- #
148
- # Default user agent for the 'get' method
149
- #
150
- # USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0"
151
-
152
- #
153
- # Get this URI using Net::HTTP
154
- #
155
- def get(headers={}, redirect_limit=10)
156
- raise "Sorry, URI can't get from #{scheme.inspect} URIs yet" unless scheme =~ /^https?$/
157
- raise 'Too many HTTP redirections' if redirect_limit == 0
158
-
159
- # headers['User-Agent'] ||= USER_AGENT
160
-
161
- # response = Net::HTTP.start(host, port) do |http|
162
- # # the_path = path.empty? ? "/" : path
163
- # req = Net::HTTP::Get.new(self, headers)
164
- # http.request(req)
165
- # end
166
-
167
- response = Net::HTTP.get_response(self)
168
-
169
- case response
170
- when Net::HTTPSuccess
171
- response
172
- when Net::HTTPRedirection
173
- # puts "redirect: #{response['location']}"
174
- URI(response['location']).get(headers, redirect_limit-1)
175
- else
176
- response.error!
177
- end
178
- end
179
-
180
- end
181
-
182
- #
183
- # Stupid workaround for URI blowing up when it receives a [ or ] character
184
- #
185
- module Better_URI_RFC3986_Parser # ::RFC3986_relative_ref
186
- ESCAPE_ME_PLZ = "[]{}!"
187
-
188
- def split(uri)
189
- subsitutions = ESCAPE_ME_PLZ.chars.map { |c| [c, CGI.escape(c)] }
190
- subsitutions << [" ", "%20"]
191
-
192
- subsitutions.each do |find, replace|
193
- uri = uri.gsub(find, replace)
194
- end
195
-
196
- super(uri)
197
- end
198
-
199
- end
200
-
201
- URI::RFC3986_Parser.prepend(Better_URI_RFC3986_Parser)
202
-
203
-
204
131
 
205
132
  #
206
133
  # Give ObjectSpace Enumerable powers (select, map, etc.)
@@ -21,6 +21,13 @@ class String
21
21
  gsub("\r\n", "\n")
22
22
  end
23
23
 
24
+ #
25
+ # Escape shell characters (globs, quotes, parens, etc.)
26
+ #
27
+ def shellescape
28
+ Shellwords.escape(self)
29
+ end
30
+
24
31
  #
25
32
  # Remove redundant whitespaces (not including newlines).
26
33
  #
@@ -0,0 +1,97 @@
1
+ require 'uri'
2
+
3
+ module URI
4
+
5
+ #
6
+ # Return a Hash of the variables in the query string
7
+ #
8
+ def params
9
+ (@query ? @query.to_params : {})
10
+ end
11
+
12
+ #
13
+ # Update all the params at once
14
+ #
15
+ def params=(new_params)
16
+ self.query = new_params.to_params
17
+ end
18
+
19
+ #
20
+ # Update one URI parameter
21
+ #
22
+ def set_param(key, value)
23
+ current = params
24
+ current[key] = value
25
+ self.query = current.to_query
26
+ end
27
+
28
+ #
29
+ # Get the query string
30
+ #
31
+ def query
32
+ params.to_query
33
+ end
34
+
35
+ #
36
+ # URIs *are* strings, dammit!
37
+ #
38
+ def to_str
39
+ to_s
40
+ end
41
+
42
+ #
43
+ # Default user agent for the 'get' method
44
+ #
45
+ # USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0"
46
+
47
+ #
48
+ # Get this URI using Net::HTTP
49
+ #
50
+ def get(headers={}, redirect_limit=10)
51
+ raise "Sorry, URI can't get from #{scheme.inspect} URIs yet" unless scheme =~ /^https?$/
52
+ raise 'Too many HTTP redirections' if redirect_limit == 0
53
+
54
+ # headers['User-Agent'] ||= USER_AGENT
55
+
56
+ # response = Net::HTTP.start(host, port) do |http|
57
+ # # the_path = path.empty? ? "/" : path
58
+ # req = Net::HTTP::Get.new(self, headers)
59
+ # http.request(req)
60
+ # end
61
+
62
+ response = Net::HTTP.get_response(self)
63
+
64
+ case response
65
+ when Net::HTTPSuccess
66
+ response
67
+ when Net::HTTPRedirection
68
+ # puts "redirect: #{response['location']}"
69
+ URI(response['location']).get(headers, redirect_limit-1)
70
+ else
71
+ response.error!
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ #
78
+ # Stupid workaround for URI blowing up when it receives a [ or ] character
79
+ #
80
+ module Better_URI_RFC3986_Parser # ::RFC3986_relative_ref
81
+ ESCAPE_ME_PLZ = "[]{}!"
82
+
83
+ def split(uri)
84
+ subsitutions = ESCAPE_ME_PLZ.chars.map { |c| [c, CGI.escape(c)] }
85
+ subsitutions << [" ", "%20"]
86
+
87
+ subsitutions.each do |find, replace|
88
+ uri = uri.gsub(find, replace)
89
+ end
90
+
91
+ super(uri)
92
+ end
93
+
94
+ end
95
+
96
+ URI::RFC3986_Parser.prepend(Better_URI_RFC3986_Parser)
97
+
@@ -1,8 +1,10 @@
1
+ ####################################################################################
1
2
  #
2
3
  # If you require 'epitools/minimal' instead of 'epitools',
3
4
  # this is what you get: core_ext monkeypatches and all the
4
5
  # autoloads; the barest essentials for survival.
5
6
  #
7
+ ####################################################################################
6
8
 
7
9
  if RUBY_VERSION[/^1.8/]
8
10
  require 'enumerator'
@@ -14,6 +16,7 @@ RbConfig = Config unless defined? RbConfig
14
16
  Infinity = Float::INFINITY
15
17
  Inf = Float::INFINITY
16
18
 
19
+ ####################################################################################
17
20
  class Object
18
21
 
19
22
  unless defined?(__DIR__)
@@ -164,14 +167,13 @@ class Object
164
167
  #
165
168
  # eg: stuff.group_by(&:self)
166
169
  #
167
- define_method :self do
170
+ def self
168
171
  if block_given?
169
172
  yield self
170
173
  else
171
174
  self
172
175
  end
173
176
  end
174
- alias_method :fap, :self
175
177
 
176
178
  def ancestors
177
179
  self.class.ancestors
@@ -179,6 +181,7 @@ class Object
179
181
  end
180
182
 
181
183
 
184
+ ####################################################################################
182
185
  #
183
186
  # Patch 'Module#const_missing' to support 'autoreq' (which can autoload gems)
184
187
  #
@@ -213,6 +216,8 @@ class Module
213
216
  end
214
217
 
215
218
 
219
+
220
+ ####################################################################################
216
221
  module Kernel
217
222
 
218
223
  #
@@ -254,10 +259,8 @@ module Kernel
254
259
  end
255
260
 
256
261
  end
257
- ####################################################################################
258
-
259
-
260
262
 
263
+ ####################################################################################
261
264
  class String
262
265
 
263
266
  #
@@ -270,6 +273,7 @@ class String
270
273
 
271
274
  end
272
275
 
276
+ ####################################################################################
273
277
  #
274
278
  # Path("/some/path") is an alias for Path["/some/path"]
275
279
  #
@@ -277,43 +281,7 @@ def Path(arg)
277
281
  Path[arg]
278
282
  end
279
283
 
280
- ####################################################################
281
-
282
- # class << ARGV
283
-
284
- # def opts
285
- # @opts, @args ||= partition { |arg| arg[/^--?\w{1,2}/].nil? }
286
- # end
287
-
288
- # def args
289
- # @args ? @args : opts && @args
290
- # end
291
-
292
- # def paths
293
- # map(&:to_Path)
294
- # end
295
-
296
- # def paths_R
297
- # recursive_proc = proc do |paths|
298
- # paths.map { |path| path.dir? ? the_expander.(path.ls_R) : path }
299
- # end
300
-
301
- # recursive_proc.(paths)
302
- # end
303
- # alias_method :recursive_paths, :paths_R
304
-
305
- # def regexes(escaped: true, case_sensitive: false)
306
- # if case_sensitive
307
- # map { |arg| /#{escaped ? Regexp.escape(arg) : arg}/ } # NO 'i'
308
- # else
309
- # map { |arg| /#{escaped ? Regexp.escape(arg) : arg}/i }
310
- # end
311
- # end
312
-
313
- # end
314
284
 
315
285
  ####################################################################
316
-
317
286
  require 'epitools/autoloads'
318
-
319
287
  ####################################################################
@@ -117,7 +117,8 @@ class Numeric
117
117
  factor = 10**pow
118
118
 
119
119
  if is_a?(Float)
120
- (BigDecimal.new(to_s) * factor).to_i
120
+ BigDecimal
121
+ (BigDecimal(to_s) * factor).to_i
121
122
  else
122
123
  self * factor
123
124
  end
@@ -1110,6 +1110,25 @@ class Path
1110
1110
  }
1111
1111
  end
1112
1112
 
1113
+ #
1114
+ # Path.mkcd(path) creates a path if it doesn't exist, and changes to it (temporarily, if a block is provided)
1115
+ #
1116
+ def self.mkcd(path, &block)
1117
+ path = path.to_Path unless path.is_a? Path
1118
+ path.mkdir_p unless path.exists?
1119
+
1120
+ raise "Error: #{path} couldn't be created." unless path.dir?
1121
+
1122
+ self.cd(path, &block)
1123
+ end
1124
+
1125
+ #
1126
+ # Path.mkcd(self)
1127
+ #
1128
+ def mkcd(&block)
1129
+ Path.mkcd(self, &block)
1130
+ end
1131
+
1113
1132
  def cp_r(dest)
1114
1133
  FileUtils.cp_r(path, dest) #if Path[dest].exists?
1115
1134
  dest
@@ -61,7 +61,7 @@ module WM
61
61
 
62
62
  def self.all
63
63
  # FIXME: Windows owned by linux-namespaced processes (firejail) report their namspaced pid to X11. `window.process` ends up pointing at either nil, or the wrong process.
64
- `wmctrl -lpG`.lines.map(&:strip).map { |line| Window.from_line(line) }
64
+ `wmctrl -lpG`.lines.map(&:strip).map { |line| Window.from_line(line) unless line.blank? }.compact
65
65
  end
66
66
 
67
67
  def self.from_line(line)
@@ -106,23 +106,6 @@ module WM
106
106
  system "wmctrl", "-i", "-a", window_id
107
107
  end
108
108
 
109
- def maximized?
110
- end
111
-
112
- def maximize!
113
- system "wmctrl", "-i", "-a", window_id
114
- end
115
-
116
-
117
- def unmazimize!
118
- end
119
-
120
- def minimized?
121
- end
122
-
123
- def minimize!
124
- end
125
-
126
109
  #
127
110
  # string is made up of regular text, plus <>'d keypresses
128
111
  # eg: "Hello<Ctrl-T><Ctrl-L><Ctrl-Shift-K><Return>!!!"
@@ -795,6 +795,38 @@ describe Enumerable do
795
795
  ps.sort_numerically.map(&:filename).should == proper
796
796
  end
797
797
 
798
+ it "to_csvs and to_tsvs" do
799
+ data = [
800
+ ["1", "2", "3"],
801
+ ["4", "5", "6"],
802
+ ["7", "8", "9", "10"],
803
+ ]
804
+
805
+ hash_data = [
806
+ {a: 1, b: 2, c: 3},
807
+ {a: 4, b: 5, c: 6},
808
+ {a: 7, b: 8, c: 9, d: 10},
809
+ ]
810
+
811
+ class EnumedData
812
+ include Enumerable
813
+ def initialize(data); @data=data; end
814
+ def each(&block); @data.each(&block); end
815
+ end
816
+
817
+ [data, hash_data, EnumedData.new(data)].each do |a|
818
+ str = a.to_csv
819
+ str.each_line.count.should == 3
820
+ str.should_not be_nil
821
+ str["1,2,3"].should_not be_nil
822
+
823
+ str = a.to_tsv
824
+ str.should_not be_nil
825
+ str["1\t2\t3"].should_not be_nil
826
+ end
827
+
828
+ end
829
+
798
830
  end
799
831
 
800
832
 
@@ -1242,6 +1274,16 @@ describe File do
1242
1274
 
1243
1275
  end
1244
1276
 
1277
+ describe ARGV do
1278
+
1279
+ # it "parses args" do
1280
+ # ARGV.clear
1281
+ # ARGV << "-d"
1282
+ # ARGV.parse!
1283
+ # p ARGV.opts
1284
+ # end
1285
+
1286
+ end
1245
1287
 
1246
1288
  describe "Anything" do
1247
1289
 
@@ -1275,5 +1317,13 @@ describe URI do
1275
1317
  (response.size > 0).should == true
1276
1318
  end
1277
1319
 
1320
+ it "params=" do
1321
+ u = "http://butt.com/?q=1".to_uri
1322
+ u.query.should == "q=1"
1323
+ u.params["q"] = 2
1324
+ u.params["q"].should == 2
1325
+ u.query.should == "q=2"
1326
+ end
1327
+
1278
1328
  end
1279
1329
 
@@ -348,6 +348,25 @@ describe Path do
348
348
  lambda { tmp2.mkdir_p }.should_not raise_error
349
349
  end
350
350
 
351
+ it "mkcds" do
352
+ tmp = Path.tmpdir
353
+ tmp.dir?.should == true
354
+ tmp.rm
355
+ tmp.exists?.should == false
356
+
357
+ Path.mkcd(tmp) do
358
+ Path.pwd.path.should == tmp.path
359
+ end
360
+
361
+ tmp.rm
362
+
363
+ tmp.mkcd do
364
+ Path.pwd.path.should == tmp.path
365
+ end
366
+
367
+ tmp.rm
368
+ end
369
+
351
370
  it "has classmethods" do
352
371
  path = Path.tmpfile
353
372
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epitools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.122
4
+ version: 0.5.128
5
5
  platform: ruby
6
6
  authors:
7
7
  - epitron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-30 00:00:00.000000000 Z
11
+ date: 2020-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -51,6 +51,7 @@ files:
51
51
  - lib/epitools/clitools.rb
52
52
  - lib/epitools/colored.rb
53
53
  - lib/epitools/core_ext.rb
54
+ - lib/epitools/core_ext/argv.rb
54
55
  - lib/epitools/core_ext/array.rb
55
56
  - lib/epitools/core_ext/class.rb
56
57
  - lib/epitools/core_ext/enumerable.rb
@@ -66,6 +67,7 @@ files:
66
67
  - lib/epitools/core_ext/string.rb
67
68
  - lib/epitools/core_ext/time.rb
68
69
  - lib/epitools/core_ext/truthiness.rb
70
+ - lib/epitools/core_ext/uri.rb
69
71
  - lib/epitools/daemonize.rb
70
72
  - lib/epitools/hexdump.rb
71
73
  - lib/epitools/iter.rb
@@ -124,7 +126,7 @@ homepage: http://github.com/epitron/epitools
124
126
  licenses:
125
127
  - WTFPL
126
128
  metadata: {}
127
- post_install_message:
129
+ post_install_message:
128
130
  rdoc_options: []
129
131
  require_paths:
130
132
  - lib
@@ -139,8 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
141
  - !ruby/object:Gem::Version
140
142
  version: '0'
141
143
  requirements: []
142
- rubygems_version: 3.0.4
143
- signing_key:
144
+ rubygems_version: 3.1.4
145
+ signing_key:
144
146
  specification_version: 3
145
147
  summary: Not utils... METILS!
146
148
  test_files: []