mork-parser 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/lib/mork/data.rb +14 -0
- data/lib/mork/lexer.rb +315 -0
- data/lib/mork/parser.rb +503 -0
- data/lib/mork/raw/alias.rb +16 -0
- data/lib/mork/raw/cell.rb +55 -0
- data/lib/mork/raw/dictionary.rb +37 -0
- data/lib/mork/raw/group.rb +46 -0
- data/lib/mork/raw/id.rb +74 -0
- data/lib/mork/raw/meta_alias.rb +19 -0
- data/lib/mork/raw/meta_table.rb +14 -0
- data/lib/mork/raw/row.rb +39 -0
- data/lib/mork/raw/table.rb +46 -0
- data/lib/mork/raw.rb +148 -0
- data/lib/mork/resolved/cell.rb +16 -0
- data/lib/mork/resolved/id.rb +18 -0
- data/lib/mork/resolved/row.rb +27 -0
- data/lib/mork/resolved/table.rb +20 -0
- data/lib/mork.rb +5 -0
- data/mork.gemspec +27 -0
- metadata +68 -0
data/lib/mork/raw/id.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mork/resolved/id"
|
4
|
+
|
5
|
+
module Mork
|
6
|
+
class Raw; end # rubocop:disable Lint/EmptyClass
|
7
|
+
|
8
|
+
# A raw Mork ID
|
9
|
+
class Raw::Id
|
10
|
+
attr_reader :raw
|
11
|
+
|
12
|
+
ACTIONS = {
|
13
|
+
"-" => :delete,
|
14
|
+
nil => :add
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(raw:)
|
18
|
+
@raw = raw
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve(dictionaries:)
|
22
|
+
namespace = resolve_namespace(dictionaries)
|
23
|
+
Resolved::Id.new(action: action, namespace: namespace, id: id)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def action
|
29
|
+
ACTIONS[parts[:action]]
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
parts[:id]
|
34
|
+
end
|
35
|
+
|
36
|
+
def raw_namespace
|
37
|
+
parts[:raw_namespace]
|
38
|
+
end
|
39
|
+
|
40
|
+
def resolve_namespace(dictionaries)
|
41
|
+
case
|
42
|
+
when raw_namespace.nil?
|
43
|
+
nil
|
44
|
+
when raw_namespace.start_with?("^")
|
45
|
+
value = raw_namespace[1..]
|
46
|
+
dictionary = dictionaries.fetch("c")
|
47
|
+
dictionary.fetch(value)
|
48
|
+
else
|
49
|
+
raw_namespace
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# rubocop:disable Lint/MixedRegexpCaptureTypes
|
54
|
+
# Rubocop gives a false positive here
|
55
|
+
RAW_ID_MATCH = /
|
56
|
+
\A
|
57
|
+
\{? # The lexer captures the table delimiter
|
58
|
+
(?<action>-)? # The optional action can indicate deletion
|
59
|
+
(?<id>[0-9]+) # Tables are numbered
|
60
|
+
(
|
61
|
+
:
|
62
|
+
(?<raw_namespace>
|
63
|
+
\^? # The raw namespace may be a reference
|
64
|
+
\S+ # The name is everything but trailing whitespace
|
65
|
+
)
|
66
|
+
)? # The namespace is optional
|
67
|
+
/x.freeze
|
68
|
+
# rubocop:enable Lint/MixedRegexpCaptureTypes
|
69
|
+
|
70
|
+
def parts
|
71
|
+
@parts ||= RAW_ID_MATCH.match(raw)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mork
|
4
|
+
class Raw; end # rubocop:disable Lint/EmptyClass
|
5
|
+
|
6
|
+
# An alias indicating a Dictionary's scope
|
7
|
+
class Raw::MetaAlias
|
8
|
+
attr_reader :raw
|
9
|
+
|
10
|
+
def initialize(raw:)
|
11
|
+
@raw = raw
|
12
|
+
end
|
13
|
+
|
14
|
+
def scope
|
15
|
+
_from, to = raw.split("=")
|
16
|
+
to
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/mork/raw/row.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mork/raw/id"
|
4
|
+
require "mork/resolved/row"
|
5
|
+
|
6
|
+
module Mork
|
7
|
+
class Raw; end # rubocop:disable Lint/EmptyClass
|
8
|
+
|
9
|
+
# A row of cells
|
10
|
+
class Raw::Row
|
11
|
+
attr_reader :raw_id
|
12
|
+
attr_reader :cells
|
13
|
+
|
14
|
+
def initialize(raw_id:, cells:)
|
15
|
+
@raw_id = raw_id
|
16
|
+
@cells = cells
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolve(dictionaries:)
|
20
|
+
resolved_id = raw_id_resolver.resolve(dictionaries: dictionaries)
|
21
|
+
Resolved::Row.new(
|
22
|
+
action: resolved_id.action,
|
23
|
+
namespace: resolved_id.namespace,
|
24
|
+
id: resolved_id.id,
|
25
|
+
cells: resolved_cells(dictionaries)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def raw_id_resolver
|
32
|
+
@raw_id_resolver ||= Raw::Id.new(raw: raw_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def resolved_cells(dictionaries)
|
36
|
+
cells.map { |c| c.resolve(dictionaries: dictionaries) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mork/raw/id"
|
4
|
+
require "mork/raw/row"
|
5
|
+
require "mork/resolved/table"
|
6
|
+
|
7
|
+
module Mork
|
8
|
+
class Raw; end # rubocop:disable Lint/EmptyClass
|
9
|
+
|
10
|
+
# A table of rows
|
11
|
+
class Raw::Table
|
12
|
+
attr_reader :raw_id
|
13
|
+
attr_reader :values
|
14
|
+
|
15
|
+
def initialize(raw_id:, values:)
|
16
|
+
@raw_id = raw_id
|
17
|
+
@values = values
|
18
|
+
end
|
19
|
+
|
20
|
+
def rows
|
21
|
+
values.filter { |c| c.is_a?(Raw::Row) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve(dictionaries:)
|
25
|
+
resolved_id = resolve_id(dictionaries)
|
26
|
+
Resolved::Table.new(
|
27
|
+
action: resolved_id.action, namespace: resolved_id.namespace, id: resolved_id.id,
|
28
|
+
rows: resolved_rows(dictionaries)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def raw_id_resolver
|
35
|
+
@raw_id_resolver ||= Raw::Id.new(raw: raw_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolve_id(dictionaries)
|
39
|
+
raw_id_resolver.resolve(dictionaries: dictionaries)
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolved_rows(dictionaries)
|
43
|
+
rows.map { |r| r.resolve(dictionaries: dictionaries) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/mork/raw.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mork/data"
|
4
|
+
require "mork/raw/dictionary"
|
5
|
+
require "mork/raw/group"
|
6
|
+
|
7
|
+
module Mork
|
8
|
+
# Raw data returned by the Parser
|
9
|
+
class Raw
|
10
|
+
attr_reader :values
|
11
|
+
|
12
|
+
def initialize(values:)
|
13
|
+
@values = values
|
14
|
+
end
|
15
|
+
|
16
|
+
# build a Hash of scopes (usually "a" and "c")
|
17
|
+
# each value being a Hash of key-value entries
|
18
|
+
def dictionaries
|
19
|
+
@dictionaries ||=
|
20
|
+
raw_dictionaries.each.with_object({}) do |d, scopes|
|
21
|
+
scope = d.scope
|
22
|
+
scopes[scope] ||= {}
|
23
|
+
scopes[scope].merge!(d.to_h)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def data
|
28
|
+
Data.new(
|
29
|
+
rows: resolved_rows(dictionaries),
|
30
|
+
tables: resolved_tables(dictionaries)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def raw_dictionaries
|
37
|
+
@raw_dictionaries ||= top_level_dictionaries + group_dictionaries
|
38
|
+
end
|
39
|
+
|
40
|
+
def top_level_dictionaries
|
41
|
+
@top_level_dictionaries ||= values.filter { |v| v.is_a?(Raw::Dictionary) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def group_dictionaries
|
45
|
+
@group_dictionaries ||= raw_groups.flat_map(&:dictionaries)
|
46
|
+
end
|
47
|
+
|
48
|
+
def raw_groups
|
49
|
+
@raw_groups ||= values.filter { |v| v.is_a?(Raw::Group) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def resolved_groups(dictionaries)
|
53
|
+
raw_groups.
|
54
|
+
map { |g| g.resolve(dictionaries: dictionaries) }
|
55
|
+
end
|
56
|
+
|
57
|
+
####################
|
58
|
+
# rows
|
59
|
+
|
60
|
+
def raw_rows
|
61
|
+
@raw_rows ||= values.filter { |v| v.is_a?(Raw::Row) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def raw_tables
|
65
|
+
@raw_tables ||= values.filter { |v| v.is_a?(Raw::Table) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def unmerged_rows(dictionaries)
|
69
|
+
top_level_rows = raw_rows.map { |r| r.resolve(dictionaries: dictionaries) }
|
70
|
+
group_rows =
|
71
|
+
resolved_groups(dictionaries).
|
72
|
+
map { |g| g[:rows] }.
|
73
|
+
flatten
|
74
|
+
(top_level_rows + group_rows).group_by(&:namespace)
|
75
|
+
end
|
76
|
+
|
77
|
+
def resolved_rows(dictionaries)
|
78
|
+
unmerged_rows(dictionaries).
|
79
|
+
each.with_object({}) do |(namespace, rows), acc|
|
80
|
+
merged = reduce_rows(rows)
|
81
|
+
acc[namespace] = merged if merged.any?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def reduce_rows(rows)
|
86
|
+
rows.each.with_object({}) do |row, acc|
|
87
|
+
case row.action
|
88
|
+
when :add
|
89
|
+
acc[row.id] = row.to_h
|
90
|
+
when :delete
|
91
|
+
acc.delete(row.id)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
####################
|
97
|
+
# tables
|
98
|
+
|
99
|
+
def resolved_tables(dictionaries)
|
100
|
+
unmerged = unmerged_tables(dictionaries)
|
101
|
+
merged = merge_tables(unmerged)
|
102
|
+
reduce_tables(merged)
|
103
|
+
end
|
104
|
+
|
105
|
+
def reduce_tables(tables)
|
106
|
+
tables.
|
107
|
+
each.with_object({}) do |(namespace, namespace_tables), acc1|
|
108
|
+
acc1[namespace] =
|
109
|
+
namespace_tables.each.with_object({}) do |(id, rows), acc2|
|
110
|
+
acc2[id] = reduce_rows(rows)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def merge_tables(tables)
|
116
|
+
tables.
|
117
|
+
each.with_object({}) do |(namespace, namespace_tables), acc|
|
118
|
+
merged = merge_namespace_tables(namespace_tables)
|
119
|
+
acc[namespace] = merged if merged.any?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def unmerged_tables(dictionaries)
|
124
|
+
top_level_tables = unmerged_resolved_tables(dictionaries)
|
125
|
+
group_tables =
|
126
|
+
resolved_groups(dictionaries).
|
127
|
+
map { |g| g[:tables] }.
|
128
|
+
flatten
|
129
|
+
(top_level_tables + group_tables).group_by(&:namespace)
|
130
|
+
end
|
131
|
+
|
132
|
+
def merge_namespace_tables(tables)
|
133
|
+
tables.each.with_object({}) do |table, acc|
|
134
|
+
case table.action
|
135
|
+
when :add
|
136
|
+
acc[table.id] ||= []
|
137
|
+
acc[table.id] += table.rows
|
138
|
+
when :delete
|
139
|
+
acc.delete(table.id)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def unmerged_resolved_tables(dictionaries)
|
145
|
+
raw_tables.map { |t| t.resolve(dictionaries: dictionaries) }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mork
|
4
|
+
class Resolved; end # rubocop:disable Lint/EmptyClass
|
5
|
+
|
6
|
+
# A resolved Cell
|
7
|
+
class Resolved::Cell
|
8
|
+
attr_reader :key
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(key:, value:)
|
12
|
+
@key = key
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mork
|
4
|
+
class Resolved; end # rubocop:disable Lint/EmptyClass
|
5
|
+
|
6
|
+
# A resolved Mork Id
|
7
|
+
class Resolved::Id
|
8
|
+
attr_reader :action
|
9
|
+
attr_reader :namespace
|
10
|
+
attr_reader :id
|
11
|
+
|
12
|
+
def initialize(action:, namespace:, id:)
|
13
|
+
@action = action
|
14
|
+
@namespace = namespace
|
15
|
+
@id = id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mork
|
4
|
+
class Resolved; end # rubocop:disable Lint/EmptyClass
|
5
|
+
|
6
|
+
# A resolved Row
|
7
|
+
class Resolved::Row
|
8
|
+
attr_reader :action
|
9
|
+
attr_reader :namespace
|
10
|
+
attr_reader :id
|
11
|
+
attr_reader :cells
|
12
|
+
|
13
|
+
def initialize(action:, namespace:, id:, cells:)
|
14
|
+
@action = action
|
15
|
+
@namespace = namespace
|
16
|
+
@id = id
|
17
|
+
@cells = cells
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
cells.
|
22
|
+
each.with_object({}) do |cell, acc|
|
23
|
+
acc[cell.key] = cell.value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mork
|
4
|
+
class Resolved; end # rubocop:disable Lint/EmptyClass
|
5
|
+
|
6
|
+
# A resolved Table
|
7
|
+
class Resolved::Table
|
8
|
+
attr_reader :action
|
9
|
+
attr_reader :namespace
|
10
|
+
attr_reader :id
|
11
|
+
attr_reader :rows
|
12
|
+
|
13
|
+
def initialize(action:, namespace:, id:, rows:)
|
14
|
+
@action = action
|
15
|
+
@namespace = namespace
|
16
|
+
@id = id
|
17
|
+
@rows = rows
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/mork.rb
ADDED
data/mork.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/mork"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "mork-parser"
|
7
|
+
spec.version = Mork::VERSION
|
8
|
+
spec.authors = ["Joe Yates"]
|
9
|
+
spec.email = ["joe.g.yates@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Parse Mork databases (as used by Mozilla Thunderbird)"
|
12
|
+
spec.homepage = "https://github.com/joeyates/mork"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.7"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
spec.metadata["changelog_uri"] = File.join(spec.homepage, "blob/main/CHANGELOG.md")
|
19
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
20
|
+
|
21
|
+
spec.files = Dir.glob("lib/**/*.rb")
|
22
|
+
spec.files += ["mork.gemspec"]
|
23
|
+
spec.files += %w[LICENSE.txt README.md]
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = []
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mork-parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe Yates
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- joe.g.yates@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- LICENSE.txt
|
21
|
+
- README.md
|
22
|
+
- lib/mork.rb
|
23
|
+
- lib/mork/data.rb
|
24
|
+
- lib/mork/lexer.rb
|
25
|
+
- lib/mork/parser.rb
|
26
|
+
- lib/mork/raw.rb
|
27
|
+
- lib/mork/raw/alias.rb
|
28
|
+
- lib/mork/raw/cell.rb
|
29
|
+
- lib/mork/raw/dictionary.rb
|
30
|
+
- lib/mork/raw/group.rb
|
31
|
+
- lib/mork/raw/id.rb
|
32
|
+
- lib/mork/raw/meta_alias.rb
|
33
|
+
- lib/mork/raw/meta_table.rb
|
34
|
+
- lib/mork/raw/row.rb
|
35
|
+
- lib/mork/raw/table.rb
|
36
|
+
- lib/mork/resolved/cell.rb
|
37
|
+
- lib/mork/resolved/id.rb
|
38
|
+
- lib/mork/resolved/row.rb
|
39
|
+
- lib/mork/resolved/table.rb
|
40
|
+
- mork.gemspec
|
41
|
+
homepage: https://github.com/joeyates/mork
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata:
|
45
|
+
homepage_uri: https://github.com/joeyates/mork
|
46
|
+
source_code_uri: https://github.com/joeyates/mork
|
47
|
+
changelog_uri: https://github.com/joeyates/mork/blob/main/CHANGELOG.md
|
48
|
+
rubygems_mfa_required: 'true'
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '2.7'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubygems_version: 3.4.10
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Parse Mork databases (as used by Mozilla Thunderbird)
|
68
|
+
test_files: []
|