key_value_name 0.0.1 → 0.1.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.
- checksums.yaml +5 -5
- data/README.md +28 -15
- data/lib/key_value_name/builders/container_builder.rb +49 -0
- data/lib/key_value_name/builders/file_builder.rb +59 -0
- data/lib/key_value_name/builders/folder_builder.rb +45 -0
- data/lib/key_value_name/builders/key_value_builder.rb +43 -0
- data/lib/key_value_name/builders/schema_builder.rb +19 -0
- data/lib/key_value_name/collection.rb +41 -0
- data/lib/key_value_name/marshalers/base.rb +7 -4
- data/lib/key_value_name/marshalers/boolean_marshaler.rb +24 -0
- data/lib/key_value_name/marshalers/float_marshaler.rb +26 -0
- data/lib/key_value_name/marshalers/integer_marshaler.rb +37 -0
- data/lib/key_value_name/marshalers/symbol_marshaler.rb +2 -2
- data/lib/key_value_name/marshalers.rb +14 -2
- data/lib/key_value_name/mixins/file_name.rb +50 -0
- data/lib/key_value_name/mixins/folder_name.rb +34 -0
- data/lib/key_value_name/mixins/name.rb +52 -0
- data/lib/key_value_name/schema.rb +26 -0
- data/lib/key_value_name/spec.rb +38 -17
- data/lib/key_value_name/version.rb +2 -2
- data/lib/key_value_name.rb +26 -6
- data/test/key_value_name/extension_test.rb +49 -0
- data/test/key_value_name/file_builder_test.rb +64 -0
- data/test/key_value_name/folder_builder_test.rb +27 -0
- data/test/key_value_name/key_value_name_test.rb +58 -35
- data/test/key_value_name/schema_test.rb +256 -0
- metadata +27 -12
- data/lib/key_value_name/builder.rb +0 -91
- data/lib/key_value_name/marshalers/numeric_marshaler.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3200bf0b2afdca95befb7a8ec9a3a5d6b815a68930726d6db4bb2df4767349be
|
4
|
+
data.tar.gz: a54d7369f421aa7ab93c0b4fae6258137c1a6e600656743ef9ce816b91a307a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa23bbc6568751705862b8c6d91eee57e0ab8ffd6a3d0a05c31bd7c07fd3094026b05d3bb4e3b19caa01e97572bf0f1c3e434d21dc2eb005fdac70ada2165105
|
7
|
+
data.tar.gz: ecf477eea90b3c33c30cf343d8a8bfa4ff87be743a674a28f1f45e81fe44980da6380dabd7a9f68085dd5a8ba35e105d8de290fdb28cc60087d7d47442d32b74
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
## Synopsis
|
6
6
|
|
7
|
-
|
7
|
+
An 'object-file system mapper' for managing data files. Key-value pairs describing the data in each file are stored in its name. Useful for managing data files for experiments or simulation runs.
|
8
8
|
|
9
9
|
This gem provides:
|
10
10
|
|
@@ -12,32 +12,45 @@ This gem provides:
|
|
12
12
|
|
13
13
|
2. Automatic formatting and type conversion for the parameters.
|
14
14
|
|
15
|
+
3. A schema builder to organize files and folders and declare what parameters are allowed.
|
16
|
+
|
15
17
|
## Usage
|
16
18
|
|
17
19
|
```rb
|
18
20
|
require 'key_value_name'
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
Results = KeyValueName.schema do
|
23
|
+
folder :simulation do
|
24
|
+
key :seed, type: Integer, format: '%06d'
|
25
|
+
key :algorithm, type: Symbol
|
26
|
+
key :alpha, type: Float
|
27
|
+
|
28
|
+
file :stats, :csv
|
29
|
+
end
|
25
30
|
end
|
26
31
|
|
27
|
-
|
32
|
+
results = Results.new(root: 'data/results')
|
33
|
+
|
34
|
+
sim = results.simulation.new(
|
28
35
|
seed: 123,
|
29
36
|
algorithm: :reticulating_splines,
|
30
37
|
alpha: 42.1)
|
31
38
|
|
32
|
-
|
33
|
-
# => seed-
|
39
|
+
sim.to_s
|
40
|
+
# => "data/results/simulation-seed-000123.algorithm-reticulating_splines.alpha-42.1"
|
41
|
+
|
42
|
+
sim.stats_csv.to_s
|
43
|
+
# => "data/results/simulation-seed-000123.algorithm-reticulating_splines.alpha-42.1/stats.csv"
|
44
|
+
|
45
|
+
# Pretend we've run a simulation and written the results...
|
46
|
+
sim.stats_csv.touch!
|
34
47
|
|
35
|
-
|
36
|
-
|
48
|
+
# List the results
|
49
|
+
results.simulation.all
|
50
|
+
# => [#<struct Results::Simulation seed=123, algorithm=:reticulating_splines, alpha=42.1>]
|
37
51
|
|
38
|
-
|
39
|
-
|
40
|
-
# => [#<struct ResultName seed=123, algorithm=:reticulating_splines, alpha=42.1>]
|
52
|
+
results.simulation.all.map(&:stats_csv).map(&:to_s)
|
53
|
+
# => ["data/results/simulation-seed-000123.algorithm-reticulating_splines.alpha-42.1/stats.csv"]
|
41
54
|
```
|
42
55
|
|
43
56
|
## INSTALLATION
|
@@ -50,7 +63,7 @@ gem install key_value_name
|
|
50
63
|
|
51
64
|
(The MIT License)
|
52
65
|
|
53
|
-
Copyright (c) 2017 John Lees-Miller
|
66
|
+
Copyright (c) 2017-2025 John Lees-Miller
|
54
67
|
|
55
68
|
Permission is hereby granted, free of charge, to any person obtaining
|
56
69
|
a copy of this software and associated documentation files (the
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# TODO
|
6
|
+
#
|
7
|
+
module ContainerBuilder
|
8
|
+
def file(...)
|
9
|
+
@builders << FileBuilder.new(...)
|
10
|
+
end
|
11
|
+
|
12
|
+
def folder(...)
|
13
|
+
@builders << FolderBuilder.new(...)
|
14
|
+
end
|
15
|
+
|
16
|
+
def extend_with_builders(klass)
|
17
|
+
@builders.each do |builder|
|
18
|
+
child_class = builder.build
|
19
|
+
klass.const_set(builder.class_name, child_class)
|
20
|
+
if builder.singular?
|
21
|
+
build_singular(builder, klass, child_class)
|
22
|
+
else
|
23
|
+
build_collection(builder, klass, child_class)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
klass
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_singular(builder, klass, child_class)
|
32
|
+
klass.class_eval do
|
33
|
+
define_method(builder.name) do
|
34
|
+
child = child_class.new
|
35
|
+
child.parent = self
|
36
|
+
child
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_collection(builder, klass, child_class)
|
42
|
+
klass.class_eval do
|
43
|
+
define_method(builder.name) do
|
44
|
+
Collection.new(child_class, self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# Build a file KeyValueName.
|
6
|
+
#
|
7
|
+
class FileBuilder < KeyValueBuilder
|
8
|
+
def initialize(name, *extension, class_name: nil, &block)
|
9
|
+
KeyValueName.check_symbol(name) if name
|
10
|
+
@name = name
|
11
|
+
@extension = extension
|
12
|
+
@class_name = class_name
|
13
|
+
super(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def extension(extension)
|
17
|
+
raise 'extension already set' if @extension.any?
|
18
|
+
@extension = Array(extension)
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
name_parts.join('_').to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_name
|
26
|
+
@class_name || default_class_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def build
|
30
|
+
klass = super
|
31
|
+
klass.class_eval do
|
32
|
+
include FileName::InstanceMethods
|
33
|
+
|
34
|
+
class <<self
|
35
|
+
include FileName::ClassMethods
|
36
|
+
end
|
37
|
+
end
|
38
|
+
klass.key_value_name_spec = make_spec
|
39
|
+
klass
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def name_parts
|
45
|
+
[@name] + @extension
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_class_name
|
49
|
+
name_parts.map { |part| KeyValueName.camelize(part) }.join('')
|
50
|
+
end
|
51
|
+
|
52
|
+
def make_spec
|
53
|
+
prefix = @name
|
54
|
+
prefix = "#{prefix}#{KEY_VALUE_SEPARATOR}" if prefix && @marshalers.any?
|
55
|
+
suffix = ".#{@extension.join('.')}" if @extension.any?
|
56
|
+
Spec.new(@marshalers, prefix, suffix)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# Build a folder KeyValueName.
|
6
|
+
#
|
7
|
+
class FolderBuilder < KeyValueBuilder
|
8
|
+
include ContainerBuilder
|
9
|
+
|
10
|
+
def initialize(name, class_name: nil, &block)
|
11
|
+
KeyValueName.check_symbol(name)
|
12
|
+
@name = name
|
13
|
+
@class_name = class_name
|
14
|
+
@builders = []
|
15
|
+
super(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :name
|
19
|
+
|
20
|
+
def class_name
|
21
|
+
@class_name || KeyValueName.camelize(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def build
|
25
|
+
klass = super
|
26
|
+
klass.class_eval do
|
27
|
+
include FolderName::InstanceMethods
|
28
|
+
|
29
|
+
class <<self
|
30
|
+
include FolderName::ClassMethods
|
31
|
+
end
|
32
|
+
end
|
33
|
+
klass.key_value_name_spec = make_spec
|
34
|
+
extend_with_builders(klass)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def make_spec
|
40
|
+
prefix = @name
|
41
|
+
prefix = "#{prefix}#{KEY_VALUE_SEPARATOR}" if prefix && @marshalers.any?
|
42
|
+
Spec.new(@marshalers, prefix)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# Yield-based Domain-Specific Language (DSL) to build a `KeyValueName`.
|
6
|
+
#
|
7
|
+
class KeyValueBuilder
|
8
|
+
def initialize(&block)
|
9
|
+
@marshalers = {}
|
10
|
+
instance_eval(&block) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def singular?
|
14
|
+
@marshalers.none?
|
15
|
+
end
|
16
|
+
|
17
|
+
def include_keys(key_value_name_klass)
|
18
|
+
spec = key_value_name_klass.key_value_name_spec
|
19
|
+
spec.marshalers.each do |name, marshaler|
|
20
|
+
check_no_existing_marshaler(name)
|
21
|
+
@marshalers[name] = marshaler
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def key(name, type:, **kwargs)
|
26
|
+
KeyValueName.check_symbol(name)
|
27
|
+
KeyValueName.check_marshaler(type)
|
28
|
+
check_no_existing_marshaler(name)
|
29
|
+
@marshalers[name] = MARSHALERS[type].new(**kwargs)
|
30
|
+
end
|
31
|
+
|
32
|
+
def build
|
33
|
+
struct_args = @marshalers.any? ? @marshalers.keys : [nil]
|
34
|
+
Struct.new(*struct_args, keyword_init: true)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def check_no_existing_marshaler(key)
|
40
|
+
raise ArgumentError, "already have key: #{key}" if @marshalers.key?(key)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# The root builder. Can contain files and folders only.
|
6
|
+
#
|
7
|
+
class SchemaBuilder
|
8
|
+
include ContainerBuilder
|
9
|
+
|
10
|
+
def initialize(&block)
|
11
|
+
@builders = []
|
12
|
+
instance_eval(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
extend_with_builders(Class.new(Schema))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# A collection of KeyValueNames.
|
6
|
+
#
|
7
|
+
class Collection
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(klass, parent)
|
11
|
+
@klass = klass
|
12
|
+
@parent = parent
|
13
|
+
end
|
14
|
+
|
15
|
+
def new(*args)
|
16
|
+
object = @klass.new(*args)
|
17
|
+
object.parent = @parent
|
18
|
+
object
|
19
|
+
end
|
20
|
+
|
21
|
+
def each(&block)
|
22
|
+
all.each(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def all
|
26
|
+
@klass.glob(@parent).sort
|
27
|
+
end
|
28
|
+
|
29
|
+
def where(**kwargs)
|
30
|
+
all.select do |name|
|
31
|
+
kwargs.all? do |key, value|
|
32
|
+
name[key] == value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_by(**kwargs)
|
38
|
+
where(**kwargs).first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -5,19 +5,22 @@ module KeyValueName
|
|
5
5
|
# A Marshaler handles conversion of typed values to and from strings.
|
6
6
|
#
|
7
7
|
class MarshalerBase
|
8
|
-
def initialize(**kwargs)
|
9
|
-
end
|
8
|
+
def initialize(**kwargs) end
|
10
9
|
|
11
10
|
def matcher
|
12
11
|
raise NotImplementedError
|
13
12
|
end
|
14
13
|
|
15
|
-
def
|
14
|
+
def parse(_string)
|
16
15
|
raise NotImplementedError
|
17
16
|
end
|
18
17
|
|
19
|
-
def
|
18
|
+
def generate(_value)
|
20
19
|
raise NotImplementedError
|
21
20
|
end
|
21
|
+
|
22
|
+
def to_comparable(value)
|
23
|
+
value
|
24
|
+
end
|
22
25
|
end
|
23
26
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# Read and write a boolean flag.
|
6
|
+
#
|
7
|
+
class BooleanMarshaler < MarshalerBase
|
8
|
+
def matcher
|
9
|
+
/true|false/i
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(string)
|
13
|
+
string == 'true'
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate(value)
|
17
|
+
value ? 'true' : 'false'
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_comparable(value)
|
21
|
+
value ? 1 : 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# Read and write float types.
|
6
|
+
#
|
7
|
+
class FloatMarshaler < MarshalerBase
|
8
|
+
def initialize(format: '%g')
|
9
|
+
@format_string = format
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :format_string
|
13
|
+
|
14
|
+
def matcher
|
15
|
+
/[-+]?[0-9]*\.?[0-9]+(?:e[-+]?[0-9]+)?/i
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(string)
|
19
|
+
string.to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate(value)
|
23
|
+
format(format_string, value.to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KeyValueName
|
4
|
+
#
|
5
|
+
# Read and write integer types. Positive binary, octal and hexadecimal numbers
|
6
|
+
# without prefixes are also supported. Padding with zeros is OK, but padding
|
7
|
+
# with spaces will not work.
|
8
|
+
#
|
9
|
+
class IntegerMarshaler < MarshalerBase
|
10
|
+
def initialize(format: '%d')
|
11
|
+
@format_string = format
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :format_string
|
15
|
+
|
16
|
+
def matcher
|
17
|
+
/[-+]?[0-9a-f]+/i
|
18
|
+
end
|
19
|
+
|
20
|
+
def base
|
21
|
+
case format_string
|
22
|
+
when /b\z/i then 2
|
23
|
+
when /o\z/i then 8
|
24
|
+
when /x\z/i then 16
|
25
|
+
else 10
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse(string)
|
30
|
+
string.to_i(base)
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate(value)
|
34
|
+
format(format_string, value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,11 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative 'marshalers/base'
|
3
|
-
require_relative 'marshalers/
|
4
|
+
require_relative 'marshalers/boolean_marshaler'
|
5
|
+
require_relative 'marshalers/float_marshaler'
|
6
|
+
require_relative 'marshalers/integer_marshaler'
|
4
7
|
require_relative 'marshalers/symbol_marshaler'
|
5
8
|
|
9
|
+
#
|
10
|
+
#
|
11
|
+
#
|
6
12
|
module KeyValueName
|
7
13
|
MARSHALERS = {
|
8
|
-
|
14
|
+
boolean: BooleanMarshaler,
|
15
|
+
Float => FloatMarshaler,
|
16
|
+
Integer => IntegerMarshaler,
|
9
17
|
Symbol => SymbolMarshaler
|
10
18
|
}.freeze
|
19
|
+
|
20
|
+
def self.check_marshaler(type)
|
21
|
+
raise ArgumentError, "bad type: #{type}" unless MARSHALERS.key?(type)
|
22
|
+
end
|
11
23
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module KeyValueName
|
6
|
+
module FileName
|
7
|
+
#
|
8
|
+
# Instance method mixin for a file.
|
9
|
+
#
|
10
|
+
module InstanceMethods
|
11
|
+
include Name::InstanceMethods
|
12
|
+
|
13
|
+
def exist?
|
14
|
+
File.exist?(to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Ensure that the parent folder exists.
|
19
|
+
#
|
20
|
+
# @return [self]
|
21
|
+
#
|
22
|
+
def mkdir!
|
23
|
+
parent.mkdir!
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def touch!
|
28
|
+
retried ||= false
|
29
|
+
FileUtils.touch(to_s)
|
30
|
+
self
|
31
|
+
rescue Errno::ENOENT
|
32
|
+
FileUtils.mkdir_p(File.dirname(to_s))
|
33
|
+
raise if retried
|
34
|
+
retried = true
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy!
|
39
|
+
FileUtils.rm_f(to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Class methods of the returned `KeyValueName` class.
|
45
|
+
#
|
46
|
+
module ClassMethods
|
47
|
+
include Name::ClassMethods
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module KeyValueName
|
6
|
+
module FolderName
|
7
|
+
#
|
8
|
+
# Instance methods of a KeyValueName for a folder.
|
9
|
+
#
|
10
|
+
module InstanceMethods
|
11
|
+
include Name::InstanceMethods
|
12
|
+
|
13
|
+
def exist?
|
14
|
+
Dir.exist?(to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def mkdir!
|
18
|
+
FileUtils.mkdir_p(to_s)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy!
|
23
|
+
FileUtils.rm_rf(to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Class methods of the returned `KeyValueName` class.
|
29
|
+
#
|
30
|
+
module ClassMethods
|
31
|
+
include Name::ClassMethods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module KeyValueName
|
6
|
+
module Name
|
7
|
+
#
|
8
|
+
# Instance method mixin for a KeyValueName.
|
9
|
+
#
|
10
|
+
module InstanceMethods
|
11
|
+
attr_accessor :parent
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
result = self.class.key_value_name_spec.generate(self)
|
15
|
+
if parent
|
16
|
+
File.join(parent.to_s, result)
|
17
|
+
else
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def <=>(other)
|
23
|
+
self.class.key_value_name_spec.compare(self, other)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Class methods of the returned `KeyValueName` class.
|
29
|
+
#
|
30
|
+
module ClassMethods
|
31
|
+
attr_accessor :key_value_name_spec
|
32
|
+
|
33
|
+
def parse_to_hash(name)
|
34
|
+
key_value_name_spec.parse(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse(name)
|
38
|
+
new(parse_to_hash(name))
|
39
|
+
end
|
40
|
+
|
41
|
+
def glob(parent)
|
42
|
+
Dir.glob(File.join(parent.to_s, key_value_name_spec.glob)).map do |name|
|
43
|
+
basename = File.basename(name)
|
44
|
+
next unless key_value_name_spec.matches?(basename)
|
45
|
+
name = parse(basename)
|
46
|
+
name.parent = parent
|
47
|
+
name
|
48
|
+
end.compact
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module KeyValueName
|
6
|
+
#
|
7
|
+
# Base class for user-defined schemas.
|
8
|
+
#
|
9
|
+
class Schema
|
10
|
+
def initialize(root:)
|
11
|
+
@root = root
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@root
|
16
|
+
end
|
17
|
+
|
18
|
+
def parent
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def mkdir!
|
23
|
+
Dir.mkdir_p(to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|