Module: LucaRecord::IO::ClassMethods

Defined in:
lib/luca_record/io.rb

Instance Method Summary collapse

Instance Method Details

#add_status!(id, status, basedir = @dirname) ⇒ Object



203
204
205
206
207
208
209
210
# File 'lib/luca_record/io.rb', line 203

def add_status!(id, status, basedir = @dirname)
  path = abs_path(basedir) / id2path(id)
  origin = YAML.safe_load(File.read(path), permitted_classes: [Date])
  newline = { status => DateTime.now.to_s }
  origin['status'] = [] if origin['status'].nil?
  origin['status'] << newline
  File.write(path, YAML.dump(origin.sort.to_h))
end

#all(basedir = @dirname) ⇒ Object

retrieve all data



117
118
119
120
121
122
123
# File 'lib/luca_record/io.rb', line 117

def all(basedir = @dirname)
  return enum_for(:all, basedir) unless block_given?

  open_all(basedir) do |f|
    yield load_data(f)
  end
end

#asof(year, month = nil, day = nil, basedir = @dirname) ⇒ Object

search date based record.

  • data hash

  • data id. Array like [2020H, V001]



86
87
88
89
90
# File 'lib/luca_record/io.rb', line 86

def asof(year, month = nil, day = nil, basedir = @dirname)
  return enum_for(:search, year, month, day, nil, basedir) unless block_given?

  search(year, month, day, nil, basedir) { |data, path| yield data, path }
end

#change_codes(id, new_codes, basedir = @dirname) ⇒ Object

change filename with new code set



242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/luca_record/io.rb', line 242

def change_codes(id, new_codes, basedir = @dirname)
  raise 'invalid id' if id.split('/').length != 2

  newfile = new_codes.empty? ? id : id + '-' + new_codes.join('-')
  Dir.chdir(abs_path(basedir)) do
    origin = Dir.glob("#{id}*")
    raise 'duplicated files' if origin.length != 1

    File.rename(origin.first, newfile)
  end
  newfile
end

#create(obj, date: nil, codes: nil, basedir: @dirname) ⇒ Object

create record both of uuid/date identified.



139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/luca_record/io.rb', line 139

def create(obj, date: nil, codes: nil, basedir: @dirname)
  validate_keys(obj)
  if date
    create_record(obj, date, codes, basedir)
  else
    obj['id'] = LucaSupport::Code.issue_random_id
    open_hashed(basedir, obj['id'], 'w') do |f|
      f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
    end
    obj['id']
  end
end

#delete(id, basedir = @dirname) ⇒ Object

delete file by id



235
236
237
238
# File 'lib/luca_record/io.rb', line 235

def delete(id, basedir = @dirname)
  FileUtils.rm(Pathname(abs_path(basedir)) / id2path(id))
  id
end

#dir_digest(year, month, basedir = @dirname) ⇒ Object

Calculate md5sum under specific month directory.



335
336
337
338
339
340
341
342
# File 'lib/luca_record/io.rb', line 335

def dir_digest(year, month, basedir = @dirname)
  subdir = year.to_s + LucaSupport::Code.encode_month(month)
  digest = String.new
  open_records(basedir, subdir).each do |f, path|
    digest = update_digest(digest, f.read, path[1])
  end
  digest
end

#encode_hashed_path(id, split_factor = 3) ⇒ Object

Directory separation for performance. Same as Git way.



286
287
288
289
290
291
292
293
# File 'lib/luca_record/io.rb', line 286

def encode_hashed_path(id, split_factor = 3)
  len = id.length
  if len <= split_factor
    ['', id]
  else
    [id[0, split_factor], id[split_factor, len - split_factor]]
  end
end

#find(id, basedir = @dirname) ⇒ Object

find ID based record. Support uuid and encoded date.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/luca_record/io.rb', line 33

