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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/CHANGELOG.md +9 -0
  4. data/COPYING +674 -0
  5. data/Gemfile +16 -0
  6. data/Gemfile.lock +79 -0
  7. data/README.md +109 -0
  8. data/Rakefile +47 -0
  9. data/bin/fn +28 -0
  10. data/features/expression_evaluation.feature +58 -0
  11. data/features/function_creation.feature +74 -0
  12. data/features/function_deletion.feature +37 -0
  13. data/features/function_list.feature +35 -0
  14. data/features/function_use.feature +56 -0
  15. data/features/help_option.feature +13 -0
  16. data/features/step_definitions/steps.rb +36 -0
  17. data/features/support/env.rb +41 -0
  18. data/features/support/functio_world.rb +45 -0
  19. data/features/version_option.feature +19 -0
  20. data/functio.gemspec +43 -0
  21. data/lib/functio.rb +24 -0
  22. data/lib/functio/data_storage.rb +90 -0
  23. data/lib/functio/errors.rb +47 -0
  24. data/lib/functio/expression.rb +61 -0
  25. data/lib/functio/formatted_num.rb +39 -0
  26. data/lib/functio/functio_cli.rb +111 -0
  27. data/lib/functio/function.rb +104 -0
  28. data/lib/functio/function_repository.rb +73 -0
  29. data/lib/functio/version.rb +23 -0
  30. data/test/doubles/expression_double.rb +33 -0
  31. data/test/doubles/function_double.rb +34 -0
  32. data/test/doubles/storage_double.rb +36 -0
  33. data/test/doubles/test_expression_double.rb +30 -0
  34. data/test/doubles/test_function_double.rb +30 -0
  35. data/test/doubles/test_storage_double.rb +30 -0
  36. data/test/expression_interface_test.rb +30 -0
  37. data/test/interface_test_helper.rb +25 -0
  38. data/test/license_test_helper.rb +49 -0
  39. data/test/storable_interface_test.rb +30 -0
  40. data/test/storage_interface_test.rb +30 -0
  41. data/test/test_data_storage.rb +127 -0
  42. data/test/test_expression.rb +89 -0
  43. data/test/test_formatted_num.rb +56 -0
  44. data/test/test_function.rb +142 -0
  45. data/test/test_function_repository.rb +97 -0
  46. data/test/test_helper.rb +22 -0
  47. data/test/test_licenses_compatibility.rb +29 -0
  48. 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