asciichem 0.4.0 → 0.4.1
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 +4 -4
- data/lib/asciichem/formatter/text.rb +18 -0
- data/lib/asciichem/grammar.rb +25 -3
- data/lib/asciichem/model/spectrum.rb +46 -0
- data/lib/asciichem/model.rb +1 -0
- data/lib/asciichem/transform.rb +81 -4
- data/lib/asciichem/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1272affa5aa88924aaa06424dbc59bb115255963b675f596b58fd9588cd94fdc
|
|
4
|
+
data.tar.gz: bc6e6da1096c7a1c4e404d23036ec9513eae0f2866feeff9bd54a31c04540473
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1fb3e8ae50d397af66fa526f4351b6876f2c28c76c13604d8bd4c488abbff0b10eace074df3d48b1ff6b12b86a81f6f88ddabf07a6bf1588c63540ef0ad0113
|
|
7
|
+
data.tar.gz: c0c694ec1edf82c9d7dfc57adaa8853cf37c1d94dca7a1288e0587f669776e5d68275345ebcc89717628b12711acd88feb3adcc885aeb945b53bf4d7f8f08d42
|
|
@@ -140,6 +140,24 @@ module AsciiChem
|
|
|
140
140
|
parts.join
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
def visit_spectrum(spectrum)
|
|
144
|
+
parts = ["spectrum"]
|
|
145
|
+
parts << "[#{spectrum.type}]" if spectrum.type
|
|
146
|
+
params = spectrum.params.map { |k, v| "#{k}=#{v}" }.join(',')
|
|
147
|
+
parts << "(#{params})" unless params.empty?
|
|
148
|
+
peak_lines = spectrum.peaks.map do |peak|
|
|
149
|
+
line = "#{peak[:position]}: #{peak[:intensity]}"
|
|
150
|
+
line += " #{peak[:multiplicity]}" if peak[:multiplicity]
|
|
151
|
+
line += %( "#{peak[:assignment]}") if peak[:assignment]
|
|
152
|
+
line
|
|
153
|
+
end
|
|
154
|
+
unless peak_lines.empty?
|
|
155
|
+
body = peak_lines.join("\n ")
|
|
156
|
+
parts << "{\n #{body}\n}"
|
|
157
|
+
end
|
|
158
|
+
parts.join
|
|
159
|
+
end
|
|
160
|
+
|
|
143
161
|
private
|
|
144
162
|
|
|
145
163
|
def render_node(node)
|
data/lib/asciichem/grammar.rb
CHANGED
|
@@ -27,16 +27,16 @@ module AsciiChem
|
|
|
27
27
|
|
|
28
28
|
rule(:nodes) { node >> (spaces? >> node).repeat }
|
|
29
29
|
|
|
30
|
-
rule(:node) { reaction_cascade | reaction | electron_config | crystal | annotated_molecule | molecule | embedded_math | text_run.as(:text_run) }
|
|
30
|
+
rule(:node) { reaction_cascade | reaction | electron_config | crystal | spectrum | annotated_molecule | molecule | embedded_math | text_run.as(:text_run) }
|
|
31
31
|
|
|
32
32
|
# -- crystallography -------------------------------------------------
|
|
33
33
|
|
|
34
34
|
# crystal[Name](a=X,b=Y,...,sg=SG){atoms with @f(x,y,z)}
|
|
35
35
|
rule(:crystal) do
|
|
36
|
-
str('crystal') >>
|
|
36
|
+
(str('crystal') >>
|
|
37
37
|
crystal_name.maybe >>
|
|
38
38
|
crystal_params.maybe >>
|
|
39
|
-
crystal_body.maybe
|
|
39
|
+
crystal_body.maybe).as(:crystal_node)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
rule(:crystal_name) do
|
|
@@ -51,6 +51,28 @@ module AsciiChem
|
|
|
51
51
|
str('{') >> (str('}').absent? >> any).repeat.as(:crystal_body) >> str('}')
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# -- spectroscopy ---------------------------------------------------
|
|
55
|
+
|
|
56
|
+
# spectrum[type](params){peak data}
|
|
57
|
+
rule(:spectrum) do
|
|
58
|
+
(str('spectrum') >>
|
|
59
|
+
spectrum_type.maybe >>
|
|
60
|
+
spectrum_params.maybe >>
|
|
61
|
+
spectrum_body.maybe).as(:spectrum_node)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
rule(:spectrum_type) do
|
|
65
|
+
str('[') >> (str(']').absent? >> any).repeat.as(:spectrum_type) >> str(']')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
rule(:spectrum_params) do
|
|
69
|
+
str('(') >> (str(')').absent? >> any).repeat.as(:spectrum_params) >> str(')')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
rule(:spectrum_body) do
|
|
73
|
+
str('{') >> (str('}').absent? >> any).repeat.as(:spectrum_body) >> str('}')
|
|
74
|
+
end
|
|
75
|
+
|
|
54
76
|
# Annotated molecule: a molecule followed by one or more
|
|
55
77
|
# `@key("value")` annotations for CML metadata (names,
|
|
56
78
|
# identifiers, title, formula, labels).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsciiChem
|
|
4
|
+
module Model
|
|
5
|
+
# A spectroscopy result: NMR, IR, MS, UV-Vis peaks.
|
|
6
|
+
#
|
|
7
|
+
# Syntax:
|
|
8
|
+
# spectrum[nmr](type=1H,solvent=CDCl3){
|
|
9
|
+
# 1.2: 3H s "CH3"
|
|
10
|
+
# 7.2: 5H m "C6H5"
|
|
11
|
+
# }
|
|
12
|
+
#
|
|
13
|
+
# spectrum[ir]{
|
|
14
|
+
# 3300: broad "O-H stretch"
|
|
15
|
+
# }
|
|
16
|
+
#
|
|
17
|
+
# spectrum[ms]{
|
|
18
|
+
# 18: 100% "M+"
|
|
19
|
+
# }
|
|
20
|
+
class Spectrum < Node
|
|
21
|
+
attr_accessor :type, :params, :peaks
|
|
22
|
+
|
|
23
|
+
def initialize(type: nil, params: {}, peaks: [])
|
|
24
|
+
@type = type
|
|
25
|
+
@params = params
|
|
26
|
+
@peaks = peaks
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def value_attributes
|
|
30
|
+
{ type: type, params: params, peaks: peaks }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def children
|
|
34
|
+
[]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def diagnostic_label
|
|
38
|
+
"Spectrum(#{type || 'unknown'})"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
"spectrum[#{type}](#{params.map { |k, v| "#{k}=#{v}" }.join(',')})"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/asciichem/model.rb
CHANGED
|
@@ -19,6 +19,7 @@ module AsciiChem
|
|
|
19
19
|
autoload :Node, "asciichem/model/node"
|
|
20
20
|
autoload :Reaction, "asciichem/model/reaction"
|
|
21
21
|
autoload :ReactionCascade, "asciichem/model/reaction_cascade"
|
|
22
|
+
autoload :Spectrum, "asciichem/model/spectrum"
|
|
22
23
|
autoload :Text, "asciichem/model/text"
|
|
23
24
|
end
|
|
24
25
|
end
|
data/lib/asciichem/transform.rb
CHANGED
|
@@ -151,10 +151,24 @@ module AsciiChem
|
|
|
151
151
|
# Grammar captures crystal_name, crystal_params, and crystal_body
|
|
152
152
|
# as optional strings. CrystalBuilder parses them into the model.
|
|
153
153
|
|
|
154
|
-
rule(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
rule(crystal_node: subtree(:data)) do
|
|
155
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
156
|
+
CrystalBuilder.new(
|
|
157
|
+
hash[:crystal_name],
|
|
158
|
+
hash[:crystal_params],
|
|
159
|
+
hash[:crystal_body]
|
|
160
|
+
).build
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# -- spectra --------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
rule(spectrum_node: subtree(:data)) do
|
|
166
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
167
|
+
SpectrumBuilder.new(
|
|
168
|
+
hash[:spectrum_type],
|
|
169
|
+
hash[:spectrum_params],
|
|
170
|
+
hash[:spectrum_body]
|
|
171
|
+
).build
|
|
158
172
|
end
|
|
159
173
|
|
|
160
174
|
# -- internal helpers ------------------------------------------------
|
|
@@ -215,6 +229,69 @@ module AsciiChem
|
|
|
215
229
|
end
|
|
216
230
|
end
|
|
217
231
|
|
|
232
|
+
# Builds a Spectrum from parsed grammar captures. Parses peak
|
|
233
|
+
# lines from the body string.
|
|
234
|
+
class SpectrumBuilder
|
|
235
|
+
def initialize(type_str, params_str, body_str)
|
|
236
|
+
@type = strip_value(type_str)
|
|
237
|
+
@params_str = strip_value(params_str)
|
|
238
|
+
@body_str = strip_value(body_str)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build
|
|
242
|
+
Model::Spectrum.new(
|
|
243
|
+
type: @type,
|
|
244
|
+
params: parse_params(@params_str),
|
|
245
|
+
peaks: parse_peaks(@body_str)
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
def strip_value(value)
|
|
252
|
+
return nil if value.nil?
|
|
253
|
+
|
|
254
|
+
s = value.to_s.strip
|
|
255
|
+
s.empty? ? nil : s
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def parse_params(str)
|
|
259
|
+
return {} unless str
|
|
260
|
+
|
|
261
|
+
str.split(',').each_with_object({}) do |pair, memo|
|
|
262
|
+
key, val = pair.strip.split('=', 2)
|
|
263
|
+
memo[key] = val&.strip if key
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def parse_peaks(str)
|
|
268
|
+
return [] unless str
|
|
269
|
+
|
|
270
|
+
str.split("\n").filter_map { |line| parse_peak(line.strip) }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def parse_peak(line)
|
|
274
|
+
return nil if line.empty?
|
|
275
|
+
|
|
276
|
+
assignment = nil
|
|
277
|
+
match = line.match(/"([^"]*)"/)
|
|
278
|
+
if match
|
|
279
|
+
assignment = match[1]
|
|
280
|
+
line = line.sub(/"[^"]*"/, '').strip
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
pos, rest = line.split(':', 2)
|
|
284
|
+
tokens = rest&.strip&.split(/\s+/) || []
|
|
285
|
+
|
|
286
|
+
{
|
|
287
|
+
position: pos&.strip,
|
|
288
|
+
intensity: tokens[0],
|
|
289
|
+
multiplicity: tokens[1],
|
|
290
|
+
assignment: assignment
|
|
291
|
+
}
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
218
295
|
# Strips the surrounding `"..."` quotes from a quoted text match.
|
|
219
296
|
# Used by both `text_run` and `group_text_run` rules so the
|
|
220
297
|
# model never carries the delimiters — the formatter re-adds them
|
data/lib/asciichem/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asciichem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -166,6 +166,7 @@ files:
|
|
|
166
166
|
- lib/asciichem/model/node.rb
|
|
167
167
|
- lib/asciichem/model/reaction.rb
|
|
168
168
|
- lib/asciichem/model/reaction_cascade.rb
|
|
169
|
+
- lib/asciichem/model/spectrum.rb
|
|
169
170
|
- lib/asciichem/model/text.rb
|
|
170
171
|
- lib/asciichem/model_adapter.rb
|
|
171
172
|
- lib/asciichem/model_adapter/from_canonical.rb
|