def find(id, basedir = @dirname)
  return enum_for(:find, id, basedir).first unless block_given?

  if id.length >= 40
    open_hashed(basedir, id) do |f|
      yield load_data(f)
    end
  elsif id.length >= 7
    parts = id.split('/')
    open_records(basedir, parts[0], parts[1]) do |f, path|
      yield load_data(f, path)
    end
  else
    raise 'specified id length is too short'
  end
end

#find_secure(id, basedir = @dirname, prefix = 's_') ⇒ Object

Merge 2 files under plain & prefixed dirs. prefixed contents will override duplicated keys. This function does not provide encryption/decryption.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/luca_record/io.rb', line 54

def find_secure(id, basedir = @dirname, prefix = 's_')
  if id.length >= 40
    plain = open_hashed(basedir, id) do |f|
      load_data(f)
    end
    secure = begin
               open_hashed("#{prefix}#{basedir}", id) do |f|
                 load_data(f)
               end
             rescue
               # No file exists, and so on.
               {}
             end
  elsif id.length >= 7
    parts = id.split('/')
    plain = open_records(basedir, parts[0], parts[1]) do |f|
      load_data(f)
    end
    secure = open_records("#{prefix}#{basedir}", parts[0], parts[1]) do |f|
      load_data(f)
    end
  else
    raise 'specified id length is too short'
  end
  plain.merge(secure)
end

#id2path(id) ⇒ Object

Convert ID to file directory/filename path. 1st element of Array is used as directory, the others as filename. String without ‘/’ is converted as git-like structure. Normal argument is as follows:

['2020H', 'V001', 'a7b806d04a044c6dbc4ce72932867719']
  => '2020H/V001-a7b806d04a044c6dbc4ce72932867719'
'a7b806d04a044c6dbc4ce72932867719'
  => 'a7b/806d04a044c6dbc4ce72932867719'
'2020H/V001'
  => '2020H/V001'


270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/luca_record/io.rb', line 270

def id2path(id)
  if id.is_a?(Array)
    case id.length
    when 0..2
      id.join('/')
    else
      [id[0], id[1..-1].join('-')].join('/')
    end
  elsif id.include?('/')
    id
  else
    encode_hashed_path(id).join('/')
  end
end

#id_completion(phrase, label: 'name', basedir: @dirname) ⇒ Object

If multiple ID matched, return short ID and human readable label.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/luca_record/io.rb', line 174

def id_completion(phrase, label: 'name', basedir: @dirname)
  list = prefix_search(phrase, basedir: basedir)
  case list.length
  when 1
    list
  when 0
    raise 'No match on specified phrase'
  else
    (3..list[0].length).each do |l|
      if list.map { |id| id[0, l] }.uniq.length == list.length
        return list.map { |id| { id: id[0, l], label: find(id).dig(label) } }
      end
    end
  end
end

#latest_month(code = nil, basedir = @dirname) ⇒ Object

year, month

pair of the latest record



127
128
129
# File 'lib/luca_record/io.rb', line 127

def latest_month(code = nil, basedir = @dirname)
  LucaSupport::Code.decode_term(Dir.entries(abs_path(basedir)).max)
end

#load_project(path, ext_conf: nil) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/luca_record/io.rb', line 295

def load_project(path, ext_conf: nil)
  CONST.set_pjdir(path)
  config = {
    'decimal_separator' => '.',
    'thousands_separator' => ','
  }
  begin
    config.merge!(YAML.safe_load(
    File.read(Pathname(CONST.configdir) / 'config.yml'),
      permitted_classes: [Date]
    ))
  rescue Errno::ENOENT
    STDERR.puts "INFO: config.yml not found. Continue with default settings."
  end
  if ext_conf
    begin
      config.merge!(YAML.safe_load(
        File.read(Pathname(CONST.configdir) / ext_conf),
        permitted_classes: [Date]
      ))
    rescue Errno::ENOENT
      STDERR.puts "WARN: #{ext_conf} not found. Extended options are not effective."
    end
  end
  config['decimal_num'] ||= config['country'] == 'jp' ? 0 : 2
  CONST.set_config(config)
