mork-parser 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bed8800b4f506dcf34185682e440188ebc60a0d40afb6709b3de078e8d8cd35d
4
+ data.tar.gz: 2efabe69f325398a9982d9eabcb44cf88898f581245ce3d4863f07b5a2ad815d
5
+ SHA512:
6
+ metadata.gz: 25845a115fbed77f346cd14cf14d9c2be85b03bb73d95b130dea0a28c4c18ff3694b6e1fab0ef6708bbf2512b4e1bf1500260b72fe42d103de75d1512c1e4ac0
7
+ data.tar.gz: 12cb8b831ac111de6848008c5e84183a4651d6e197dfd07d4b092badf74aa37083b12b34d959471696958e2043ba51d70c34eb1222aefa8ad79ee911409856b4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Joe Yates
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Mork
2
+
3
+ [![Build Status](https://github.com/joeyates/imap-backup/actions/workflows/main.yml/badge.svg)][CI Status]
4
+ ![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/joeyates/0ad88e9ac5abded0a579daf09b3dbc8f/raw/coverage.json)
5
+
6
+ [CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
7
+
8
+ Mozilla Thunderbird uses the [Mork database format](https://en.wikipedia.org/wiki/Mork_%28file_format%29) for its folder indexes.
9
+
10
+ This library reads in a Mork file and produces a `Mork::Data` instance.
11
+
12
+ This provides:
13
+
14
+ * `tables` - The top-level tables,
15
+ * `rows` - The top-level rows (i.e. rows not contained in tables).
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ require "mork/parser"
21
+
22
+ parser = Mork::Parser.new
23
+ content = File.read("MyFolder.msf")
24
+ raw = parser.parse(content)
25
+ data = raw.data
26
+ ```
27
+
28
+ ## Development
29
+
30
+ After checking out the repo, run `bundle` to install dependencies.
31
+ Then, run `rake spec` to run the tests.
32
+
33
+ To release a new version, update the version number in `version.rb`,
34
+ and then run `rake release`, which will create a git tag for the version,
35
+ push git commits and the created tag,
36
+ and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+
38
+ ## Contributing
39
+
40
+ Bug reports and pull requests are welcome on GitHub at https://github.com/joeyates/mork.
41
+ This project is intended to be a safe, welcoming space for collaboration,
42
+ and contributors are expected to adhere to the [code of conduct](https://github.com/joeyates/mork/blob/main/CODE_OF_CONDUCT.md).
43
+
44
+ ## License
45
+
46
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
47
+
48
+ ## Code of Conduct
49
+
50
+ Everyone interacting in the Mork project's codebases, issue trackers,
51
+ chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/joeyates/mork/blob/main/CODE_OF_CONDUCT.md).
data/lib/mork/data.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mork
4
+ # The pure data extracted from a Mork file
5
+ class Data
6
+ attr_reader :rows
7
+ attr_reader :tables
8
+
9
+ def initialize(rows:, tables:)
10
+ @rows = rows
11
+ @tables = tables
12
+ end
13
+ end
14
+ end
data/lib/mork/lexer.rb ADDED
@@ -0,0 +1,315 @@
1
+ #--
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by rex 1.0.7
4
+ # from lexical definition file "parser/mork.rex".
5
+ #++
6
+
7
+ require 'racc/parser'
8
+ # mork
9
+ # lexical definition for Mork files
10
+ #
11
+ # Generate Ruby lexer:
12
+ # $ rex parser/mork.rex --output-file lib/mork/lexer.rb
13
+
14
+ class Mork::Lexer < Racc::Parser
15
+ require 'strscan'
16
+
17
+ class ScanError < StandardError ; end
18
+
19
+ attr_reader :lineno
20
+ attr_reader :filename
21
+ attr_accessor :state
22
+
23
+ def scan_setup(str)
24
+ @ss = StringScanner.new(str)
25
+ @lineno = 1
26
+ @state = nil
27
+ end
28
+
29
+ def action
30
+ yield
31
+ end
32
+
33
+ def scan_str(str)
34
+ scan_setup(str)
35
+ do_parse
36
+ end
37
+ alias :scan :scan_str
38
+
39
+ def load_file( filename )
40
+ @filename = filename
41
+ File.open(filename, "r") do |f|
42
+ scan_setup(f.read)
43
+ end
44
+ end
45
+
46
+ def scan_file( filename )
47
+ load_file(filename)
48
+ do_parse
49
+ end
50
+
51
+
52
+ def next_token
53
+ return if @ss.eos?
54
+
55
+ # skips empty actions
56
+ until token = _next_token or @ss.eos?; end
57
+ token
58
+ end
59
+
60
+ def _next_token
61
+ text = @ss.peek(1)
62
+ @lineno += 1 if text == "\n"
63
+ token = case @state
64
+ when nil
65
+ case
66
+ when (text = @ss.scan(/\/\/\s<!--\s<mdb:mork:z.*\n/))
67
+ action { @stack = []; [:magic, text] }
68
+
69
+ when (text = @ss.scan(/\/\//))
70
+ action { @state = :COMMENT; [:comment_in, text] }
71
+
72
+ when (text = @ss.scan(/\n/))
73
+ action { }
74
+
75
+ when (text = @ss.scan(/</))
76
+ action { @stack.push(@state); @state = :DICTIONARY; [:dictionary_in, text] }
77
+
78
+ when (text = @ss.scan(/\[/))
79
+ action { @stack.push(@state); @state = :ROW; [:row_in, text] }
80
+
81
+ when (text = @ss.scan(/\{[\-A-Z0-9]+(:[^\s]+\s)?/))
82
+ action { @stack.push(@state); @state = :TABLE; [:table_in, text] }
83
+
84
+ when (text = @ss.scan(/@\$\${[0-9A-F]+{@/))
85
+ action { @state = :GROUP; [:group_in, text] }
86
+
87
+
88
+ else
89
+ text = @ss.string[@ss.pos .. -1]
90
+ raise ScanError, "can not match: '" + text + "'"
91
+ end # if
92
+
93
+ when :COMMENT
94
+ case
95
+ when (text = @ss.scan(/\n/))
96
+ action { @state = @stack.pop; [:comment_out, text] }
97
+
98
+ when (text = @ss.scan(/.*/))
99
+ action { [:comment_text, text] }
100
+
101
+
102
+ else
103
+ text = @ss.string[@ss.pos .. -1]
104
+ raise ScanError, "can not match: '" + text + "'"
105
+ end # if
106
+
107
+ when :DICTIONARY
108
+ case
109
+ when (text = @ss.scan(/\s+/))
110
+ action { }
111
+
112
+ when (text = @ss.scan(/>/))
113
+ action { @state = @stack.pop; [:dictionary_out, text] }
114
+
115
+ when (text = @ss.scan(/\/\//))
116
+ action { @stack.push(@state); @state = :COMMENT; [:comment_in, text] }
117
+
118
+ when (text = @ss.scan(/</))
119
+ action { @state = :META; [:meta_in, text] }
120
+
121
+ when (text = @ss.scan(/\(/))
122
+ action { @state = :ALIAS; [:alias_in, text] }
123
+
124
+
125
+ else
126
+ text = @ss.string[@ss.pos .. -1]
127
+ raise ScanError, "can not match: '" + text + "'"
128
+ end # if
129
+
130
+ when :ALIAS
131
+ case
132
+ when (text = @ss.scan(/\)/))
133
+ action { @state = :DICTIONARY; [:alias_out, text] }
134
+
135
+ when (text = @ss.scan(/[0-9A-F]+(?==|$)/))
136
+ action { @state = :ALIAS_VALUE; [:alias_key, text] }
137
+
138
+
139
+ else
140
+ text = @ss.string[@ss.pos .. -1]
141
+ raise ScanError, "can not match: '" + text + "'"
142
+ end # if
143
+
144
+ when :ALIAS_VALUE
145
+ case
146
+ when (text = @ss.scan(/\n/))
147
+ action { }
148
+
149
+ when (text = @ss.scan(/\s+/))
150
+ action { }
151
+
152
+ when (text = @ss.scan(/=(?=\))/))
153
+ action { @state = :ALIAS; [:alias_value, nil] }
154
+
155
+ when (text = @ss.scan(/=(\\\)|[^\)])+/))
156
+ action { @state = :ALIAS; value = text[1..]; [:alias_value, value] }
157
+
158
+
159
+ else
160
+ text = @ss.string[@ss.pos .. -1]
161
+ raise ScanError, "can not match: '" + text + "'"
162
+ end # if
163
+
164
+ when :META
165
+ case
166
+ when (text = @ss.scan(/\s+/))
167
+ action { }
168
+
169
+ when (text = @ss.scan(/>/))
170
+ action { @state = :DICTIONARY; [:meta_out, text] }
171
+
172
+ when (text = @ss.scan(/\(/))
173
+ action { @state = :META_ALIAS; [:meta_alias_in, text] }
174
+
175
+
176
+ else
177
+ text = @ss.string[@ss.pos .. -1]
178
+ raise ScanError, "can not match: '" + text + "'"
179
+ end # if
180
+
181
+ when :META_ALIAS
182
+ case
183
+ when (text = @ss.scan(/\)/))
184
+ action { @state = :META; [:meta_alias_out, text] }
185
+
186
+ when (text = @ss.scan(/[^=]+=[^\)]+/))
187
+ action { [:meta_alias, text] }
188
+
189
+
190
+ else
191
+ text = @ss.string[@ss.pos .. -1]
192
+ raise ScanError, "can not match: '" + text + "'"
193
+ end # if
194
+
195
+ when :ROW
196
+ case
197
+ when (text = @ss.scan(/[\-A-Z0-9]+(:[^\(]+)?/))
198
+ action { @state = :CELLS; [:row_mid, text] }
199
+
200
+
201
+ else
202
+ text = @ss.string[@ss.pos .. -1]
203
+ raise ScanError, "can not match: '" + text + "'"
204
+ end # if
205
+
206
+ when :CELLS
207
+ case
208
+ when (text = @ss.scan(/\s+/))
209
+ action { }
210
+
211
+ when (text = @ss.scan(/\n/))
212
+ action { }
213
+
214
+ when (text = @ss.scan(/\]/))
215
+ action { @state = @stack.pop; [:row_out, text] }
216
+
217
+ when (text = @ss.scan(/\(/))
218
+ action { @state = :CELL; [:cell_in, text] }
219
+
220
+
221
+ else
222
+ text = @ss.string[@ss.pos .. -1]
223
+ raise ScanError, "can not match: '" + text + "'"
224
+ end # if
225
+
226
+ when :CELL
227
+ case
228
+ when (text = @ss.scan(/\)/))
229
+ action { @state = :CELLS; [:cell_out, text] }
230
+
231
+ when (text = @ss.scan(/\^[0-9A-F]+=[^\)]*/))
232
+ action { [:cell_value, text] }
233
+
234
+ when (text = @ss.scan(/\^[0-9A-F]+\^[0-9A-F]+/))
235
+ action { [:cell_value, text] }
236
+
237
+
238
+ else
239
+ text = @ss.string[@ss.pos .. -1]
240
+ raise ScanError, "can not match: '" + text + "'"
241
+ end # if
242
+
243
+ when :TABLE
244
+ case
245
+ when (text = @ss.scan(/\s+/))
246
+ action { }
247
+
248
+ when (text = @ss.scan(/\}/))
249
+ action { @state = @stack.pop; [:table_out, text] }
250
+
251
+ when (text = @ss.scan(/\{/))
252
+ action { @state = :META_TABLE; [:meta_table_in, text] }
253
+
254
+ when (text = @ss.scan(/\[/))
255
+ action { @stack.push(@state); @state = :ROW; [:row_in, text] }
256
+
257
+ when (text = @ss.scan(/\-[A-Z0-9]+\s*/))
258
+ action { [:row_delete, text] }
259
+
260
+ when (text = @ss.scan(/[A-Z0-9]+(:[^\s]+\s)?/))
261
+ action { [:table_row_ref, text] }
262
+
263
+
264
+ else
265
+ text = @ss.string[@ss.pos .. -1]
266
+ raise ScanError, "can not match: '" + text + "'"
267
+ end # if
268
+
269
+ when :META_TABLE
270
+ case
271
+ when (text = @ss.scan(/\}/))
272
+ action { @state = :TABLE; [:meta_table_out, text] }
273
+
274
+ when (text = @ss.scan(/\([^\)]+\)/))
275
+ action { [:meta_table_cell, text] }
276
+
277
+ when (text = @ss.scan(/[A-Z0-9]+(:[^\s]+\s)?/))
278
+ action { [:meta_table_row_ref, text] }
279
+
280
+
281
+ else
282
+ text = @ss.string[@ss.pos .. -1]
283
+ raise ScanError, "can not match: '" + text + "'"
284
+ end # if
285
+
286
+ when :GROUP
287
+ case
288
+ when (text = @ss.scan(/\n/))
289
+ action { }
290
+
291
+ when (text = @ss.scan(/@\$\$}[0-9A-F]+}@/))
292
+ action { @state = nil; [:group_out, text] }
293
+
294
+ when (text = @ss.scan(/</))
295
+ action { @stack.push(@state); @state = :DICTIONARY; [:dictionary_in, text] }
296
+
297
+ when (text = @ss.scan(/\[/))
298
+ action { @stack.push(@state); @state = :ROW; [:row_in, text] }
299
+
300
+ when (text = @ss.scan(/\{[\-A-Z0-9]+(:[^\s]+\s)?/))
301
+ action { @stack.push(@state); @state = :TABLE; [:table_in, text] }
302
+
303
+
304
+ else
305
+ text = @ss.string[@ss.pos .. -1]
306
+ raise ScanError, "can not match: '" + text + "'"
307
+ end # if
308
+
309
+ else
310
+ raise ScanError, "undefined state: '" + state.to_s + "'"
311
+ end # case state
312
+ token
313
+ end # def _next_token
314
+
315
+ end # class