textrepo 0.4.5 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/lib/textrepo/error.rb +26 -13
- data/lib/textrepo/file_system_repository.rb +215 -16
- data/lib/textrepo/repository.rb +37 -4
- data/lib/textrepo/timestamp.rb +8 -1
- data/lib/textrepo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa38f5fbd3d1fd393eeb4ac200d1c051d8ff082cd92beaf0ab1b3756efd79e92
|
4
|
+
data.tar.gz: 70107459347c9685f722a4aa2367c1f04ec3257c12c489a1436d242e2f322f4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19e12a6e1ac352a005aca887e887089a8daa8ae8fa0573e3d640803f19950747addd52ed77ddb9da9534f7b9596cc125917c9892ea2a14a031aaae33df753e01
|
7
|
+
data.tar.gz: 3c304dbd6330719398ee377183ac26f29ffdfa89b279ff12fac795e9ad664e7b3b249c8f79d36e3bae215e7e114064dd01d3bb23b6652cd2147c8c09382d01ff
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
## [Unreleased]
|
8
8
|
Nothing to record here.
|
9
9
|
|
10
|
+
## [0.5.4] - 2020-11-05
|
11
|
+
### Add
|
12
|
+
- Add a feature for `Repository#update` to keep timestamp unchanged
|
13
|
+
- add the third argument as:
|
14
|
+
- `Repository#update(timestamp, text, keep_stamp = false)`
|
15
|
+
|
16
|
+
## [0.5.3] - 2020-11-03
|
17
|
+
### Changed
|
18
|
+
- Fix issue #38: fix typo in code for FileSystemRepository.
|
19
|
+
|
20
|
+
## [0.5.2] - 2020-11-03
|
21
|
+
### Changed
|
22
|
+
- Fix issue #34:
|
23
|
+
- fix FileSystemRepository#entries to accept "yyyymo" pattern as a
|
24
|
+
Timestamp pattern.
|
25
|
+
- Fix issue #33: fix typo in the doc for FileSystemRepository.new.
|
26
|
+
- Fix issue #31: unfriendly error message of Timestamp.parse_s.
|
27
|
+
|
28
|
+
## [0.5.1] - 2020-11-02
|
29
|
+
### Changed
|
30
|
+
- Fix issue #28.
|
31
|
+
- Modify `Repository#update` to do nothing when the given text is
|
32
|
+
identical to the one in the repository.
|
33
|
+
|
34
|
+
## [0.5.0] - 2020-11-01
|
35
|
+
### Added
|
36
|
+
- Add a new API `Repository#search`.
|
37
|
+
- Add a new API `Repository#exist?`. (0.4.3)
|
38
|
+
|
10
39
|
## [0.4.0] - 2020-10-14
|
11
40
|
### Added
|
12
41
|
- Released to rubygems.org.
|
data/lib/textrepo/error.rb
CHANGED
@@ -2,19 +2,21 @@ module Textrepo
|
|
2
2
|
|
3
3
|
##
|
4
4
|
# Following errors might occur in repository operations:
|
5
|
-
#
|
6
|
-
# | operation (args)
|
7
|
-
#
|
8
|
-
# | create (timestamp, text)
|
9
|
-
# |
|
10
|
-
#
|
11
|
-
# | read (timestamp)
|
12
|
-
#
|
13
|
-
# | update (timestamp, text)
|
14
|
-
# |
|
15
|
-
#
|
16
|
-
# | delete (timestamp)
|
17
|
-
#
|
5
|
+
# +---------------------------------+-----------------------+
|
6
|
+
# | operation (args) | error type |
|
7
|
+
# +---------------------------------+-----------------------+
|
8
|
+
# | create (timestamp, text) | Duplicate timestamp |
|
9
|
+
# | | Empty text |
|
10
|
+
# +---------------------------------+-----------------------+
|
11
|
+
# | read (timestamp) | Missing timestamp |
|
12
|
+
# +---------------------------------+-----------------------+
|
13
|
+
# | update (timestamp, text) | Mssing timestamp |
|
14
|
+
# | | Empty text |
|
15
|
+
# +---------------------------------+-----------------------+
|
16
|
+
# | delete (timestamp) | Missing timestamp |
|
17
|
+
# +---------------------------------+-----------------------+
|
18
|
+
# | search (pattern, stamp_pattern) | Invalid search result |
|
19
|
+
# +---------------------------------+-----------------------+
|
18
20
|
|
19
21
|
class Error < StandardError; end
|
20
22
|
|
@@ -25,6 +27,7 @@ module Textrepo
|
|
25
27
|
EMPTY_TEXT = 'empty text'
|
26
28
|
MISSING_TIMESTAMP = 'missing timestamp: %s'
|
27
29
|
INVALID_TIMESTAMP_STRING = "invalid string as timestamp: %s"
|
30
|
+
INVALID_SEARCH_RESULT = "invalid result by searcher: %s"
|
28
31
|
end
|
29
32
|
# :startdoc:
|
30
33
|
|
@@ -77,4 +80,14 @@ module Textrepo
|
|
77
80
|
end
|
78
81
|
end
|
79
82
|
|
83
|
+
##
|
84
|
+
# An error raise if the search result is not suitable to use.
|
85
|
+
#
|
86
|
+
|
87
|
+
class InvalidSearchResultError < Error
|
88
|
+
def initialize(str)
|
89
|
+
super(ErrMsg::INVALID_SEARCH_RESULT % str)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
80
93
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require "open3"
|
2
3
|
|
3
4
|
module Textrepo
|
4
5
|
|
@@ -19,6 +20,16 @@ module Textrepo
|
|
19
20
|
|
20
21
|
attr_reader :extname
|
21
22
|
|
23
|
+
##
|
24
|
+
# Searcher program name.
|
25
|
+
|
26
|
+
attr_reader :searcher
|
27
|
+
|
28
|
+
##
|
29
|
+
# An array of options to pass to the searcher program.
|
30
|
+
|
31
|
+
attr_reader :searcher_options
|
32
|
+
|
22
33
|
##
|
23
34
|
# Default name for the repository which uses when no name is
|
24
35
|
# specified in the configuration settings.
|
@@ -31,6 +42,11 @@ module Textrepo
|
|
31
42
|
|
32
43
|
FAVORITE_EXTNAME = 'md'
|
33
44
|
|
45
|
+
##
|
46
|
+
# Default searcher program to search text in the repository.
|
47
|
+
|
48
|
+
FAVORITE_SEARCHER = 'grep'
|
49
|
+
|
34
50
|
##
|
35
51
|
# Creates a new repository object. The argument, `conf` must be a
|
36
52
|
# Hash object. It should hold the follwoing values:
|
@@ -41,15 +57,33 @@ module Textrepo
|
|
41
57
|
# - OPTIONAL: (if not specified, default values are used)
|
42
58
|
# - :repository_name => basename of the root path for the repository
|
43
59
|
# - :default_extname => extname for a file stored into in the repository
|
60
|
+
# - :searcher => a program to search like `grep`
|
61
|
+
# - :searcher_options => an Array of option to pass to the searcher
|
44
62
|
#
|
45
63
|
# The root path of the repository looks like the following:
|
46
64
|
# - conf[:repository_base]/conf[:repository_name]
|
47
65
|
#
|
48
|
-
# Default values are set when
|
66
|
+
# Default values are set when `:repository_name` and `:default_extname`
|
49
67
|
# were not defined in `conf`.
|
50
68
|
#
|
69
|
+
# Be careful to set `:searcher_options`, it must be to specify the
|
70
|
+
# searcher behavior equivalent to `grep` with "-inRE". The
|
71
|
+
# default values for the searcher options is defined for BSD grep
|
72
|
+
# (default grep on macOS), GNU grep, and ripgrep (aka rg). They
|
73
|
+
# are:
|
74
|
+
#
|
75
|
+
# "grep" => ["-i", "-n", "-R", "-E"]
|
76
|
+
# "egrep" => ["-i", "-n", "-R"]
|
77
|
+
# "ggrep" => ["-i", "-n", "-R", "-E"]
|
78
|
+
# "gegrep" => ["-i", "-n", "-R"]
|
79
|
+
# "rg" => ["-S", "-n", "--no-heading", "--color", "never"]
|
80
|
+
#
|
81
|
+
# If use those searchers, it is not recommended to set
|
82
|
+
# `:searcher_options`. The default value works well in
|
83
|
+
# `textrepo`.
|
84
|
+
#
|
51
85
|
# :call-seq:
|
52
|
-
# new(
|
86
|
+
# new(Hash or Hash like object) -> FileSystemRepository
|
53
87
|
|
54
88
|
def initialize(conf)
|
55
89
|
super
|
@@ -58,6 +92,8 @@ module Textrepo
|
|
58
92
|
@path = File.expand_path("#{name}", base)
|
59
93
|
FileUtils.mkdir_p(@path)
|
60
94
|
@extname = conf[:default_extname] || FAVORITE_EXTNAME
|
95
|
+
@searcher = find_searcher(conf[:searcher])
|
96
|
+
@searcher_options = conf[:searcher_options]
|
61
97
|
end
|
62
98
|
|
63
99
|
##
|
@@ -94,26 +130,32 @@ module Textrepo
|
|
94
130
|
end
|
95
131
|
|
96
132
|
##
|
97
|
-
# Updates the file content in the repository. A new
|
98
|
-
# will be attached to the text.
|
133
|
+
# Updates the file content in the repository. A new Timestamp
|
134
|
+
# object will be attached to the text. Then, returns the new
|
135
|
+
# Timestamp object.
|
136
|
+
#
|
137
|
+
# When true is passed as the third argument, keeps the Timestamp
|
138
|
+
# unchanged, though updates the content. Then, returns the given
|
139
|
+
# Timestamp object.
|
140
|
+
#
|
141
|
+
# See the documentation of Repository#update to know about errors
|
142
|
+
# and constraints of this method.
|
99
143
|
#
|
100
144
|
# :call-seq:
|
101
|
-
# update(Timestamp, Array) -> Timestamp
|
145
|
+
# update(Timestamp, Array, true or false) -> Timestamp
|
102
146
|
|
103
|
-
def update(timestamp, text)
|
147
|
+
def update(timestamp, text, keep_stamp = false)
|
104
148
|
raise EmptyTextError if text.empty?
|
105
|
-
|
106
|
-
raise MissingTimestampError, timestamp unless FileTest.exist?(org_abs)
|
149
|
+
raise MissingTimestampError, timestamp unless exist?(timestamp)
|
107
150
|
|
108
|
-
#
|
109
|
-
|
110
|
-
new_abs = abspath(new_stamp)
|
111
|
-
write_text(new_abs, text)
|
151
|
+
# does nothing if given text is the same in the repository one
|
152
|
+
return timestamp if read(timestamp) == text
|
112
153
|
|
113
|
-
|
114
|
-
|
154
|
+
stamp = keep_stamp ? timestamp : Timestamp.new(Time.now)
|
155
|
+
write_text(abspath(stamp), text)
|
156
|
+
FileUtils.remove_file(abspath(timestamp)) unless keep_stamp
|
115
157
|
|
116
|
-
|
158
|
+
stamp
|
117
159
|
end
|
118
160
|
|
119
161
|
##
|
@@ -147,7 +189,7 @@ module Textrepo
|
|
147
189
|
if exist?(stamp)
|
148
190
|
results << stamp
|
149
191
|
end
|
150
|
-
when 0, "yyyymoddhhmiss".size, "yyyymodd".size
|
192
|
+
when 0, "yyyymoddhhmiss".size, "yyyymodd".size, "yyyymo".size
|
151
193
|
results += find_entries(stamp_pattern)
|
152
194
|
when 4 # "yyyy" or "modd"
|
153
195
|
pat = nil
|
@@ -179,6 +221,27 @@ module Textrepo
|
|
179
221
|
FileTest.exist?(abspath(timestamp))
|
180
222
|
end
|
181
223
|
|
224
|
+
##
|
225
|
+
# Searches a pattern in all text. The given pattern is a word to
|
226
|
+
# search or a regular expression. The pattern would be passed to
|
227
|
+
# a searcher program as it passed.
|
228
|
+
#
|
229
|
+
# See the document for Textrepo::Repository#search to know about
|
230
|
+
# the search result.
|
231
|
+
#
|
232
|
+
# :call-seq:
|
233
|
+
# search(String for pattern, String for Timestamp pattern) -> Array
|
234
|
+
|
235
|
+
def search(pattern, stamp_pattern = nil)
|
236
|
+
result = nil
|
237
|
+
if stamp_pattern.nil?
|
238
|
+
result = invoke_searcher_at_repo_root(@searcher, pattern)
|
239
|
+
else
|
240
|
+
result = invoke_searcher_for_entries(@searcher, pattern, entries(stamp_pattern))
|
241
|
+
end
|
242
|
+
construct_search_result(result)
|
243
|
+
end
|
244
|
+
|
182
245
|
# :stopdoc:
|
183
246
|
|
184
247
|
private
|
@@ -219,6 +282,142 @@ module Textrepo
|
|
219
282
|
}.compact
|
220
283
|
end
|
221
284
|
|
285
|
+
##
|
286
|
+
# The upper limit of files to search at one time. The value has
|
287
|
+
# no reason to select. It seems to me that not too much, not too
|
288
|
+
# little to handle in one process to search.
|
289
|
+
|
290
|
+
LIMIT_OF_FILES = 20
|
291
|
+
|
292
|
+
##
|
293
|
+
# When no timestamp pattern was given, invoke the searcher with
|
294
|
+
# the repository root path as its argument and the recursive
|
295
|
+
# searching option. The search could be done in only one process.
|
296
|
+
|
297
|
+
def invoke_searcher_at_repo_root(searcher, pattern)
|
298
|
+
o, s = Open3.capture2(searcher, *find_searcher_options(searcher),
|
299
|
+
pattern, @path)
|
300
|
+
output = []
|
301
|
+
output += o.lines.map(&:chomp) if s.success? && (! o.empty?)
|
302
|
+
output
|
303
|
+
end
|
304
|
+
|
305
|
+
##
|
306
|
+
# When a timestamp pattern was given, at first, list target files,
|
307
|
+
# then invoke the searcher for those files. Since the number of
|
308
|
+
# target files may be so much, it seems to be dangerous to pass
|
309
|
+
# all of them to a single search process at one time.
|
310
|
+
#
|
311
|
+
# One more thing to mention, the searcher, like `grep`, does not
|
312
|
+
# add the filename at the beginning of the search result line, if
|
313
|
+
# the target is one file. This behavior is not suitable in this
|
314
|
+
# purpose. The code below adds the filename when the target is
|
315
|
+
# one file.
|
316
|
+
|
317
|
+
def invoke_searcher_for_entries(searcher, pattern, entries)
|
318
|
+
output = []
|
319
|
+
|
320
|
+
num_of_entries = entries.size
|
321
|
+
if num_of_entries == 1
|
322
|
+
# If the search taget is one file, the output needs special
|
323
|
+
# treatment.
|
324
|
+
file = abspath(entries[0])
|
325
|
+
o, s = Open3.capture2(searcher, *find_searcher_options(searcher),
|
326
|
+
pattern, file)
|
327
|
+
if s.success? && (! o.empty?)
|
328
|
+
output += o.lines.map { |line|
|
329
|
+
# add filename at the beginning of the search result line
|
330
|
+
[file, line.chomp].join(":")
|
331
|
+
}
|
332
|
+
end
|
333
|
+
elsif num_of_entries > LIMIT_OF_FILES
|
334
|
+
output += invoke_searcher_for_entries(searcher, pattern, entries[0..(LIMIT_OF_FILES - 1)])
|
335
|
+
output += invoke_searcher_for_entries(searcher, pattern, entries[LIMIT_OF_FILES..-1])
|
336
|
+
else
|
337
|
+
# When the number of target is less than the upper limit,
|
338
|
+
# invoke the searcher with all of target files as its
|
339
|
+
# arguments.
|
340
|
+
files = find_files(entries)
|
341
|
+
o, s = Open3.capture2(searcher, *find_searcher_options(searcher),
|
342
|
+
pattern, *files)
|
343
|
+
if s.success? && (! o.empty?)
|
344
|
+
output += o.lines.map(&:chomp)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
output
|
349
|
+
end
|
350
|
+
|
351
|
+
SEARCHER_OPTS = {
|
352
|
+
# case insensitive, print line number, recursive search, work as egrep
|
353
|
+
"grep" => ["-i", "-n", "-R", "-E"],
|
354
|
+
# case insensitive, print line number, recursive search
|
355
|
+
"egrep" => ["-i", "-n", "-R"],
|
356
|
+
# case insensitive, print line number, recursive search, work as gegrep
|
357
|
+
"ggrep" => ["-i", "-n", "-R", "-E"],
|
358
|
+
# case insensitive, print line number, recursive search
|
359
|
+
"gegrep" => ["-i", "-n", "-R"],
|
360
|
+
# smart case, print line number, no color
|
361
|
+
"rg" => ["-S", "-n", "--no-heading", "--color", "never"],
|
362
|
+
}
|
363
|
+
|
364
|
+
def find_searcher_options(searcher)
|
365
|
+
@searcher_options || SEARCHER_OPTS[File.basename(searcher)] || ""
|
366
|
+
end
|
367
|
+
|
368
|
+
def find_files(timestamps)
|
369
|
+
timestamps.map{|stamp| abspath(stamp)}
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# The argument must be an Array contains the searcher output.
|
374
|
+
# Each item is constructed from 3 parts:
|
375
|
+
# "<pathname>:<integer>:<text>"
|
376
|
+
#
|
377
|
+
# For example, it may looks like:
|
378
|
+
#
|
379
|
+
# "/somewhere/2020/11/20201101044300.md:18:foo is foo"
|
380
|
+
#
|
381
|
+
# Or it may contains more ":" in the text part as:
|
382
|
+
#
|
383
|
+
# "/somewhere/2020/11/20201101044500.md:119:apple:orange:grape"
|
384
|
+
#
|
385
|
+
# In the latter case, `split(":")` will split it too much. That is,
|
386
|
+
# the result will be:
|
387
|
+
#
|
388
|
+
# ["/somewhere/2020/11/20201101044500.md", "119", "apple", "orange", "grape"]
|
389
|
+
#
|
390
|
+
# Text part must be joined with ":".
|
391
|
+
|
392
|
+
def construct_search_result(output)
|
393
|
+
output.map { |line|
|
394
|
+
begin
|
395
|
+
pathname, num, *match_text = line.split(":")
|
396
|
+
[Timestamp.parse_s(timestamp_str(pathname)),
|
397
|
+
num.to_i,
|
398
|
+
match_text.join(":")]
|
399
|
+
rescue InvalidTimestampStringError, TypeError => _
|
400
|
+
raise InvalidSearchResultError, [@searcher, @searcher_options.join(" ")].join(" ")
|
401
|
+
end
|
402
|
+
}.compact
|
403
|
+
end
|
404
|
+
|
405
|
+
def find_searcher(program = nil)
|
406
|
+
candidates = [FAVORITE_SEARCHER]
|
407
|
+
candidates.unshift(program) unless program.nil? || candidates.include?(program)
|
408
|
+
search_paths = ENV["PATH"].split(":")
|
409
|
+
candidates.map { |prog|
|
410
|
+
find_in_paths(prog, search_paths)
|
411
|
+
}[0]
|
412
|
+
end
|
413
|
+
|
414
|
+
def find_in_paths(prog, paths)
|
415
|
+
paths.each { |p|
|
416
|
+
abspath = File.expand_path(prog, p)
|
417
|
+
return abspath if FileTest.exist?(abspath) && FileTest.executable?(abspath)
|
418
|
+
}
|
419
|
+
nil
|
420
|
+
end
|
222
421
|
# :startdoc:
|
223
422
|
|
224
423
|
end
|
data/lib/textrepo/repository.rb
CHANGED
@@ -43,13 +43,27 @@ module Textrepo
|
|
43
43
|
def read(timestamp); []; end
|
44
44
|
|
45
45
|
##
|
46
|
-
# Updates the content with text in the repository, which is
|
47
|
-
# associated to the
|
46
|
+
# Updates the content with given text in the repository, which is
|
47
|
+
# associated to the given Timestamp object. Returns the Timestamp
|
48
|
+
# newly generated during the execution.
|
49
|
+
#
|
50
|
+
# When true is passed as the third argument, keeps the Timestamp
|
51
|
+
# unchanged, though updates the content. Then, returns the given
|
52
|
+
# Timestamp object.
|
53
|
+
#
|
54
|
+
# If the given Timestamp object is not existed as a Timestamp
|
55
|
+
# attached to text in the repository, raises
|
56
|
+
# MissingTimestampError.
|
57
|
+
#
|
58
|
+
# If the given text is empty, raises EmptyTextError.
|
59
|
+
#
|
60
|
+
# If the given text is identical to the text in the repository,
|
61
|
+
# does nothing. Returns the given timestamp itself.
|
48
62
|
#
|
49
63
|
# :call-seq:
|
50
|
-
# update(Timestamp, Array) -> Timestamp
|
64
|
+
# update(Timestamp, Array, true or false) -> Timestamp
|
51
65
|
|
52
|
-
def update(timestamp, text); timestamp; end
|
66
|
+
def update(timestamp, text, keep_stamp = false); timestamp; end
|
53
67
|
|
54
68
|
##
|
55
69
|
# Deletes the content in the repository, which is associated to
|
@@ -92,6 +106,25 @@ module Textrepo
|
|
92
106
|
# exist?(Timestamp) -> true or false
|
93
107
|
|
94
108
|
def exist?(timestamp); false; end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Searches a pattern (word or regular expression) in text those
|
112
|
+
# matches to a given timestamp pattern. Returns an Array of
|
113
|
+
# search results. If no match, returns an empty Array.
|
114
|
+
#
|
115
|
+
# See the document for Repository#entries about a timestamp
|
116
|
+
# pattern. When nil is passed as a timestamp pattern, searching
|
117
|
+
# applies to all text in the repository.
|
118
|
+
#
|
119
|
+
# Each entry of the result Array is constructed from 3 items, (1)
|
120
|
+
# timestamp (Timestamp), (2) line number (Integer), (3) matched
|
121
|
+
# line (String).
|
122
|
+
#
|
123
|
+
# :call-seq:
|
124
|
+
# search(String for pattern, String for Timestamp pattern) -> Array
|
125
|
+
|
126
|
+
def search(pattern, stamp_pattern = nil); []; end
|
127
|
+
|
95
128
|
end
|
96
129
|
|
97
130
|
require_relative 'file_system_repository'
|
data/lib/textrepo/timestamp.rb
CHANGED
@@ -98,7 +98,14 @@ module Textrepo
|
|
98
98
|
ye, mo, da, ho, mi, se, sfx = split_stamp(stamp_str).map(&:to_i)
|
99
99
|
Timestamp.new(Time.new(ye, mo, da, ho, mi, se), sfx)
|
100
100
|
rescue InvalidTimestampStringError, ArgumentError => _
|
101
|
-
|
101
|
+
emsg = if stamp_str.nil?
|
102
|
+
"(nil)"
|
103
|
+
elsif stamp_str.empty?
|
104
|
+
"(empty string)"
|
105
|
+
else
|
106
|
+
stamp_str
|
107
|
+
end
|
108
|
+
raise InvalidTimestampStringError, emsg
|
102
109
|
end
|
103
110
|
end
|
104
111
|
|
data/lib/textrepo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: textrepo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4
|
4
|
+
version: 0.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mnbi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|