textrepo 0.4.3 → 0.5.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Rakefile +0 -1
- data/lib/textrepo/error.rb +57 -14
- data/lib/textrepo/file_system_repository.rb +230 -21
- data/lib/textrepo/repository.rb +38 -10
- data/lib/textrepo/timestamp.rb +67 -18
- 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: c33d87733a93d357237cf217161c5d56c37e9b48d4f9a52b9a59ec8a945f6377
|
4
|
+
data.tar.gz: 2c8969d2ee93dfb1f6b84e49be43eb81630e63341ad77da2fbed3c59be465b78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 711e6b19e280887f6558ecd62e39abf6146efbafe817ad9db329dd33c9fdd0e4b5d5feef4c8a246872c748142daee96518030ab378c2756dd1eb66ca40dbbade
|
7
|
+
data.tar.gz: 3b129941b8df7f41540a5088357bd562a11c3567635e8e26002a2a4222850b89a58d1147d0b3305b17835f0e720b86bf3a8b4ea0dccbfb01d098d00962bbe42d
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
## [Unreleased]
|
8
8
|
Nothing to record here.
|
9
9
|
|
10
|
+
## [0.5.2] - 2020-11-03
|
11
|
+
### Changed
|
12
|
+
- Fix issue #34:
|
13
|
+
- fix FileSystemRepository#entries to accept "yyyymo" pattern as a
|
14
|
+
Timestamp pattern.
|
15
|
+
- Fix issue #33: fix typo in the doc for FileSystemRepository.new.
|
16
|
+
- Fix issue #31: unfriendly error message of Timestamp.parse_s.
|
17
|
+
|
18
|
+
## [0.5.1] - 2020-11-02
|
19
|
+
### Changed
|
20
|
+
- Fix issue #28.
|
21
|
+
- Modify `Repository#update` to do nothing when the given text is
|
22
|
+
identical to the one in the repository.
|
23
|
+
|
24
|
+
## [0.5.0] - 2020-11-01
|
25
|
+
### Added
|
26
|
+
- Add a new API `Repository#search`.
|
27
|
+
- Add a new API `Repository#exist?`. (0.4.3)
|
28
|
+
|
10
29
|
## [0.4.0] - 2020-10-14
|
11
30
|
### Added
|
12
31
|
- Released to rubygems.org.
|
data/Rakefile
CHANGED
data/lib/textrepo/error.rb
CHANGED
@@ -1,12 +1,39 @@
|
|
1
1
|
module Textrepo
|
2
|
+
|
3
|
+
##
|
4
|
+
# Following errors might occur in repository operations:
|
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
|
+
# +---------------------------------+-----------------------+
|
20
|
+
|
2
21
|
class Error < StandardError; end
|
3
22
|
|
23
|
+
# :stopdoc:
|
4
24
|
module ErrMsg
|
5
25
|
UNKNOWN_REPO_TYPE = 'unknown type for repository: %s'
|
6
26
|
DUPLICATE_TIMESTAMP = 'duplicate timestamp: %s'
|
7
27
|
EMPTY_TEXT = 'empty text'
|
8
28
|
MISSING_TIMESTAMP = 'missing timestamp: %s'
|
29
|
+
INVALID_TIMESTAMP_STRING = "invalid string as timestamp: %s"
|
30
|
+
INVALID_SEARCH_RESULT = "invalid result by searcher: %s"
|
9
31
|
end
|
32
|
+
# :startdoc:
|
33
|
+
|
34
|
+
##
|
35
|
+
# An error raised if unknown type was specified as the repository
|
36
|
+
# type.
|
10
37
|
|
11
38
|
class UnknownRepoTypeError < Error
|
12
39
|
def initialize(type)
|
@@ -14,20 +41,9 @@ module Textrepo
|
|
14
41
|
end
|
15
42
|
end
|
16
43
|
|
17
|
-
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# +--------------------------+---------------------+
|
21
|
-
# | create (timestamp, text) | Duplicate timestamp |
|
22
|
-
# | | Empty text |
|
23
|
-
# +--------------------------+---------------------+
|
24
|
-
# | read (timestamp) | Missing timestamp |
|
25
|
-
# +--------------------------+---------------------+
|
26
|
-
# | update (timestamp, text) | Mssing timestamp |
|
27
|
-
# | | Empty text |
|
28
|
-
# +--------------------------+---------------------+
|
29
|
-
# | delete (timestamp) | Missing timestamp |
|
30
|
-
# +--------------------------+---------------------+
|
44
|
+
##
|
45
|
+
# An error raised if the specified timestamp has already exist in
|
46
|
+
# the repository.
|
31
47
|
|
32
48
|
class DuplicateTimestampError < Error
|
33
49
|
def initialize(timestamp)
|
@@ -35,16 +51,43 @@ module Textrepo
|
|
35
51
|
end
|
36
52
|
end
|
37
53
|
|
54
|
+
##
|
55
|
+
# An error raised if the given text is empty.
|
56
|
+
|
38
57
|
class EmptyTextError < Error
|
39
58
|
def initialize
|
40
59
|
super(ErrMsg::EMPTY_TEXT)
|
41
60
|
end
|
42
61
|
end
|
43
62
|
|
63
|
+
##
|
64
|
+
# An error raised if the given timestamp has not exist in the
|
65
|
+
# repository.
|
66
|
+
|
44
67
|
class MissingTimestampError < Error
|
45
68
|
def initialize(timestamp)
|
46
69
|
super(ErrMsg::MISSING_TIMESTAMP % timestamp)
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
73
|
+
##
|
74
|
+
# An error raised if an argument is invalid to convert a
|
75
|
+
# Textrepo::Timestamp object.
|
76
|
+
|
77
|
+
class InvalidTimestampStringError < Error
|
78
|
+
def initialize(str)
|
79
|
+
super(ErrMsg::INVALID_TIMESTAMP_STRING % str)
|
80
|
+
end
|
81
|
+
end
|
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
|
+
|
50
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,12 +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`.
|
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
|
+
#
|
85
|
+
# :call-seq:
|
86
|
+
# new(Hash or Hash like object) -> FileSystemRepository
|
50
87
|
|
51
88
|
def initialize(conf)
|
52
89
|
super
|
@@ -55,14 +92,16 @@ module Textrepo
|
|
55
92
|
@path = File.expand_path("#{name}", base)
|
56
93
|
FileUtils.mkdir_p(@path)
|
57
94
|
@extname = conf[:default_extname] || FAVORITE_EXTNAME
|
95
|
+
@searcher = find_searcher(conf[:searcher])
|
96
|
+
@searcher_options = conf[:searcher_options]
|
58
97
|
end
|
59
98
|
|
60
99
|
##
|
61
100
|
# Creates a file into the repository, which contains the specified
|
62
101
|
# text and is associated to the timestamp.
|
63
|
-
|
102
|
+
#
|
64
103
|
# :call-seq:
|
65
|
-
# create(Timestamp, Array)
|
104
|
+
# create(Timestamp, Array) -> Timestamp
|
66
105
|
|
67
106
|
def create(timestamp, text)
|
68
107
|
abs = abspath(timestamp)
|
@@ -76,9 +115,9 @@ module Textrepo
|
|
76
115
|
##
|
77
116
|
# Reads the file content in the repository. Then, returns its
|
78
117
|
# content.
|
79
|
-
|
118
|
+
#
|
80
119
|
# :call-seq:
|
81
|
-
# read(Timestamp)
|
120
|
+
# read(Timestamp) -> Array
|
82
121
|
|
83
122
|
def read(timestamp)
|
84
123
|
abs = abspath(timestamp)
|
@@ -93,31 +132,35 @@ module Textrepo
|
|
93
132
|
##
|
94
133
|
# Updates the file content in the repository. A new timestamp
|
95
134
|
# will be attached to the text.
|
96
|
-
|
135
|
+
#
|
136
|
+
# See the documentation of Repository#update to know about errors
|
137
|
+
# and constraints of this method.
|
138
|
+
#
|
97
139
|
# :call-seq:
|
98
|
-
# update(Timestamp, Array)
|
140
|
+
# update(Timestamp, Array) -> Timestamp
|
99
141
|
|
100
142
|
def update(timestamp, text)
|
101
143
|
raise EmptyTextError if text.empty?
|
102
|
-
|
103
|
-
|
144
|
+
raise MissingTimestampError, timestamp unless exist?(timestamp)
|
145
|
+
|
146
|
+
# does nothing if given text is the same in the repository one
|
147
|
+
return timestamp if read(timestamp) == text
|
104
148
|
|
105
149
|
# the text must be stored with the new timestamp
|
106
150
|
new_stamp = Timestamp.new(Time.now)
|
107
|
-
|
108
|
-
write_text(new_abs, text)
|
151
|
+
write_text(abspath(new_stamp), text)
|
109
152
|
|
110
|
-
# delete the original file in the repository
|
111
|
-
FileUtils.remove_file(
|
153
|
+
# delete the original text file in the repository
|
154
|
+
FileUtils.remove_file(abspath(timestamp))
|
112
155
|
|
113
156
|
new_stamp
|
114
157
|
end
|
115
158
|
|
116
159
|
##
|
117
160
|
# Deletes the file in the repository.
|
118
|
-
|
161
|
+
#
|
119
162
|
# :call-seq:
|
120
|
-
# delete(Timestamp)
|
163
|
+
# delete(Timestamp) -> Array
|
121
164
|
|
122
165
|
def delete(timestamp)
|
123
166
|
abs = abspath(timestamp)
|
@@ -131,9 +174,9 @@ module Textrepo
|
|
131
174
|
|
132
175
|
##
|
133
176
|
# Finds entries of text those timestamp matches the specified pattern.
|
134
|
-
|
177
|
+
#
|
135
178
|
# :call-seq:
|
136
|
-
# entries(String = nil)
|
179
|
+
# entries(String = nil) -> Array of Timestamp instances
|
137
180
|
|
138
181
|
def entries(stamp_pattern = nil)
|
139
182
|
results = []
|
@@ -142,9 +185,9 @@ module Textrepo
|
|
142
185
|
when "yyyymoddhhmiss_lll".size
|
143
186
|
stamp = Timestamp.parse_s(stamp_pattern)
|
144
187
|
if exist?(stamp)
|
145
|
-
results << stamp
|
188
|
+
results << stamp
|
146
189
|
end
|
147
|
-
when 0, "yyyymoddhhmiss".size, "yyyymodd".size
|
190
|
+
when 0, "yyyymoddhhmiss".size, "yyyymodd".size, "yyyymo".size
|
148
191
|
results += find_entries(stamp_pattern)
|
149
192
|
when 4 # "yyyy" or "modd"
|
150
193
|
pat = nil
|
@@ -168,7 +211,7 @@ module Textrepo
|
|
168
211
|
##
|
169
212
|
# Check the existence of text which is associated with the given
|
170
213
|
# timestamp.
|
171
|
-
|
214
|
+
#
|
172
215
|
# :call-seq:
|
173
216
|
# exist?(Timestamp) -> true or false
|
174
217
|
|
@@ -176,6 +219,27 @@ module Textrepo
|
|
176
219
|
FileTest.exist?(abspath(timestamp))
|
177
220
|
end
|
178
221
|
|
222
|
+
##
|
223
|
+
# Searches a pattern in all text. The given pattern is a word to
|
224
|
+
# search or a regular expression. The pattern would be passed to
|
225
|
+
# a searcher program as it passed.
|
226
|
+
#
|
227
|
+
# See the document for Textrepo::Repository#search to know about
|
228
|
+
# the search result.
|
229
|
+
#
|
230
|
+
# :call-seq:
|
231
|
+
# search(String for pattern, String for Timestamp pattern) -> Array
|
232
|
+
|
233
|
+
def search(pattern, stamp_pattern = nil)
|
234
|
+
result = nil
|
235
|
+
if stamp_pattern.nil?
|
236
|
+
result = invoke_searcher_at_repo_root(@searcher, pattern)
|
237
|
+
else
|
238
|
+
result = invoke_searcher_for_entries(@searcher, pattern, entries(stamp_pattern))
|
239
|
+
end
|
240
|
+
construct_search_result(result)
|
241
|
+
end
|
242
|
+
|
179
243
|
# :stopdoc:
|
180
244
|
|
181
245
|
private
|
@@ -205,9 +269,154 @@ module Textrepo
|
|
205
269
|
|
206
270
|
def find_entries(stamp_pattern)
|
207
271
|
Dir.glob("#{@path}/**/#{stamp_pattern}*.#{@extname}").map { |e|
|
208
|
-
|
272
|
+
begin
|
273
|
+
Timestamp.parse_s(timestamp_str(e))
|
274
|
+
rescue InvalidTimestampStringError => _
|
275
|
+
# Just ignore the erroneous entry, since it is not a text in
|
276
|
+
# the repository. It may be a garbage, or some kind of
|
277
|
+
# hidden stuff of the repository, ... etc.
|
278
|
+
nil
|
279
|
+
end
|
280
|
+
}.compact
|
281
|
+
end
|
282
|
+
|
283
|
+
##
|
284
|
+
# The upper limit of files to search at one time. The value has
|
285
|
+
# no reason to select. It seems to me that not too much, not too
|
286
|
+
# little to handle in one process to search.
|
287
|
+
|
288
|
+
LIMIT_OF_FILES = 20
|
289
|
+
|
290
|
+
##
|
291
|
+
# When no timestamp pattern was given, invoke the searcher with
|
292
|
+
# the repository root path as its argument and the recursive
|
293
|
+
# searching option. The search could be done in only one process.
|
294
|
+
|
295
|
+
def invoke_searcher_at_repo_root(searcher, pattern)
|
296
|
+
o, s = Open3.capture2(searcher, *find_searcher_options(searcher),
|
297
|
+
pattern, @path)
|
298
|
+
output = []
|
299
|
+
output += o.lines.map(&:chomp) if s.success? && (! o.empty?)
|
300
|
+
output
|
301
|
+
end
|
302
|
+
|
303
|
+
##
|
304
|
+
# When a timestamp pattern was given, at first, list target files,
|
305
|
+
# then invoke the searcher for those files. Since the number of
|
306
|
+
# target files may be so much, it seems to be dangerous to pass
|
307
|
+
# all of them to a single search process at one time.
|
308
|
+
#
|
309
|
+
# One more thing to mention, the searcher, like `grep`, does not
|
310
|
+
# add the filename at the beginning of the search result line, if
|
311
|
+
# the target is one file. This behavior is not suitable in this
|
312
|
+
# purpose. The code below adds the filename when the target is
|
313
|
+
# one file.
|
314
|
+
|
315
|
+
def invoke_searcher_for_entries(searcher, pattern, entries)
|
316
|
+
output = []
|
317
|
+
|
318
|
+
num_of_entries = entries.size
|
319
|
+
if num_of_entries == 1
|
320
|
+
# If the search taget is one file, the output needs special
|
321
|
+
# treatment.
|
322
|
+
file = abspath(entries[0])
|
323
|
+
o, s = Open3.capture2(searcher, *find_searcher_options(searcher),
|
324
|
+
pattern, file)
|
325
|
+
if s.success? && (! o.empty)
|
326
|
+
output += o.lines.map { |line|
|
327
|
+
# add filename at the beginning of the search result line
|
328
|
+
[file, line.chomp].join(":")
|
329
|
+
}
|
330
|
+
end
|
331
|
+
elsif num_of_entries > LIMIT_OF_FILES
|
332
|
+
output += invoke_searcher_for_entries(searcher, pattern, entries[0..(LIMIT_OF_FILES - 1)])
|
333
|
+
output += invoke_searcher_for_entries(searcher, pattern, entries[LIMIT_OF_FILES..-1])
|
334
|
+
else
|
335
|
+
# When the number of target is less than the upper limit,
|
336
|
+
# invoke the searcher with all of target files as its
|
337
|
+
# arguments.
|
338
|
+
files = find_files(entries)
|
339
|
+
o, s = Open3.capture2(searcher, *find_searcher_options(searcher),
|
340
|
+
pattern, *files)
|
341
|
+
if s.success? && (! o.empty)
|
342
|
+
output += o.lines.map(&:chomp)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
output
|
347
|
+
end
|
348
|
+
|
349
|
+
SEARCHER_OPTS = {
|
350
|
+
# case insensitive, print line number, recursive search, work as egrep
|
351
|
+
"grep" => ["-i", "-n", "-R", "-E"],
|
352
|
+
# case insensitive, print line number, recursive search
|
353
|
+
"egrep" => ["-i", "-n", "-R"],
|
354
|
+
# case insensitive, print line number, recursive search, work as gegrep
|
355
|
+
"ggrep" => ["-i", "-n", "-R", "-E"],
|
356
|
+
# case insensitive, print line number, recursive search
|
357
|
+
"gegrep" => ["-i", "-n", "-R"],
|
358
|
+
# smart case, print line number, no color
|
359
|
+
"rg" => ["-S", "-n", "--no-heading", "--color", "never"],
|
360
|
+
}
|
361
|
+
|
362
|
+
def find_searcher_options(searcher)
|
363
|
+
@searcher_options || SEARCHER_OPTS[File.basename(searcher)] || ""
|
364
|
+
end
|
365
|
+
|
366
|
+
def find_files(timestamps)
|
367
|
+
timestamps.map{|stamp| abspath(stamp)}
|
368
|
+
end
|
369
|
+
|
370
|
+
##
|
371
|
+
# The argument must be an Array contains the searcher output.
|
372
|
+
# Each item is constructed from 3 parts:
|
373
|
+
# "<pathname>:<integer>:<text>"
|
374
|
+
#
|
375
|
+
# For example, it may looks like:
|
376
|
+
#
|
377
|
+
# "/somewhere/2020/11/20201101044300.md:18:foo is foo"
|
378
|
+
#
|
379
|
+
# Or it may contains more ":" in the text part as:
|
380
|
+
#
|
381
|
+
# "/somewhere/2020/11/20201101044500.md:119:apple:orange:grape"
|
382
|
+
#
|
383
|
+
# In the latter case, `split(":")` will split it too much. That is,
|
384
|
+
# the result will be:
|
385
|
+
#
|
386
|
+
# ["/somewhere/2020/11/20201101044500.md", "119", "apple", "orange", "grape"]
|
387
|
+
#
|
388
|
+
# Text part must be joined with ":".
|
389
|
+
|
390
|
+
def construct_search_result(output)
|
391
|
+
output.map { |line|
|
392
|
+
begin
|
393
|
+
pathname, num, *match_text = line.split(":")
|
394
|
+
[Timestamp.parse_s(timestamp_str(pathname)),
|
395
|
+
num.to_i,
|
396
|
+
match_text.join(":")]
|
397
|
+
rescue InvalidTimestampStringError, TypeError => _
|
398
|
+
raise InvalidSearchResultError, [@searcher, @searcher_options.join(" ")].join(" ")
|
399
|
+
end
|
400
|
+
}.compact
|
401
|
+
end
|
402
|
+
|
403
|
+
def find_searcher(program = nil)
|
404
|
+
candidates = [FAVORITE_SEARCHER]
|
405
|
+
candidates.unshift(program) unless program.nil? || candidates.include?(program)
|
406
|
+
search_paths = ENV["PATH"].split(":")
|
407
|
+
candidates.map { |prog|
|
408
|
+
find_in_paths(prog, search_paths)
|
409
|
+
}[0]
|
410
|
+
end
|
411
|
+
|
412
|
+
def find_in_paths(prog, paths)
|
413
|
+
paths.each { |p|
|
414
|
+
abspath = File.expand_path(prog, p)
|
415
|
+
return abspath if FileTest.exist?(abspath) && FileTest.executable?(abspath)
|
209
416
|
}
|
417
|
+
nil
|
210
418
|
end
|
419
|
+
# :startdoc:
|
211
420
|
|
212
421
|
end
|
213
422
|
end
|
data/lib/textrepo/repository.rb
CHANGED
@@ -27,7 +27,7 @@ module Textrepo
|
|
27
27
|
##
|
28
28
|
# Stores text data into the repository with the specified timestamp.
|
29
29
|
# Returns the timestamp.
|
30
|
-
|
30
|
+
#
|
31
31
|
# :call-seq:
|
32
32
|
# create(Timestamp, Array) -> Timestamp
|
33
33
|
|
@@ -36,16 +36,25 @@ module Textrepo
|
|
36
36
|
##
|
37
37
|
# Reads text data from the repository, which is associated to the
|
38
38
|
# timestamp. Returns an array which contains the text.
|
39
|
-
|
39
|
+
#
|
40
40
|
# :call-seq:
|
41
41
|
# read(Timestamp) -> Array
|
42
42
|
|
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 timestamp. Returns the timestamp
|
48
|
-
|
46
|
+
# Updates the content with given text in the repository, which is
|
47
|
+
# associated to the given timestamp. Returns the timestamp newly
|
48
|
+
# generated during the execution.
|
49
|
+
#
|
50
|
+
# If the given Timestamp is not existed as a Timestamp attached to
|
51
|
+
# text in the repository, raises MissingTimestampError.
|
52
|
+
#
|
53
|
+
# If the given text is empty, raises EmptyTextError.
|
54
|
+
#
|
55
|
+
# If the given text is identical to the text in the repository,
|
56
|
+
# does nothing. Returns the given timestamp itself.
|
57
|
+
#
|
49
58
|
# :call-seq:
|
50
59
|
# update(Timestamp, Array) -> Timestamp
|
51
60
|
|
@@ -54,7 +63,7 @@ module Textrepo
|
|
54
63
|
##
|
55
64
|
# Deletes the content in the repository, which is associated to
|
56
65
|
# the timestamp. Returns an array which contains the deleted text.
|
57
|
-
|
66
|
+
#
|
58
67
|
# :call-seq:
|
59
68
|
# delete(Timestamp) -> Array
|
60
69
|
|
@@ -63,8 +72,8 @@ module Textrepo
|
|
63
72
|
##
|
64
73
|
# Finds all entries of text those have timestamps which mathes the
|
65
74
|
# specified pattern of timestamp. Returns an array which contains
|
66
|
-
#
|
67
|
-
# returned.
|
75
|
+
# instances of Timestamp. If none of text was found, an empty
|
76
|
+
# array would be returned.
|
68
77
|
#
|
69
78
|
# A pattern must be one of the following:
|
70
79
|
#
|
@@ -78,9 +87,9 @@ module Textrepo
|
|
78
87
|
# If `stamp_pattern` is omitted, the recent entries will be listed.
|
79
88
|
# Then, how many entries are listed depends on the implementaiton
|
80
89
|
# of the concrete repository class.
|
81
|
-
|
90
|
+
#
|
82
91
|
# :call-seq:
|
83
|
-
# entries(String) -> Array
|
92
|
+
# entries(String) -> Array of Timestamp instances
|
84
93
|
|
85
94
|
def entries(stamp_pattern = nil); []; end
|
86
95
|
|
@@ -92,6 +101,25 @@ module Textrepo
|
|
92
101
|
# exist?(Timestamp) -> true or false
|
93
102
|
|
94
103
|
def exist?(timestamp); false; end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Searches a pattern (word or regular expression) in text those
|
107
|
+
# matches to a given timestamp pattern. Returns an Array of
|
108
|
+
# search results. If no match, returns an empty Array.
|
109
|
+
#
|
110
|
+
# See the document for Repository#entries about a timestamp
|
111
|
+
# pattern. When nil is passed as a timestamp pattern, searching
|
112
|
+
# applies to all text in the repository.
|
113
|
+
#
|
114
|
+
# Each entry of the result Array is constructed from 3 items, (1)
|
115
|
+
# timestamp (Timestamp), (2) line number (Integer), (3) matched
|
116
|
+
# line (String).
|
117
|
+
#
|
118
|
+
# :call-seq:
|
119
|
+
# search(String for pattern, String for Timestamp pattern) -> Array
|
120
|
+
|
121
|
+
def search(pattern, stamp_pattern = nil); []; end
|
122
|
+
|
95
123
|
end
|
96
124
|
|
97
125
|
require_relative 'file_system_repository'
|
data/lib/textrepo/timestamp.rb
CHANGED
@@ -1,23 +1,47 @@
|
|
1
1
|
module Textrepo
|
2
2
|
##
|
3
|
-
#
|
3
|
+
# Timestamp is generated from a Time object. It converts a time to
|
4
4
|
# string in the obvious format, such "20201023122400".
|
5
5
|
#
|
6
|
+
# Since the obvious format contains only year, month, day, hour,
|
7
|
+
# minute, and second, the resolution of time is a second. That is,
|
8
|
+
# two Time object those are different only in second will generates
|
9
|
+
# equal Timestamp objects.
|
10
|
+
#
|
11
|
+
# If a client program of Textrepo::Timestamp wants to distinguish
|
12
|
+
# those Time objects, an attribute `suffix` could be used.
|
13
|
+
#
|
14
|
+
# For example, the `suffix` will be converted into a 3 character
|
15
|
+
# string, such "012", "345", "678", ... etc. So, millisecond part
|
16
|
+
# of a Time object will be suitable to pass as `suffix` when
|
17
|
+
# creating a Timestamp object.
|
18
|
+
|
6
19
|
class Timestamp
|
7
20
|
include Comparable
|
8
21
|
|
9
|
-
|
22
|
+
##
|
23
|
+
# Time object which generates the Timestamp object.
|
24
|
+
|
25
|
+
attr_reader :time
|
10
26
|
|
11
27
|
##
|
12
|
-
#
|
13
|
-
|
28
|
+
# An integer specified in `new` method to create the Timestamp object.
|
29
|
+
|
30
|
+
attr_reader :suffix
|
31
|
+
|
32
|
+
##
|
33
|
+
# Creates a Timestamp object from a Time object. In addition, an
|
34
|
+
# Integer can be passed as a suffix use.
|
14
35
|
#
|
36
|
+
# :call-seq:
|
37
|
+
# new(Time, Integer = nil) -> Timestamp
|
38
|
+
|
15
39
|
def initialize(time, suffix = nil)
|
16
40
|
@time = time
|
17
41
|
@suffix = suffix
|
18
42
|
end
|
19
43
|
|
20
|
-
def <=>(other)
|
44
|
+
def <=>(other) # :nodoc:
|
21
45
|
result = (self.time <=> other.time)
|
22
46
|
|
23
47
|
sfx = self.suffix || 0
|
@@ -29,12 +53,10 @@ module Textrepo
|
|
29
53
|
##
|
30
54
|
# Generate an obvious time string.
|
31
55
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
36
|
-
# ```
|
37
|
-
#
|
56
|
+
# %Y %m %d %H %M %S suffix
|
57
|
+
# "2020-12-30 12:34:56 (0 | nil)" -> "20201230123456"
|
58
|
+
# "2020-12-30 12:34:56 (7)" -> "20201230123456_007"
|
59
|
+
|
38
60
|
def to_s
|
39
61
|
s = @time.strftime("%Y%m%d%H%M%S")
|
40
62
|
s += "_#{"%03u" % @suffix}" unless @suffix.nil? || @suffix == 0
|
@@ -43,21 +65,48 @@ module Textrepo
|
|
43
65
|
|
44
66
|
class << self
|
45
67
|
##
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
# "20201230123456_789" => "2020", "12", "30", "12", "34", "56", "789"
|
50
|
-
# ```
|
68
|
+
# Splits a string which represents a timestamp into components.
|
69
|
+
# Each component represents a part of constructs to instantiate
|
70
|
+
# a Time object.
|
51
71
|
#
|
72
|
+
# yyyymoddhhmiss sfx yyyy mo dd hh mi ss sfx
|
73
|
+
# "20201230123456" -> "2020", "12", "30", "12", "34", "56"
|
74
|
+
# "20201230123456_789" -> "2020", "12", "30", "12", "34", "56", "789"
|
75
|
+
#
|
76
|
+
# Raises InvalidTimestampStringError if nil was passed as an arguemnt.
|
77
|
+
|
52
78
|
def split_stamp(stamp_str)
|
79
|
+
raise InvalidTimestampStringError, stamp_str if stamp_str.nil?
|
53
80
|
# yyyy mo dd hh mi ss sfx
|
54
81
|
a = [0..3, 4..5, 6..7, 8..9, 10..11, 12..13, 15..17].map {|r| stamp_str[r]}
|
55
82
|
a[-1].nil? ? a[0..-2] : a
|
56
83
|
end
|
57
84
|
|
85
|
+
##
|
86
|
+
# Generate a Timestamp object from a string which represents a
|
87
|
+
# timestamp, such "20201028163400".
|
88
|
+
#
|
89
|
+
# Raises InvalidTimestampStringError if cannot convert the
|
90
|
+
# argument into a Timestamp object.
|
91
|
+
#
|
92
|
+
# :call-seq:
|
93
|
+
# parse_s("20201028163400") -> Timestamp
|
94
|
+
# parse_s("20201028163529_034") -> Timestamp
|
95
|
+
|
58
96
|
def parse_s(stamp_str)
|
59
|
-
|
60
|
-
|
97
|
+
begin
|
98
|
+
ye, mo, da, ho, mi, se, sfx = split_stamp(stamp_str).map(&:to_i)
|
99
|
+
Timestamp.new(Time.new(ye, mo, da, ho, mi, se), sfx)
|
100
|
+
rescue InvalidTimestampStringError, ArgumentError => e
|
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
|
109
|
+
end
|
61
110
|
end
|
62
111
|
|
63
112
|
end
|
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
|
+
version: 0.5.2
|
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-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|