functio 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 +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +9 -0
- data/COPYING +674 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +79 -0
- data/README.md +109 -0
- data/Rakefile +47 -0
- data/bin/fn +28 -0
- data/features/expression_evaluation.feature +58 -0
- data/features/function_creation.feature +74 -0
- data/features/function_deletion.feature +37 -0
- data/features/function_list.feature +35 -0
- data/features/function_use.feature +56 -0
- data/features/help_option.feature +13 -0
- data/features/step_definitions/steps.rb +36 -0
- data/features/support/env.rb +41 -0
- data/features/support/functio_world.rb +45 -0
- data/features/version_option.feature +19 -0
- data/functio.gemspec +43 -0
- data/lib/functio.rb +24 -0
- data/lib/functio/data_storage.rb +90 -0
- data/lib/functio/errors.rb +47 -0
- data/lib/functio/expression.rb +61 -0
- data/lib/functio/formatted_num.rb +39 -0
- data/lib/functio/functio_cli.rb +111 -0
- data/lib/functio/function.rb +104 -0
- data/lib/functio/function_repository.rb +73 -0
- data/lib/functio/version.rb +23 -0
- data/test/doubles/expression_double.rb +33 -0
- data/test/doubles/function_double.rb +34 -0
- data/test/doubles/storage_double.rb +36 -0
- data/test/doubles/test_expression_double.rb +30 -0
- data/test/doubles/test_function_double.rb +30 -0
- data/test/doubles/test_storage_double.rb +30 -0
- data/test/expression_interface_test.rb +30 -0
- data/test/interface_test_helper.rb +25 -0
- data/test/license_test_helper.rb +49 -0
- data/test/storable_interface_test.rb +30 -0
- data/test/storage_interface_test.rb +30 -0
- data/test/test_data_storage.rb +127 -0
- data/test/test_expression.rb +89 -0
- data/test/test_formatted_num.rb +56 -0
- data/test/test_function.rb +142 -0
- data/test/test_function_repository.rb +97 -0
- data/test/test_helper.rb +22 -0
- data/test/test_licenses_compatibility.rb +29 -0
- metadata +140 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
require 'dentaku'
|
21
|
+
require 'functio/errors'
|
22
|
+
|
23
|
+
module Functio
|
24
|
+
# Represents a mathematical expression.
|
25
|
+
class Expression
|
26
|
+
# String representation of Expression instance.
|
27
|
+
attr_reader :expression
|
28
|
+
|
29
|
+
# The calculator instance used for expression evaluation.
|
30
|
+
attr_reader :calculator
|
31
|
+
|
32
|
+
# Constructs an Expression instance passing in an +expression+ String.
|
33
|
+
# Raises InvalidExpressionError if +expression+ is invalid. Raises
|
34
|
+
# DivisionByZeroError if +expression+ contains a division by zero.
|
35
|
+
def initialize(expression)
|
36
|
+
raise RuntimeError if expression.strip.empty?
|
37
|
+
@calculator = Dentaku::Calculator.new
|
38
|
+
calculator.evaluate(expression)
|
39
|
+
@expression = expression
|
40
|
+
rescue Dentaku::ParseError, RuntimeError
|
41
|
+
raise InvalidExpressionError, 'invalid expression'
|
42
|
+
rescue Dentaku::ZeroDivisionError
|
43
|
+
raise DivisionByZeroError
|
44
|
+
end
|
45
|
+
|
46
|
+
# Evaluates the expression passing in its +variables+ in a Hash which keys
|
47
|
+
# are the variables names and values the variables values. Raises
|
48
|
+
# DivisionByZeroError if a division by zero occurs. Returns +nil+ if no
|
49
|
+
# value is provided in +variables+ for some of the expression variables.
|
50
|
+
def evaluate(variables = {})
|
51
|
+
calculator.evaluate(expression, variables)
|
52
|
+
rescue Dentaku::ZeroDivisionError
|
53
|
+
raise DivisionByZeroError
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the variables of the expression in an Array of Strings.
|
57
|
+
def variables
|
58
|
+
calculator.dependencies(expression)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
module Functio
|
21
|
+
# Represents a number using a default format.
|
22
|
+
class FormattedNum
|
23
|
+
# Creates a FormattedNum instance from a +num_obj+.
|
24
|
+
def initialize(num_obj)
|
25
|
+
@num = num_obj
|
26
|
+
end
|
27
|
+
|
28
|
+
# Converts the FormattedNum to a String. The String is formatted using
|
29
|
+
# conventional floating point notation if the number is a float.
|
30
|
+
def to_s
|
31
|
+
case @num
|
32
|
+
when BigDecimal
|
33
|
+
@num.to_s('F')
|
34
|
+
else
|
35
|
+
@num.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
require 'thor'
|
21
|
+
require 'functio/errors'
|
22
|
+
require 'functio/expression'
|
23
|
+
require 'functio/formatted_num'
|
24
|
+
require 'functio/function'
|
25
|
+
require 'functio/function_repository'
|
26
|
+
require 'functio/version'
|
27
|
+
|
28
|
+
module Functio
|
29
|
+
# Functio command-line interface.
|
30
|
+
class FunctioCLI < Thor
|
31
|
+
package_name 'Functio'
|
32
|
+
|
33
|
+
map '--version' => :version
|
34
|
+
|
35
|
+
desc 'version', 'Show Functio version'
|
36
|
+
def version # :nodoc:
|
37
|
+
puts <<-EOS
|
38
|
+
Functio #{Functio::VERSION}
|
39
|
+
Copyright (C) 2016 Cassiano Rocha Kuplich
|
40
|
+
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
|
41
|
+
This is free software: you are free to change and redistribute it.
|
42
|
+
There is NO WARRANTY, to the extent permitted by law.
|
43
|
+
EOS
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'eval EXPRESSION', 'Evaluate EXPRESSION and return its result'
|
47
|
+
def eval(expr) # :nodoc:
|
48
|
+
result = Expression.new(expr).evaluate
|
49
|
+
abort "Error: expression can't have variables" unless result
|
50
|
+
puts FormattedNum.new(result)
|
51
|
+
rescue DivisionByZeroError
|
52
|
+
abort 'Error: division by zero'
|
53
|
+
rescue InvalidExpressionError
|
54
|
+
abort 'Error: invalid expression'
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'create NAME DEF',
|
58
|
+
'Create a function called NAME defined by DEF'
|
59
|
+
def create(name, definition) # :nodoc:
|
60
|
+
function = Function.build(name: name, definition: definition)
|
61
|
+
rescue DivisionByZeroError
|
62
|
+
abort "Invalid function: division by zero '#{definition}'"
|
63
|
+
rescue InvalidFunctionError => ex
|
64
|
+
abort "Invalid function: #{ex.message}"
|
65
|
+
else
|
66
|
+
abort "Error: a function '#{name}' already exists" unless
|
67
|
+
repository.add(function)
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'list', 'List all functions'
|
71
|
+
def list # :nodoc:
|
72
|
+
puts repository.all.map { |function| function_to_str(function) }
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'use FUNCTION [ARGS]', 'Use the function FUNCTION with ARGS'
|
76
|
+
def use(function_name, *args) # :nodoc:
|
77
|
+
function = repository.find(function_name)
|
78
|
+
abort "Error: function '#{function_name}' doesn't exist" unless function
|
79
|
+
begin
|
80
|
+
result = function.use(map_function_args(function.params, args))
|
81
|
+
abort 'Error: no value provided for some parameters' unless result
|
82
|
+
puts FormattedNum.new(result)
|
83
|
+
rescue DivisionByZeroError
|
84
|
+
abort 'Error: division by zero'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'delete FUNCTION', 'Delete the function called FUNCTION'
|
89
|
+
def delete(function_name) # :nodoc:
|
90
|
+
if repository.delete(function_name)
|
91
|
+
puts "Deleted function '#{function_name}'"
|
92
|
+
else
|
93
|
+
abort "Error: function '#{function_name}' doesn't exist"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def repository
|
100
|
+
@repository ||= FunctionRepository.new
|
101
|
+
end
|
102
|
+
|
103
|
+
def function_to_str(function)
|
104
|
+
"#{function.name} <#{function.params.join('> <')}>"
|
105
|
+
end
|
106
|
+
|
107
|
+
def map_function_args(params, args)
|
108
|
+
args.map.with_index { |arg, i| [params[i], arg] }.to_h
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
require 'functio/errors'
|
21
|
+
require 'functio/expression'
|
22
|
+
|
23
|
+
module Functio
|
24
|
+
# Represents a function.
|
25
|
+
class Function
|
26
|
+
# Maximum size of the function name.
|
27
|
+
MAX_NAME_SIZE = 20
|
28
|
+
|
29
|
+
# Pattern for valid function name format.
|
30
|
+
NAME_PATTERN = /\A[a-zA-Z][a-zA-Z0-9]*\z/
|
31
|
+
|
32
|
+
# Function name.
|
33
|
+
attr_reader :name
|
34
|
+
|
35
|
+
# Expression instance used for Function definition.
|
36
|
+
attr_reader :expression
|
37
|
+
|
38
|
+
# Builds a new Function instance by Function +attributes+. +attributes+ is a
|
39
|
+
# Hash containing +:name+ and +:definition+. Raises InvalidFunctionError if
|
40
|
+
# some of the +attributes+ are invalid. Raises DivisionByZeroError if
|
41
|
+
# +:definition+ contains a division by zero.
|
42
|
+
def self.build(attributes)
|
43
|
+
expression = Expression.new(attributes[:definition])
|
44
|
+
new(name: attributes[:name], expression: expression)
|
45
|
+
rescue InvalidExpressionError
|
46
|
+
raise InvalidFunctionError, 'definition must be a valid expression'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Constructs a Function instance. The parameter +args+ is a Hash containing
|
50
|
+
# the function +:name+ and an +:expression+ instance.
|
51
|
+
def initialize(args)
|
52
|
+
@name = validate_name(args[:name])
|
53
|
+
@expression = args[:expression]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Function definition.
|
57
|
+
def definition
|
58
|
+
expression.expression
|
59
|
+
end
|
60
|
+
|
61
|
+
# Function attributes.
|
62
|
+
def attributes
|
63
|
+
{ name: name, definition: definition }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the function input parameters in an Array of Strings.
|
67
|
+
def params
|
68
|
+
expression.variables
|
69
|
+
end
|
70
|
+
|
71
|
+
# Uses function with +args+ Hash. The keys of +args+ Hash are the input
|
72
|
+
# parameters and the values are the input arguments.
|
73
|
+
def use(args = {})
|
74
|
+
expression.evaluate(args)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Compares equality between Function instances. Returns +true+ if
|
78
|
+
# Function#attributes of this instance is equal to Function#attributes of
|
79
|
+
# +other+.
|
80
|
+
def ==(other)
|
81
|
+
attributes == other.attributes
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def validate_name(name)
|
87
|
+
raise InvalidFunctionError,
|
88
|
+
"name size can't be greater than #{MAX_NAME_SIZE} characters" unless
|
89
|
+
valid_name_size?(name)
|
90
|
+
raise InvalidFunctionError,
|
91
|
+
'name must be alphanumeric and begin with a letter' unless
|
92
|
+
valid_name_format?(name)
|
93
|
+
name
|
94
|
+
end
|
95
|
+
|
96
|
+
def valid_name_size?(name)
|
97
|
+
name.size <= MAX_NAME_SIZE
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_name_format?(name)
|
101
|
+
name =~ NAME_PATTERN
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
require 'functio/data_storage'
|
21
|
+
require 'functio/function'
|
22
|
+
|
23
|
+
module Functio
|
24
|
+
# Repository for function management.
|
25
|
+
class FunctionRepository
|
26
|
+
# Symbol with the name of the attribute that identifies a function instance.
|
27
|
+
attr_reader :id_attr
|
28
|
+
|
29
|
+
# Storage instance used for function persistence.
|
30
|
+
attr_reader :storage
|
31
|
+
|
32
|
+
# Class of the function instances this repository manages.
|
33
|
+
attr_reader :function_class
|
34
|
+
|
35
|
+
# Constructs a FunctionRepository instance passing in a +configs+ Hash. The
|
36
|
+
# +configs+ Hash can contain a +:storage+ instance, defaults to a
|
37
|
+
# DataStorage instance; a +:function_class+, defaults to Function; and an
|
38
|
+
# +:id_attr+ Symbol with a name of the attribute that identifies the
|
39
|
+
# function instances, defaults to +:name+.
|
40
|
+
def initialize(configs = {})
|
41
|
+
@storage = configs.fetch(:storage, DataStorage.new)
|
42
|
+
@function_class = configs.fetch(:function_class, Function)
|
43
|
+
@id_attr = configs.fetch(:id_attr, :name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds a +function+ instance to repository and returns +true+. It doesn't
|
47
|
+
# add the function and returns +false+ if its +id_attr+ already exists in
|
48
|
+
# the repository.
|
49
|
+
def add(function)
|
50
|
+
return false if find(function.attributes[id_attr])
|
51
|
+
storage.store(function.attributes)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Retrieves all function instances in the repository.
|
56
|
+
def all
|
57
|
+
storage.all.map { |record| function_class.build(record) }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Finds a function instance by its +id+ attribute. Returns +nil+ otherwise.
|
61
|
+
def find(id)
|
62
|
+
found = storage.find(id_attr => id)
|
63
|
+
function_class.build(found) if found
|
64
|
+
end
|
65
|
+
|
66
|
+
# Deletes a function instance from the repository by its +id+ attribute.
|
67
|
+
# Returns +true+ if the function instance is deleted successfully. Returns
|
68
|
+
# +false+ otherwise.
|
69
|
+
def delete(id)
|
70
|
+
storage.delete(id_attr => id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
module Functio
|
21
|
+
# Functio version
|
22
|
+
VERSION = '0.1.0'.freeze
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2016 Cassiano Rocha Kuplich
|
3
|
+
#
|
4
|
+
# This file is part of Functio.
|
5
|
+
#
|
6
|
+
# Functio is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Functio is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Functio. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#++
|
19
|
+
|
20
|
+
class ExpressionDouble # :nodoc:
|
21
|
+
attr_reader :expression
|
22
|
+
attr_reader :variables
|
23
|
+
|
24
|
+
def initialize(results = {})
|
25
|
+
@expression = results[:expression]
|
26
|
+
@variables = results[:variables]
|
27
|
+
@evaluate = results[:evaluate]
|
28
|
+
end
|
29
|
+
|
30
|
+
def evaluate(_args = {})
|
31
|
+
@evaluate.call
|
32
|
+
end
|
33
|
+
end
|