functio 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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