furud 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.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Furud
4
+ Reference = Data.define(:sheet, :row, :column, :absolute_row, :absolute_column) do
5
+ def initialize(sheet: nil, row:, column:, absolute_row: false, absolute_column: false)
6
+ raise ArgumentError, "row and column must be positive" unless row.to_i.positive? && column.to_i.positive?
7
+
8
+ super(sheet: sheet&.to_s, row: Integer(row), column: Integer(column),
9
+ absolute_row: !!absolute_row, absolute_column: !!absolute_column)
10
+ end
11
+ end
12
+
13
+ Area = Data.define(:sheet, :top, :left, :bottom, :right) do
14
+ def initialize(sheet: nil, top:, left:, bottom:, right:)
15
+ values = [top, left, bottom, right].map { |value| Integer(value) }
16
+ raise ArgumentError, "area coordinates must be positive" unless values.all?(&:positive?)
17
+
18
+ top, left, bottom, right = values
19
+ super(sheet: sheet&.to_s, top: [top, bottom].min, left: [left, right].min,
20
+ bottom: [top, bottom].max, right: [left, right].max)
21
+ end
22
+
23
+ def include?(reference)
24
+ (sheet.nil? || reference.sheet == sheet) && reference.row.between?(top, bottom) &&
25
+ reference.column.between?(left, right)
26
+ end
27
+ end
28
+
29
+ ERROR_CODES = { div0: "#DIV/0!", value: "#VALUE!", ref: "#REF!", name: "#NAME?",
30
+ na: "#N/A", num: "#NUM!", cycle: "#CYCLE!", spill: "#SPILL!",
31
+ calc: "#CALC!" }.freeze
32
+ ErrorValue = Data.define(:code) do
33
+ def initialize(code:)
34
+ super(code: code.to_sym)
35
+ end
36
+
37
+ def to_s = ERROR_CODES.fetch(code, "#VALUE!")
38
+ end
39
+
40
+ Node = Data.define(:type, :value, :children) do
41
+ def initialize(type:, value: nil, children: [])
42
+ super(type: type.to_sym, value: value, children: children.freeze)
43
+ end
44
+ end
45
+
46
+ Adjustment = Data.define(:type, :sheet, :at, :count) do
47
+ def initialize(type:, sheet: nil, at:, count: 1)
48
+ type = type.to_sym
49
+ raise ArgumentError, "type must be insert_rows, delete_rows, insert_columns, or delete_columns" unless
50
+ %i[insert_rows delete_rows insert_columns delete_columns].include?(type)
51
+ raise ArgumentError, "at and count must be positive" unless Integer(at).positive? && Integer(count).positive?
52
+
53
+ super(type: type, sheet: sheet&.to_s, at: Integer(at), count: Integer(count))
54
+ end
55
+ end
56
+
57
+ ArrayValue = Data.define(:rows) do
58
+ def initialize(rows:)
59
+ rows = rows.map { |row| row.dup.freeze }.freeze
60
+ raise ArgumentError, "array must not be empty" if rows.empty? || rows.any?(&:empty?)
61
+ raise ArgumentError, "array rows must have equal length" unless rows.map(&:length).uniq.one?
62
+
63
+ super(rows: rows)
64
+ end
65
+
66
+ def height = rows.length
67
+ def width = rows.first.length
68
+ def [](row, column) = rows[row][column]
69
+ end
70
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Furud
4
+ VERSION = "0.1.0"
5
+ end
data/lib/furud.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "furud/version"
4
+ require_relative "furud/types"
5
+ require_relative "furud/formula"
6
+ require_relative "furud/functions"
7
+ require_relative "furud/format"
8
+ require_relative "furud/engine"
9
+
10
+ module Furud
11
+ module Source; end
12
+
13
+ class Error < StandardError; end
14
+ class ParseError < Error; end
15
+ end
data/sig/furud.rbs ADDED
@@ -0,0 +1,128 @@
1
+ module Furud
2
+ VERSION: String
3
+ ERROR_CODES: Hash[Symbol, String]
4
+
5
+ class Error < StandardError
6
+ end
7
+
8
+ class ParseError < Error
9
+ end
10
+
11
+ class Reference
12
+ attr_reader sheet: String?
13
+ attr_reader row: Integer
14
+ attr_reader column: Integer
15
+ attr_reader absolute_row: bool
16
+ attr_reader absolute_column: bool
17
+ def initialize: (?sheet: String?, row: Integer, column: Integer, ?absolute_row: bool, ?absolute_column: bool) -> void
18
+ def with: (**untyped) -> Reference
19
+ end
20
+
21
+ class Area
22
+ attr_reader sheet: String?
23
+ attr_reader top: Integer
24
+ attr_reader left: Integer
25
+ attr_reader bottom: Integer
26
+ attr_reader right: Integer
27
+ def initialize: (?sheet: String?, top: Integer, left: Integer, bottom: Integer, right: Integer) -> void
28
+ def include?: (Reference) -> bool
29
+ end
30
+
31
+ class ErrorValue
32
+ attr_reader code: Symbol
33
+ def initialize: (code: Symbol) -> void
34
+ def to_s: () -> String
35
+ end
36
+
37
+ class Node
38
+ attr_reader type: Symbol
39
+ attr_reader value: untyped
40
+ attr_reader children: Array[Node]
41
+ def initialize: (type: Symbol, ?value: untyped, ?children: Array[Node]) -> void
42
+ end
43
+
44
+ class Adjustment
45
+ attr_reader type: Symbol
46
+ attr_reader sheet: String?
47
+ attr_reader at: Integer
48
+ attr_reader count: Integer
49
+ def initialize: (type: Symbol, ?sheet: String?, at: Integer, ?count: Integer) -> void
50
+ end
51
+
52
+ class ArrayValue
53
+ attr_reader rows: Array[Array[untyped]]
54
+ def initialize: (rows: Array[Array[untyped]]) -> void
55
+ def height: () -> Integer
56
+ def width: () -> Integer
57
+ def []: (Integer row, Integer column) -> untyped
58
+ end
59
+
60
+ module Formula
61
+ def self.parse: (String source, ?origin: Reference | Hash[Symbol, untyped]?) -> Node
62
+ def self.render: (Node ast, ?origin: Reference | Hash[Symbol, untyped]?) -> String
63
+ def self.references: (Node ast) -> Array[Reference | Area]
64
+ def self.translate: (Node ast, from: Reference | Hash[Symbol, untyped], to: Reference | Hash[Symbol, untyped]) -> Node
65
+ def self.adjust: (Node ast, (Adjustment | Hash[Symbol, untyped]) operation) -> Node
66
+ end
67
+
68
+ module Source
69
+ def value_at: (Reference) -> untyped
70
+ end
71
+
72
+ class Engine
73
+ attr_reader source: untyped
74
+ attr_reader locale: Symbol
75
+ def initialize: (?untyped source, ?functions: Functions::Registry, ?locale: Symbol, ?iterative: bool, ?max_iterations: Integer, ?epsilon: Numeric) -> void
76
+ def set: (Reference, untyped input) -> untyped
77
+ def clear: (Reference) -> nil
78
+ def value: (Reference) -> untyped
79
+ def formula: (Reference) -> String?
80
+ def recalculate: () -> Array[Reference]
81
+ def dirty: () -> Set[Reference]
82
+ def cycles: () -> Array[Array[Reference]]
83
+ def insert_rows: (String? sheet, Integer at, ?Integer count) -> self
84
+ def delete_rows: (String? sheet, Integer at, ?Integer count) -> self
85
+ def insert_columns: (String? sheet, Integer at, ?Integer count) -> self
86
+ def delete_columns: (String? sheet, Integer at, ?Integer count) -> self
87
+ def define_name: (String name, (Reference | Area) area) -> (Reference | Area)
88
+ def precedents: (Reference) -> Array[Reference | Area]
89
+ def dependents: (Reference) -> Array[Reference]
90
+ end
91
+
92
+ module Functions
93
+ class Entry
94
+ attr_reader name: String
95
+ attr_reader arity: untyped
96
+ attr_reader volatile: bool
97
+ def call: (*untyped) -> untyped
98
+ def accepts?: (Integer) -> bool
99
+ end
100
+
101
+ class Registry
102
+ include Enumerable[Entry]
103
+ def register: (String name, arity: Integer | Range[Integer] | Array[Integer], ?volatile: bool) { (*untyped) -> untyped } -> self
104
+ def []: (String name) -> Entry?
105
+ def names: () -> Array[String]
106
+ def each: () { (Entry) -> void } -> self
107
+ def size: () -> Integer
108
+ def call: (String name, *untyped args) -> untyped
109
+ end
110
+
111
+ def self.standard: () -> Registry
112
+ end
113
+
114
+ module Format
115
+ class Section
116
+ attr_reader pattern: String
117
+ attr_reader condition: [String, Numeric]?
118
+ attr_reader color: Symbol?
119
+ end
120
+ class Spec
121
+ attr_reader sections: Array[Section]
122
+ attr_reader locale: Symbol
123
+ end
124
+ def self.parse: (String pattern, ?locale: Symbol) -> Spec
125
+ def self.apply: (untyped value, Spec spec) -> [String, Hash[Symbol, untyped]]
126
+ def self.infer: (String text, locale: Symbol) -> [untyped, String]
127
+ end
128
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: furud
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Parses spreadsheet formulas, evaluates 192 standard functions, tracks
13
+ dependencies, recalculates incrementally, and formats values.
14
+ email:
15
+ - t.yudai92@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - LICENSE.txt
22
+ - README.md
23
+ - lib/furud.rb
24
+ - lib/furud/engine.rb
25
+ - lib/furud/format.rb
26
+ - lib/furud/formula.rb
27
+ - lib/furud/functions.rb
28
+ - lib/furud/types.rb
29
+ - lib/furud/version.rb
30
+ - sig/furud.rbs
31
+ homepage: https://github.com/noxdea/furud
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ allowed_push_host: https://rubygems.org
36
+ homepage_uri: https://github.com/noxdea/furud
37
+ source_code_uri: https://github.com/noxdea/furud/tree/main
38
+ changelog_uri: https://github.com/noxdea/furud/blob/main/CHANGELOG.md
39
+ rubygems_mfa_required: 'true'
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 3.2.0
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 4.0.16
55
+ specification_version: 4
56
+ summary: Dependency-free spreadsheet formula and recalculation engine
57
+ test_files: []