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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d401ee7215f54961b4126b1da5451a2e71ac316c05e3864d199c5482e28d366
4
- data.tar.gz: c132a8c376dd7485bf0319f8d971d89cb952c27d9f2466c0f3af4af945b0ca4b
3
+ metadata.gz: 1272affa5aa88924aaa06424dbc59bb115255963b675f596b58fd9588cd94fdc
4
+ data.tar.gz: bc6e6da1096c7a1c4e404d23036ec9513eae0f2866feeff9bd54a31c04540473
5
5
  SHA512:
6
- metadata.gz: ab9ee8f2410f46c659cc97426a7272c511e983b601cbf043809c5b657621f0d7a48e79f17c2c4f79707cfebaf265e963077a5f636d8e03c87be2942e4fddc27e
7
- data.tar.gz: 1cf4d97ef6459a29b9d7f05ddbd8b8b1db0ca512f547cb48badd7ebb5abc42f9c70c98cdade086af9dedf48f2e16c96ddd199097ee90c3e2ba56984e66a91aa7
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)
@@ -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
@@ -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
@@ -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(crystal_name: subtree(:name),
155
- crystal_params: subtree(:params),
156
- crystal_body: subtree(:body)) do
157
- CrystalBuilder.new(name, params, body).build
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsciiChem
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  end
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.0
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