end

#new_record_id(basedir, date_obj) ⇒ Object



329
330
331
# File 'lib/luca_record/io.rb', line 329

def new_record_id(basedir, date_obj)
  LucaSupport::Code.encode_txid(new_record_no(basedir, date_obj))
end

#prefix_search(phrase, basedir: @dirname) ⇒ Object



190
191
192
193
194
195
# File 'lib/luca_record/io.rb', line 190

def prefix_search(phrase, basedir: @dirname)
  glob_str = phrase.length <= 3 ? "#{phrase}*/*" : "#{id2path(phrase)}*"
  Dir.chdir(abs_path(basedir)) do
    Dir.glob(glob_str).to_a.map! { |path| path.gsub!('/', '') }
  end
end

#prepare_dir!(basedir, date_obj) ⇒ Object



197
198
199
200
201
# File 'lib/luca_record/io.rb', line 197

def prepare_dir!(basedir, date_obj)
  dir_name = (Pathname(basedir) + LucaSupport::Code.encode_dirname(date_obj)).to_s
  FileUtils.mkdir_p(dir_name) unless Dir.exist?(dir_name)
  dir_name
end

#save(obj, basedir = @dirname) ⇒ Object

update file with obj



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/luca_record/io.rb', line 213

def save(obj, basedir = @dirname)
  if obj['id'].nil?
    create(obj, basedir)
  else
    validate_keys(obj)
    if obj['id'].length < 40
      parts = obj['id'].split('/')
      raise 'invalid ID' if parts.length != 2

      open_records(basedir, parts[0], parts[1], nil, 'w') do |f, path|
        f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
      end
    else
      open_hashed(basedir, obj['id'], 'w') do |f|
        f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
      end
    end
  end
  obj['id']
end

#search(year, month = nil, day = nil, code = nil, basedir = @dirname) ⇒ Object

search with date params & code.



106
107
108
109
110
111
112
113
# File 'lib/luca_record/io.rb', line 106

def search(year, month = nil, day = nil, code = nil, basedir = @dirname)
  return enum_for(:search, year, month, day, code, basedir) unless block_given?

  subdir = year.to_s + LucaSupport::Code.encode_month(month)
  open_records(basedir, subdir, LucaSupport::Code.encode_date(day), code) do |f, path|
    yield load_data(f, path), path
  end
end

#term(start_year, start_month, end_year, end_month, code = nil, basedir = @dirname) ⇒ Object

scan ranging data on multiple months



94
95
96
97
98
99
100
101
102
# File 'lib/luca_record/io.rb', line 94

def term(start_year, start_month, end_year, end_month, code = nil, basedir = @dirname)
  return enum_for(:term, start_year, start_month, end_year, end_month, code, basedir) unless block_given?

  LucaSupport::Code.encode_term(start_year, start_month, end_year, end_month).each do |subdir|
    open_records(basedir, subdir, nil, code) do |f, path|
      yield load_data(f, path)
    end
  end
end

#upsert(obj, basedir: @dirname) ⇒ Object

update uuid keyed record based on ‘id’ field. If not found, just create



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/luca_record/io.rb', line 155

def upsert(obj, basedir: @dirname)
  return nil if obj['id'].nil?

  validate_keys(obj)
  merged = begin
              open_hashed(basedir, obj['id'], 'r') do |f|
                load_data(f).merge(obj)
              end
            rescue
              obj
            end
  open_hashed(basedir, obj['id'], 'w') do |f|
    f.write(YAML.dump(LucaSupport::Code.readable(merged.sort.to_h)))
  end
  obj['id']
end

#valid_project?(path = CONST.pjdir) ⇒ Boolean

test if having required dirs/files under exec path

Returns:

  • (Boolean)


324
325
326
327
# File 'lib/luca_record/io.rb', line 324

def valid_project?(path = CONST.pjdir)
  project_dir = Pathname(path)
  FileTest.directory?( (project_dir / 'data').to_s)
end