tdrb 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2e92d32f3edc5319cab952f4777c42c97170325cd9e19c94558dd9f6cf36d965
4
+ data.tar.gz: 86954cb680c46b56814d95511d52a371fcfb2062fb715cdf8b7addc1d426e31e
5
+ SHA512:
6
+ metadata.gz: 20e77bb2e93ddd65de9b9f72df08f19711152cdf47c38b9dc257b59b31508497bbb8b7ecec32259a62518929c489c67817e94d3115d7f40b274f33b77fd4739c
7
+ data.tar.gz: 13bbcf53226ef1afb1d2273c2ad4d8799fde2cea64f04d62b8d973477ed5bb4bf061c15a79be294048af58b25b0daba250035c2f5ef02b8683bef5779ddaf0b9
data/lib/tdrb/error.rb ADDED
@@ -0,0 +1,16 @@
1
+ module TDRB
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ParseError < Error
6
+ end
7
+
8
+ class TokenArgsError < Error
9
+ end
10
+
11
+ class TokenError < Error
12
+ end
13
+
14
+ class TokenTypeError < Error
15
+ end
16
+ end
data/lib/tdrb/lexer.rb ADDED
@@ -0,0 +1,83 @@
1
+ module TDRB
2
+ class Lexer
3
+ attr_reader :source, :tokens
4
+
5
+ def initialize(source:, state: -> (_) {})
6
+ @col = 0
7
+ @line = 0
8
+ @pos = 0
9
+ @source = source
10
+ @stack = []
11
+ @start = 0
12
+ @state = state
13
+ @tokens = []
14
+ end
15
+
16
+ def chars
17
+ @chars ||= source.grapheme_clusters
18
+ end
19
+
20
+ def current
21
+ chars[@start..]
22
+ .take(@pos - @start)
23
+ .join
24
+ end
25
+
26
+ def emit(token_type)
27
+ tokens << Token.new(literal: current, type: token_type)
28
+
29
+ @start = @pos
30
+ @stack = []
31
+ end
32
+
33
+ def ignore
34
+ @stack = []
35
+ @start = @pos
36
+ end
37
+
38
+ def next_char
39
+ return @stack << Token::EOF && Token::EOF if chars.count.pred == @pos
40
+
41
+ sub_source = source[@pos..]
42
+ @pos += 1
43
+
44
+ return @stack << Token::EOF && Token::EOF if sub_source.nil?
45
+
46
+ @stack << sub_source[0] && sub_source[0]
47
+ end
48
+
49
+ def peek
50
+ char = next_char
51
+ rewind
52
+
53
+ char
54
+ end
55
+
56
+ def pop_stack
57
+ @stack.pop || Token::EOF
58
+ end
59
+
60
+ def rewind
61
+ char = pop_stack
62
+ @pos -= 1 if char != Token::EOF
63
+ @pos = @start if (@pos < @start)
64
+ end
65
+
66
+ def run
67
+ while @state do
68
+ @state = @state.call(self)
69
+ end
70
+
71
+ true
72
+ end
73
+
74
+ def take(characters)
75
+ char = next_char
76
+ while characters.include?(char) do
77
+ char = next_char
78
+ end
79
+
80
+ rewind
81
+ end
82
+ end
83
+ end
data/lib/tdrb/parse.rb ADDED
@@ -0,0 +1,89 @@
1
+ require "time"
2
+
3
+ module TDRB
4
+ ## Returns Array<Result> to allow for multisession documents.
5
+ def self.parse(traindown:)
6
+ metakey_buf = nil
7
+ movement_meta = {}
8
+ movement_notes = []
9
+ result = nil
10
+ performance = nil
11
+
12
+ Parser
13
+ .new(traindown: traindown)
14
+ .tokens
15
+ .each_with_object([]) do |token, arr|
16
+ case token.type
17
+ when Token::DATETIME
18
+ arr << result if !result.nil?
19
+
20
+ occurred_at = safe_time(token.literal)
21
+ result = Result.new(occurred_at: occurred_at)
22
+ when Token::FAIL
23
+ fails = safe_float(token.literal)
24
+
25
+ (performance || Performance.new).fails = fails
26
+ when Token::LOAD
27
+ if performance.nil? || performance.load
28
+ result.add_performance(performance) if performance&.load
29
+
30
+ performance =
31
+ Performance.new(
32
+ movement: performance&.movement,
33
+ load: safe_float(token.literal),
34
+ )
35
+ else
36
+ performance.load = safe_float(token.literal)
37
+ end
38
+ when Token::METAKEY
39
+ metakey_buf = token.literal
40
+ target = performance.nil? ? result.metadata : movement_meta
41
+ target[metakey_buf] = ""
42
+ when Token::METAVALUE
43
+ target = performance.nil? ? result.metadata : movement_meta
44
+ target[metakey_buf] = token.literal
45
+ metakey_buf = nil
46
+ when Token::MOVEMENT
47
+ if !performance.nil?
48
+ result
49
+ .add_performance(
50
+ performance,
51
+ metadata: movement_metadata,
52
+ notes: movement_notes,
53
+ )
54
+ end
55
+
56
+ performance = Performance.new(movement: token.literal)
57
+ when Token::NOTE
58
+ target = performance.nil? ? result.notes : movement_notes
59
+ target << token.literal
60
+ when Token::REP
61
+ reps = safe_float(token.literal)
62
+
63
+ (performance || Performance.new).reps = reps
64
+ when Token::SET
65
+ sets = safe_float(token.literal)
66
+
67
+ (performance || Performance.new).sets = sets
68
+ when Token::SUPERSET
69
+ end
70
+ end.tap do |results|
71
+ return results if result.nil?
72
+
73
+ result.add_performance(performance) if !performance.nil?
74
+ results << result
75
+ end
76
+ end
77
+
78
+ def self.safe_float(maybe_float_str)
79
+ Float(maybe_float_str)
80
+ rescue
81
+ 0.0
82
+ end
83
+
84
+ def self.safe_time(maybe_time_str)
85
+ Time.parse(maybe_time_str)
86
+ rescue
87
+ Time.now
88
+ end
89
+ end
@@ -0,0 +1,220 @@
1
+ module TDRB
2
+ ##
3
+ # Parser is the thing that does STUFF to a Token stream. What stuff? Well,
4
+ # that is defined by the handler lambdas.
5
+
6
+ class Parser
7
+ attr_reader :lexer, :traindown
8
+
9
+ def initialize(traindown:)
10
+ @lexer = Lexer.new(source: traindown, state: IDLE)
11
+ @traindown = traindown
12
+ end
13
+
14
+ def tokens
15
+ lexer.run
16
+ lexer.tokens
17
+ end
18
+
19
+ class << self
20
+ def colon_terminator?(char)
21
+ char == ":" || char == Token::EOF
22
+ end
23
+
24
+ def line_terminator?(char)
25
+ return true if char == Token::EOF
26
+
27
+ cp = char.codepoints;
28
+
29
+ return true if cp.empty?
30
+ return true if cp.count == 1 && [0, 10, 13, 59].include?(cp.first)
31
+ return true if cp.count == 2 && cp.first == 13 and cp.last == 10
32
+
33
+ false
34
+ end
35
+
36
+ def whitespace?(char)
37
+ char.strip.empty?
38
+ end
39
+ end
40
+
41
+ IDLE = lambda do |lexer|
42
+ char = lexer.peek
43
+ return if char == Token::EOF
44
+
45
+ if whitespace?(char) || line_terminator?(char)
46
+ lexer.next_char
47
+ lexer.ignore
48
+
49
+ return IDLE
50
+ end
51
+
52
+ case char
53
+ when "@" then DATETIME
54
+ when "#" then METAKEY
55
+ when "*" then NOTE
56
+ else
57
+ VALUE
58
+ end
59
+ end
60
+
61
+ DATETIME = lambda do |lexer|
62
+ lexer.take(["@", " "])
63
+ lexer.ignore
64
+
65
+ char = lexer.next_char
66
+
67
+ while !line_terminator?(char) do
68
+ char = lexer.next_char
69
+ end
70
+
71
+ lexer.rewind
72
+ lexer.emit(Token::DATETIME)
73
+
74
+ IDLE
75
+ end
76
+
77
+ METAKEY = lambda do |lexer|
78
+ lexer.take(["#", " "])
79
+ lexer.ignore
80
+
81
+ char = lexer.next_char
82
+
83
+ while !colon_terminator?(char) do
84
+ char = lexer.next_char
85
+ end
86
+
87
+ lexer.rewind
88
+ lexer.emit(Token::METAKEY)
89
+
90
+ METAVALUE
91
+ end
92
+
93
+ METAVALUE = lambda do |lexer|
94
+ lexer.take([":", " "])
95
+ lexer.ignore
96
+
97
+ char = lexer.next_char
98
+
99
+ while !line_terminator?(char) do
100
+ char = lexer.next_char
101
+ end
102
+
103
+ lexer.rewind
104
+ lexer.emit(Token::METAVALUE)
105
+
106
+ IDLE
107
+ end
108
+
109
+ # register_state :movement do
110
+ # end
111
+
112
+ MOVEMENT = lambda do |lexer|
113
+ superset = false;
114
+
115
+ char = lexer.next_char
116
+
117
+ return if char == Token::EOF
118
+
119
+ if char == "+"
120
+ superset = true
121
+ lexer.take([" "])
122
+ lexer.ignore
123
+ char = lexer.next_char
124
+ end
125
+
126
+ if char == "'"
127
+ lexer.ignore
128
+ char = lexer.next_char
129
+ end
130
+
131
+ while !colon_terminator?(char) do
132
+ char = lexer.next_char
133
+ end
134
+
135
+ lexer.rewind
136
+
137
+ if (superset)
138
+ lexer.emit(Token::SUPERSET)
139
+ else
140
+ lexer.emit(Token::MOVEMENT)
141
+ end
142
+
143
+ lexer.take([":"])
144
+ lexer.ignore
145
+
146
+ IDLE
147
+ end
148
+
149
+ NOTE = lambda do |lexer|
150
+ lexer.take(["*", " "])
151
+ lexer.ignore
152
+
153
+ char = lexer.next_char
154
+
155
+ while !line_terminator?(char) do
156
+ char = lexer.next_char
157
+ end
158
+
159
+ lexer.rewind
160
+ lexer.emit(Token::NOTE)
161
+
162
+ IDLE
163
+ end
164
+
165
+ NUMBER = lambda do |lexer|
166
+ lexer.take(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."])
167
+
168
+ # TODO: Configurable chars
169
+ case lexer.peek
170
+ when "f", "F" then lexer.emit(Token::FAIL)
171
+ when "r", "R" then lexer.emit(Token::REP)
172
+ when "s", "S" then lexer.emit(TokenType::SET)
173
+ else
174
+ lexer.emit(Token::LOAD)
175
+ end
176
+
177
+ lexer.take(["f", "F", "r", "R", "s", "S"])
178
+ lexer.ignore
179
+
180
+ IDLE
181
+ end
182
+
183
+ VALUE = lambda do |lexer|
184
+ char = lexer.next_char
185
+
186
+ if char == "+" || char == "'"
187
+ lexer.rewind
188
+ return MOVEMENT
189
+ end
190
+
191
+ begin
192
+ !!Integer(char)
193
+ return NUMBER
194
+ rescue ArgumentError, TypeError
195
+ nil
196
+ end
197
+
198
+ if char != "b" && char != "B"
199
+ lexer.rewind
200
+ return MOVEMENT
201
+ end
202
+
203
+ peek = lexer.peek
204
+
205
+ if peek != "w" && peek != "W"
206
+ lexer.rewind
207
+ MOVEMENT
208
+ end
209
+
210
+ while !whitespace?(char)
211
+ char = lexer.next_char
212
+ end
213
+
214
+ lexer.rewind
215
+ lexer.emit(Token::LOAD)
216
+
217
+ IDLE
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,57 @@
1
+ module TDRB
2
+ class Performance
3
+ attr_accessor :fails,
4
+ :load,
5
+ :metadata,
6
+ :movement,
7
+ :notes,
8
+ :sequence,
9
+ :sets,
10
+ :reps
11
+
12
+ def initialize(
13
+ fails: 0,
14
+ load: nil,
15
+ movement: "Unidentified Movement",
16
+ metadata: {},
17
+ notes: [],
18
+ reps: 1,
19
+ sequence: 0,
20
+ sets: 1
21
+ )
22
+ @fails = 0
23
+ @load = load
24
+ @movement = movement
25
+ @metadata = metadata
26
+ @notes = notes
27
+ @reps = 1
28
+ @sequence = 0
29
+ @sets = 1
30
+ end
31
+
32
+ def ==(p)
33
+ raise TypeError.new("Not a Performance") if !p.is_a? Performance
34
+
35
+ p.movement == movement &&
36
+ p.reps == reps &&
37
+ p.sets == sets &&
38
+ p.fails == fails &&
39
+ p.load == load
40
+ end
41
+
42
+ def to_h
43
+ @hash ||=
44
+ instance_variables.each_with_object({}) do |iv, h|
45
+ h[iv[1..].to_sym] = instance_variable_get(iv)
46
+ end
47
+ end
48
+
49
+ def to_json
50
+ @json ||= to_h.to_json
51
+ end
52
+
53
+ def volume
54
+ (load || 0.0) * sets * (reps - fails)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,50 @@
1
+ require "json"
2
+ require "set"
3
+
4
+ module TDRB
5
+ class Result
6
+ attr_reader :metadata,
7
+ :notes,
8
+ :movements,
9
+ :occurred_at,
10
+ :performances,
11
+ :source
12
+
13
+ def initialize(
14
+ movements: ::Set.new,
15
+ occurred_at: nil,
16
+ performances: [],
17
+ source: ""
18
+ )
19
+ @metadata = {}
20
+ @movements = movements
21
+ @notes = []
22
+ @occurred_at = occurred_at
23
+ @performances = performances
24
+ @source = source
25
+ end
26
+
27
+ def add_performance(performance, metadata: {}, notes: [])
28
+ raise TypeError.new("Not a performance") if !performance.is_a? Performance
29
+
30
+ movements.add(performance.movement)
31
+
32
+ performance.metadata = metadata
33
+ performance.notes = notes
34
+ performance.sequence = performances.count
35
+
36
+ performances << performance
37
+ end
38
+
39
+ def to_h
40
+ @hash ||=
41
+ instance_variables.each_with_object({}) do |iv, h|
42
+ h[iv[1..].to_sym] = instance_variable_get(iv)
43
+ end
44
+ end
45
+
46
+ def to_json
47
+ @json ||= to_h.to_json
48
+ end
49
+ end
50
+ end
data/lib/tdrb/token.rb ADDED
@@ -0,0 +1,63 @@
1
+ module TDRB
2
+ class Token
3
+ attr_reader :literal, :type
4
+
5
+ DATETIME = "Date / Time".freeze
6
+ FAIL = "Fails".freeze
7
+ LOAD = "Load".freeze
8
+ METAKEY = "Metadata Key".freeze
9
+ METAVALUE = "Metadata Value".freeze
10
+ MOVEMENT = "Movement".freeze
11
+ NOTE = "Note".freeze
12
+ REP = "Reps".freeze
13
+ SET = "Sets".freeze
14
+ SUPERSET = "Superset Movement".freeze
15
+
16
+ TOKEN_SYMBOLS = %i[
17
+ datetime fail load metakey metavalue movement note rep set superset
18
+ ]
19
+
20
+ ALLOWED_TYPES = TOKEN_SYMBOLS.map { |ts| const_get(ts.upcase) }.freeze
21
+
22
+ EOF = "\x00".freeze
23
+
24
+ class << self
25
+ TOKEN_SYMBOLS.each do |method|
26
+ define_method(method) do |literal|
27
+ new(literal: literal, type: const_get(method.upcase))
28
+ end
29
+ end
30
+ end
31
+
32
+ def initialize(literal:, type:)
33
+ raise TokenTypeError if !ALLOWED_TYPES.include?(type)
34
+
35
+ @literal = literal
36
+ @type = type
37
+ end
38
+
39
+ def ==(t)
40
+ raise TokenArgsError.new("Invalid comparison") if !t.is_a? Token
41
+
42
+ literal == t.literal && type == t.type
43
+ end
44
+
45
+ def ===(t)
46
+ raise TokenTypeError if !ALLOWED_TYPES.include?(t)
47
+
48
+ type == t
49
+ end
50
+
51
+ def movement?
52
+ @movement ||= [MOVEMENT, SUPERSET].include?(type)
53
+ end
54
+
55
+ def performance?
56
+ @performance ||= [FAIL, LOAD, REP, SET].include?(type)
57
+ end
58
+
59
+ def to_s
60
+ "[#{type}] #{literal}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module TDRB
2
+ VERSION = "1.0.0".freeze
3
+ end
data/lib/tdrb.rb ADDED
@@ -0,0 +1,4 @@
1
+ Dir[File.join(__dir__, 'tdrb', '*.rb')].each { |f| require f }
2
+
3
+ module TDRB
4
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tdrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tyler Scott
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-12-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A fun little gem chockful of Traindown tchotchkies
14
+ email: tyler@greaterscott.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/tdrb.rb
20
+ - lib/tdrb/error.rb
21
+ - lib/tdrb/lexer.rb
22
+ - lib/tdrb/parse.rb
23
+ - lib/tdrb/parser.rb
24
+ - lib/tdrb/performance.rb
25
+ - lib/tdrb/result.rb
26
+ - lib/tdrb/token.rb
27
+ - lib/tdrb/version.rb
28
+ homepage: https://traindown.com
29
+ licenses:
30
+ - BSD-3-Clause
31
+ metadata:
32
+ homepage_uri: https://traindown.com
33
+ source_code_uri: https://traindown.com
34
+ changelog_uri: https://traindown.com
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.7.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.1.2
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Ruby + Traindown = TDRB
54
+ test_files: []