functio 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/.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
|