cuprum 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +13 -0
- data/DEVELOPMENT.md +17 -0
- data/LICENSE +22 -0
- data/README.md +125 -0
- data/lib/cuprum.rb +8 -0
- data/lib/cuprum/function.rb +122 -0
- data/lib/cuprum/result.rb +30 -0
- data/lib/cuprum/version.rb +54 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bea85b3d71780b913660e164e106f5d996c36218
|
4
|
+
data.tar.gz: 256bcdf2ac7b383c060c812d004f8b45d491e907
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0dbaddff3b7672ee4dc4cfd4ce06f27d958b2f816643aa2db13ee3704b8717e28f93c446130a104476bcda2991ac9963b2fc768b983a2d468cdc7b883545841
|
7
|
+
data.tar.gz: 8a465d71bcb45eb32b7cfb60a26e09e91be3736b0346c7b60f786d99371c09d8fa913c6ae2c9ad817a62cb837b15fae4c24d1545a00f05cd0e9dfc6a697de4ff
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.1.0
|
4
|
+
|
5
|
+
Initial version.
|
6
|
+
|
7
|
+
### Functions
|
8
|
+
|
9
|
+
Implemented `Cuprum::Function`. A functional object that encapsulates a business logic operation with a consistent interface and tracking of result value and status.
|
10
|
+
|
11
|
+
### Results
|
12
|
+
|
13
|
+
Implemented `Cuprum::Result`. A data object that encapsulates the result of calling a Cuprum function.
|
data/DEVELOPMENT.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Development
|
2
|
+
|
3
|
+
## Errors
|
4
|
+
|
5
|
+
- Dedicated errors object.
|
6
|
+
|
7
|
+
## Function
|
8
|
+
|
9
|
+
- Chaining with #then, #else, etc.
|
10
|
+
|
11
|
+
## Operation
|
12
|
+
|
13
|
+
- Wraps a function and tracks the last result object.
|
14
|
+
|
15
|
+
## Result
|
16
|
+
|
17
|
+
- Force success or failure status with #success!, #failure! methods.
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Rob Smith
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# Cuprum
|
2
|
+
|
3
|
+
A lightweight, functional-lite toolkit for making business logic a first-class
|
4
|
+
citizen of your application.
|
5
|
+
|
6
|
+
## Support
|
7
|
+
|
8
|
+
Cuprum is tested against Ruby 2.4.
|
9
|
+
|
10
|
+
## Contribute
|
11
|
+
|
12
|
+
### GitHub
|
13
|
+
|
14
|
+
The canonical repository for this gem is located at https://github.com/sleepingkingstudios/cuprum.
|
15
|
+
|
16
|
+
### A Note From The Developer
|
17
|
+
|
18
|
+
Hi, I'm Rob Smith, a Ruby Engineer and the developer of this library. I use these tools every day, but they're not just written for me. If you find this project helpful in your own work, or if you have any questions, suggestions or critiques, please feel free to get in touch! I can be reached on GitHub (see above, and feel encouraged to submit bug reports or merge requests there) or via email at merlin@sleepingkingstudios.com. I look forward to hearing from you!
|
19
|
+
|
20
|
+
## Features
|
21
|
+
|
22
|
+
### Functions
|
23
|
+
|
24
|
+
Functions are the core feature of Cuprum. In a nutshell, each Cuprum::Function is a functional object that encapsulates a business logic operation. A Function provides a consistent interface and tracking of result value and status. This minimizes boilerplate and allows for interchangeability between different implementations or strategies for managing your data and processes.
|
25
|
+
|
26
|
+
Each Function implements a `#call` method that wraps your defined business logic and returns an instance of Cuprum::Result. The result wraps the returned data (with the `#value` method), any `#errors` generated when running the Function, and the overall status with the `#success?` and `#failure` methods. For more details about Cuprum::Result, see below.
|
27
|
+
|
28
|
+
#### Defining With a Block
|
29
|
+
|
30
|
+
Functions can be used right out of the box by passing a block to the Cuprum::Function constructor, as follows:
|
31
|
+
|
32
|
+
# A Function with a block
|
33
|
+
double_function = Function.new { |int| 2 * int }
|
34
|
+
result = double_function.call(5)
|
35
|
+
|
36
|
+
result.value #=> 10
|
37
|
+
|
38
|
+
The constructor block will be called each time `Function#call` is executed, and will be passed all of the arguments given to `#call`. You can even define a block parameter, which will be passed along to the constructor block when `#call` is called with a block argument.
|
39
|
+
|
40
|
+
#### Defining With a Subclass
|
41
|
+
|
42
|
+
Larger applications will want to create Function subclasses that encapsulate their business logic in a reusable, composable fashion. The implementation for each subclass is handled by the `#process` private method. If a subclass or its ancestors does not implement `#process`, a `Cuprum::Function::NotImplementedError` will be raised.
|
43
|
+
|
44
|
+
# A Function subclass
|
45
|
+
class MultiplyFunction
|
46
|
+
def initialize multiplier
|
47
|
+
@multiplier = multiplier
|
48
|
+
end # constructor
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def process int
|
53
|
+
int * @multiplier
|
54
|
+
end # method process
|
55
|
+
end # class
|
56
|
+
|
57
|
+
triple_function = MultiplyFunction.new(3)
|
58
|
+
result = triple_function.call(5)
|
59
|
+
|
60
|
+
result.value #=> 15
|
61
|
+
|
62
|
+
As with the block syntax, a Function whose implementation is defined via the `#process` method will call `#process` each time that `#call` is executed, and will pass all arguments from `#call` on to `#process`. The value returned by `#process` will be assigned to the result `#value`.
|
63
|
+
|
64
|
+
#### Success, Failure, and Errors
|
65
|
+
|
66
|
+
Whether defined with a block or in the `#process` method, the Function implementation can access an `#errors` object while in the `#call` method. Any errors added to the errors object will be exposed by the `#errors` method on the result object.
|
67
|
+
|
68
|
+
# A Function with errors
|
69
|
+
class DivideFunction
|
70
|
+
def initialize divisor
|
71
|
+
@divisor = divisor
|
72
|
+
end # constructor
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def process int
|
77
|
+
if @divisor.zero?
|
78
|
+
errors << 'errors.messages.divide_by_zero'
|
79
|
+
|
80
|
+
return
|
81
|
+
end # if
|
82
|
+
|
83
|
+
int / @divisor
|
84
|
+
end # method process
|
85
|
+
end # class
|
86
|
+
|
87
|
+
In addition, the result object defines `#success?` and `#failure?` predicates. If the result has no errors, then `#success?` will return true and `#failure?` will return false.
|
88
|
+
|
89
|
+
halve_function = DivideFunction.new(2)
|
90
|
+
result = halve_function.call(10)
|
91
|
+
|
92
|
+
result.errors #=> []
|
93
|
+
result.success? #=> true
|
94
|
+
result.failure? #=> false
|
95
|
+
result.value #=> 5
|
96
|
+
|
97
|
+
If the result does have errors, `#success?` will return false and `#failure?` will return true.
|
98
|
+
|
99
|
+
function_with_errors = DivideFunction.new(0)
|
100
|
+
result = function_with_errors.call(10)
|
101
|
+
|
102
|
+
result.errors #=> ['errors.messages.divide_by_zero']
|
103
|
+
result.success? #=> false
|
104
|
+
result.failure? #=> true
|
105
|
+
result.value #=> nil
|
106
|
+
|
107
|
+
### Results
|
108
|
+
|
109
|
+
A Cuprum::Result is a data object that encapsulates the result of calling a Cuprum function - the returned value, the success or failure status, and any errors generated by the function. It defines the following methods:
|
110
|
+
|
111
|
+
#### `#value`
|
112
|
+
|
113
|
+
The value returned by the function. For example, for an increment function that added 1 to a given integer, the `#value` of the result object would be the incremented integer.
|
114
|
+
|
115
|
+
#### `#errors`
|
116
|
+
|
117
|
+
The errors generated by the function, or an empty array if no errors were generated.
|
118
|
+
|
119
|
+
#### `#success?`
|
120
|
+
|
121
|
+
True if the function did not generate any errors, otherwise false.
|
122
|
+
|
123
|
+
#### `#failure?`
|
124
|
+
|
125
|
+
True if the function generated one or more errors, otherwise false.
|
data/lib/cuprum.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# lib/cuprum/function.rb
|
2
|
+
|
3
|
+
require 'cuprum/result'
|
4
|
+
|
5
|
+
module Cuprum
|
6
|
+
# Functional object that encapsulates a business logic operation with a
|
7
|
+
# consistent interface and tracking of result value and status.
|
8
|
+
#
|
9
|
+
# A Function can be defined either by passing a block to the constructor, or
|
10
|
+
# by defining a subclass of Function and implementing the #process method.
|
11
|
+
#
|
12
|
+
# @example A Function with a block
|
13
|
+
# double_function = Function.new { |int| 2 * int }
|
14
|
+
# result = double_function.call(5)
|
15
|
+
#
|
16
|
+
# result.value #=> 10
|
17
|
+
#
|
18
|
+
# @example A Function subclass
|
19
|
+
# class MultiplyFunction
|
20
|
+
# def initialize multiplier
|
21
|
+
# @multiplier = multiplier
|
22
|
+
# end # constructor
|
23
|
+
#
|
24
|
+
# private
|
25
|
+
#
|
26
|
+
# def process int
|
27
|
+
# int * @multiplier
|
28
|
+
# end # method process
|
29
|
+
# end # class
|
30
|
+
#
|
31
|
+
# triple_function = MultiplyFunction.new(3)
|
32
|
+
# result = triple_function.call(5)
|
33
|
+
#
|
34
|
+
# result.value #=> 15
|
35
|
+
#
|
36
|
+
# @example A Function with errors
|
37
|
+
# class DivideFunction
|
38
|
+
# def initialize divisor
|
39
|
+
# @divisor = divisor
|
40
|
+
# end # constructor
|
41
|
+
#
|
42
|
+
# private
|
43
|
+
#
|
44
|
+
# def process int
|
45
|
+
# if @divisor.zero?
|
46
|
+
# errors << 'errors.messages.divide_by_zero'
|
47
|
+
#
|
48
|
+
# return
|
49
|
+
# end # if
|
50
|
+
#
|
51
|
+
# int / @divisor
|
52
|
+
# end # method process
|
53
|
+
# end # class
|
54
|
+
#
|
55
|
+
# halve_function = DivideFunction.new(2)
|
56
|
+
# result = halve_function.call(10)
|
57
|
+
#
|
58
|
+
# result.errors #=> []
|
59
|
+
# result.value #=> 5
|
60
|
+
#
|
61
|
+
# function_with_errors = DivideFunction.new(0)
|
62
|
+
# result = function_with_errors.call(10)
|
63
|
+
#
|
64
|
+
# result.errors #=> ['errors.messages.divide_by_zero']
|
65
|
+
# result.value #=> nil
|
66
|
+
class Function
|
67
|
+
# Error class for calling a Function that was not given a definition block
|
68
|
+
# or have a #process method defined.
|
69
|
+
class NotImplementedError < StandardError
|
70
|
+
# Error message for a NotImplementedError.
|
71
|
+
DEFAULT_MESSAGE = 'no implementation defined for function'.freeze
|
72
|
+
|
73
|
+
def initialize message = nil
|
74
|
+
super(message || DEFAULT_MESSAGE)
|
75
|
+
end # constructor
|
76
|
+
end # class
|
77
|
+
|
78
|
+
# Returns a new instance of Cuprum::Function.
|
79
|
+
#
|
80
|
+
# @yield [*arguments, **keywords, &block] If a block is given, the
|
81
|
+
# #call method will wrap the block and set the result #value to the return
|
82
|
+
# value of the block. This overrides the implementation in #process, if
|
83
|
+
# any.
|
84
|
+
def initialize &implementation
|
85
|
+
define_singleton_method :process, &implementation if implementation
|
86
|
+
end # method initialize
|
87
|
+
|
88
|
+
# @overload call(*arguments, *keywords, &block)
|
89
|
+
# Executes the logic encoded in the constructor block, or the #process
|
90
|
+
# method if no block was passed to the constructor.
|
91
|
+
#
|
92
|
+
# @param arguments [Array] Arguments to be passed to the implementation.
|
93
|
+
#
|
94
|
+
# @param keywords [Hash] Keywords to be passed to the implementation.
|
95
|
+
#
|
96
|
+
# @return [Cuprum::Result] The result object for the function.
|
97
|
+
#
|
98
|
+
# @yield If a block argument is given, it will be passed to the
|
99
|
+
# implementation.
|
100
|
+
#
|
101
|
+
# @raise [NotImplementedError] unless a block was passed to the
|
102
|
+
# constructor or the #process method was overriden by a Function
|
103
|
+
# subclass.
|
104
|
+
def call *args, &block
|
105
|
+
Cuprum::Result.new.tap do |result|
|
106
|
+
@errors = result.errors
|
107
|
+
|
108
|
+
result.value = process(*args, &block)
|
109
|
+
|
110
|
+
@errors = nil
|
111
|
+
end # tap
|
112
|
+
end # method call
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
attr_reader :errors
|
117
|
+
|
118
|
+
def process *_args
|
119
|
+
raise NotImplementedError, nil, caller(1..-1)
|
120
|
+
end # method process
|
121
|
+
end # class
|
122
|
+
end # module
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'cuprum'
|
2
|
+
|
3
|
+
module Cuprum
|
4
|
+
# Data object that encapsulates the result of calling a Cuprum function or
|
5
|
+
# operation.
|
6
|
+
class Result
|
7
|
+
# @return [Object] the value returned by calling the function.
|
8
|
+
attr_accessor :value
|
9
|
+
|
10
|
+
attr_writer :errors
|
11
|
+
|
12
|
+
# @return [Array] the errors (if any) generated when the function was
|
13
|
+
# called.
|
14
|
+
def errors
|
15
|
+
@errors ||= []
|
16
|
+
end # method errors
|
17
|
+
|
18
|
+
# @return [Boolean] false if the function did not generate any errors,
|
19
|
+
# otherwise true.
|
20
|
+
def failure?
|
21
|
+
!errors.empty?
|
22
|
+
end # method failure?
|
23
|
+
|
24
|
+
# @return [Boolean] true if the function did not generate any errors,
|
25
|
+
# otherwise false.
|
26
|
+
def success?
|
27
|
+
errors.empty?
|
28
|
+
end # method success?
|
29
|
+
end # class
|
30
|
+
end # module
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Cuprum
|
2
|
+
# @api private
|
3
|
+
#
|
4
|
+
# The current version of the gem.
|
5
|
+
#
|
6
|
+
# @see http://semver.org/
|
7
|
+
module Version
|
8
|
+
# Major version.
|
9
|
+
MAJOR = 0
|
10
|
+
# Minor version.
|
11
|
+
MINOR = 1
|
12
|
+
# Patch version.
|
13
|
+
PATCH = 0
|
14
|
+
# Prerelease version.
|
15
|
+
PRERELEASE = nil
|
16
|
+
# Build metadata.
|
17
|
+
BUILD = nil
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Generates the gem version string from the Version constants.
|
21
|
+
#
|
22
|
+
# Inlined here because dependencies may not be loaded when processing a
|
23
|
+
# gemspec, which results in the user being unable to install the gem for
|
24
|
+
# the first time.
|
25
|
+
#
|
26
|
+
# @see SleepingKingStudios::Tools::SemanticVersion#to_gem_version
|
27
|
+
def to_gem_version
|
28
|
+
str = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
29
|
+
|
30
|
+
prerelease = value_of(:PRERELEASE)
|
31
|
+
str << ".#{prerelease}" if prerelease
|
32
|
+
|
33
|
+
build = value_of(:BUILD)
|
34
|
+
str << ".#{build}" if build
|
35
|
+
|
36
|
+
str
|
37
|
+
end # class method to_version
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def value_of constant
|
42
|
+
return nil unless const_defined?(constant)
|
43
|
+
|
44
|
+
value = const_get(constant)
|
45
|
+
|
46
|
+
return nil if value.respond_to?(:empty?) && value.empty?
|
47
|
+
|
48
|
+
value
|
49
|
+
end # method value_of
|
50
|
+
end # eigenclass
|
51
|
+
end # module
|
52
|
+
|
53
|
+
VERSION = Version.to_gem_version
|
54
|
+
end # module
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuprum
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob "Merlin" Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-sleeping_king_studios
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.3.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.3.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.49.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.49.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.15.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.15.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.15'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.15'
|
83
|
+
description: A lightweight, functional-lite toolkit for making business logic a first-class
|
84
|
+
citizen of your application.
|
85
|
+
email:
|
86
|
+
- merlin@sleepingkingstudios.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- CHANGELOG.md
|
92
|
+
- DEVELOPMENT.md
|
93
|
+
- LICENSE
|
94
|
+
- README.md
|
95
|
+
- lib/cuprum.rb
|
96
|
+
- lib/cuprum/function.rb
|
97
|
+
- lib/cuprum/result.rb
|
98
|
+
- lib/cuprum/version.rb
|
99
|
+
homepage: http://sleepingkingstudios.com
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.6.11
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: A lightweight, functional-lite toolkit.
|
123
|
+
test_files: []
|