postscript 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 657b74cfdae6702c2d5aa7bc8f6fcc4d80c4319f38806b2d1a4c0f48d9d050e9
4
- data.tar.gz: f13ac7de23328c6a211cd40e915942ab4228bdb12bd0cda6f565f557547fda20
3
+ metadata.gz: f7ba0b3e5f7cfe5f693b7f2eb22e2d6a79e91842985a0a33453fc36c1c005eea
4
+ data.tar.gz: a7c4e82279cac62a9073b83feed535137e8cc002c8e1dfa0356576b9c57b5797
5
5
  SHA512:
6
- metadata.gz: ca8e96d0ad7ca8b75f0b41690ed71e9b0e55167f1326db5cce018ae41f49a0e104b5fd77d588d4bfe5aa0ad41ebaa7eccb166b773db5e2b1dd5312a0f90063bd
7
- data.tar.gz: 43a30efc33221687d6658e4fd61dbe4b97b9851991a8a345ba14b68f7e01e11c164d9c60c5773d0ae796777e64f4e7998f4884eecd609b9b33aee921a515ad78
6
+ metadata.gz: bbea9ef38cedb6ca0be62c352fa9f2892ac2c6d942410e08b1cd03200b05bb9fa386bf6d6dd7fda1cfe88f7436375c5bf75fac081894650ae95a52e73de12530
7
+ data.tar.gz: f6eb5a058243de2b3d4bff21ba33c27ff1ea477fca54279e89de22ab7deb4935ef6729fec726e8d2f78f9a19f15f999ad76e60f3de1b50ed77d697afa1bff885
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ inherit_from:
4
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/main/ci/rubocop.yml
5
+ - .rubocop_todo.yml
6
+
7
+ inherit_mode:
8
+ merge:
9
+ - Exclude
10
+
11
+ # local repo-specific modifications
12
+ # ...
13
+ plugins:
14
+ - rubocop-rspec
15
+ - rubocop-performance
16
+ - rubocop-rake
17
+
18
+ AllCops:
19
+ TargetRubyVersion: 3.0
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,323 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-07-27 10:07:24 UTC using RuboCop version 1.88.2.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 16
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
12
+ # SupportedStyles: with_first_argument, with_fixed_indentation
13
+ Layout/ArgumentAlignment:
14
+ Exclude:
15
+ - 'lib/postscript/color.rb'
16
+ - 'lib/postscript/format_number.rb'
17
+ - 'lib/postscript/model/operators/control_flow.rb'
18
+ - 'lib/postscript/serializer.rb'
19
+ - 'lib/postscript/source/ast_builder.rb'
20
+ - 'spec/postscript/color_spec.rb'
21
+ - 'spec/postscript/serializer_spec.rb'
22
+
23
+ # Offense count: 2
24
+ # This cop supports safe autocorrection (--autocorrect).
25
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
26
+ # SupportedStyles: with_first_element, with_fixed_indentation
27
+ Layout/ArrayAlignment:
28
+ Exclude:
29
+ - 'lib/postscript/source/lexer.rb'
30
+
31
+ # Offense count: 1
32
+ # This cop supports safe autocorrection (--autocorrect).
33
+ # Configuration parameters: IndentationWidth.
34
+ Layout/AssignmentIndentation:
35
+ Exclude:
36
+ - 'lib/postscript/source/ast_builder.rb'
37
+
38
+ # Offense count: 5
39
+ # This cop supports safe autocorrection (--autocorrect).
40
+ # Configuration parameters: EnforcedStyleAlignWith.
41
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
42
+ Layout/BlockAlignment:
43
+ Exclude:
44
+ - 'lib/postscript/serializer.rb'
45
+ - 'lib/postscript/source/ast_builder.rb'
46
+ - 'spec/postscript/format_number_spec.rb'
47
+
48
+ # Offense count: 5
49
+ # This cop supports safe autocorrection (--autocorrect).
50
+ Layout/BlockEndNewline:
51
+ Exclude:
52
+ - 'lib/postscript/serializer.rb'
53
+ - 'lib/postscript/source/ast_builder.rb'
54
+ - 'spec/postscript/format_number_spec.rb'
55
+
56
+ # Offense count: 1
57
+ # This cop supports safe autocorrection (--autocorrect).
58
+ # Configuration parameters: IndentationWidth.
59
+ Layout/ClosingParenthesisIndentation:
60
+ Exclude:
61
+ - 'spec/postscript/serializer_spec.rb'
62
+
63
+ # Offense count: 1
64
+ # This cop supports safe autocorrection (--autocorrect).
65
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
66
+ # SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
67
+ Layout/FirstArgumentIndentation:
68
+ Exclude:
69
+ - 'spec/postscript/serializer_spec.rb'
70
+
71
+ # Offense count: 8
72
+ # This cop supports safe autocorrection (--autocorrect).
73
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
74
+ # SupportedHashRocketStyles: key, separator, table
75
+ # SupportedColonStyles: key, separator, table
76
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
77
+ Layout/HashAlignment:
78
+ Exclude:
79
+ - 'lib/postscript/model/operators/control_flow.rb'
80
+ - 'lib/postscript/source/lexer.rb'
81
+ - 'spec/postscript/serializer_spec.rb'
82
+
83
+ # Offense count: 10
84
+ # This cop supports safe autocorrection (--autocorrect).
85
+ # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
86
+ # SupportedStylesAlignWith: start_of_line, relative_to_receiver
87
+ Layout/IndentationWidth:
88
+ Exclude:
89
+ - 'lib/postscript/serializer.rb'
90
+ - 'lib/postscript/source/ast_builder.rb'
91
+ - 'spec/postscript/format_number_spec.rb'
92
+
93
+ # Offense count: 10
94
+ # This cop supports safe autocorrection (--autocorrect).
95
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
96
+ # SupportedStyles: aligned, indented
97
+ Layout/LineEndStringConcatenationIndentation:
98
+ Exclude:
99
+ - 'lib/postscript/serializer.rb'
100
+
101
+ # Offense count: 51
102
+ # This cop supports safe autocorrection (--autocorrect).
103
+ # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
104
+ # URISchemes: http, https
105
+ Layout/LineLength:
106
+ Exclude:
107
+ - 'lib/postscript/color.rb'
108
+ - 'lib/postscript/format_number.rb'
109
+ - 'lib/postscript/model/operators/control_flow.rb'
110
+ - 'lib/postscript/serializer.rb'
111
+ - 'lib/postscript/source/ast_builder.rb'
112
+ - 'lib/postscript/source/lexer.rb'
113
+ - 'lib/postscript/source/operand_stack.rb'
114
+ - 'postscript.gemspec'
115
+ - 'spec/postscript/color_spec.rb'
116
+ - 'spec/postscript/format_number_spec.rb'
117
+ - 'spec/postscript/model/operator_spec.rb'
118
+ - 'spec/postscript/serializer_spec.rb'
119
+ - 'spec/postscript/source/ast_builder_spec.rb'
120
+
121
+ # Offense count: 1
122
+ # This cop supports safe autocorrection (--autocorrect).
123
+ # Configuration parameters: EnforcedStyle.
124
+ # SupportedStyles: symmetrical, new_line, same_line
125
+ Layout/MultilineMethodCallBraceLayout:
126
+ Exclude:
127
+ - 'spec/postscript/serializer_spec.rb'
128
+
129
+ # Offense count: 3
130
+ # This cop supports safe autocorrection (--autocorrect).
131
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
132
+ # SupportedStyles: aligned, indented, indented_relative_to_receiver
133
+ Layout/MultilineMethodCallIndentation:
134
+ Exclude:
135
+ - 'lib/postscript/serializer.rb'
136
+
137
+ # Offense count: 18
138
+ # This cop supports safe autocorrection (--autocorrect).
139
+ # Configuration parameters: AllowInHeredoc.
140
+ Layout/TrailingWhitespace:
141
+ Exclude:
142
+ - 'lib/postscript/color.rb'
143
+ - 'lib/postscript/format_number.rb'
144
+ - 'lib/postscript/model/operators/control_flow.rb'
145
+ - 'lib/postscript/serializer.rb'
146
+ - 'lib/postscript/source/ast_builder.rb'
147
+ - 'lib/postscript/source/lexer.rb'
148
+ - 'spec/postscript/color_spec.rb'
149
+ - 'spec/postscript/serializer_spec.rb'
150
+
151
+ # Offense count: 4
152
+ # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
153
+ Lint/DuplicateBranch:
154
+ Exclude:
155
+ - 'lib/postscript/color.rb'
156
+ - 'lib/postscript/serializer.rb'
157
+ - 'lib/postscript/source/ast_builder.rb'
158
+
159
+ # Offense count: 87
160
+ # Configuration parameters: AllowedParentClasses.
161
+ Lint/MissingSuper:
162
+ Exclude:
163
+ - 'lib/postscript/model/operators.rb'
164
+ - 'lib/postscript/model/operators/arithmetic.rb'
165
+ - 'lib/postscript/model/operators/boolean.rb'
166
+ - 'lib/postscript/model/operators/color.rb'
167
+ - 'lib/postscript/model/operators/container.rb'
168
+ - 'lib/postscript/model/operators/control_flow.rb'
169
+ - 'lib/postscript/model/operators/dictionary.rb'
170
+ - 'lib/postscript/model/operators/font.rb'
171
+ - 'lib/postscript/model/operators/graphics_state.rb'
172
+ - 'lib/postscript/model/operators/path.rb'
173
+ - 'lib/postscript/model/operators/stack.rb'
174
+ - 'lib/postscript/model/operators/transformations.rb'
175
+
176
+ # Offense count: 1
177
+ Lint/UselessConstantScoping:
178
+ Exclude:
179
+ - 'lib/postscript/source/lexer.rb'
180
+
181
+ # Offense count: 12
182
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
183
+ Metrics/AbcSize:
184
+ Exclude:
185
+ - 'lib/postscript/color.rb'
186
+ - 'lib/postscript/matrix.rb'
187
+ - 'lib/postscript/serializer.rb'
188
+ - 'lib/postscript/source/ast_builder.rb'
189
+ - 'lib/postscript/source/lexer.rb'
190
+
191
+ # Offense count: 7
192
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
193
+ Metrics/CyclomaticComplexity:
194
+ Exclude:
195
+ - 'lib/postscript/color.rb'
196
+ - 'lib/postscript/serializer.rb'
197
+ - 'lib/postscript/source/ast_builder.rb'
198
+ - 'lib/postscript/source/lexer.rb'
199
+
200
+ # Offense count: 17
201
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
202
+ Metrics/MethodLength:
203
+ Max: 36
204
+
205
+ # Offense count: 4
206
+ # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
207
+ Metrics/ParameterLists:
208
+ Max: 10
209
+
210
+ # Offense count: 2
211
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
212
+ Metrics/PerceivedComplexity:
213
+ Exclude:
214
+ - 'lib/postscript/serializer.rb'
215
+ - 'lib/postscript/source/lexer.rb'
216
+
217
+ # Offense count: 97
218
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
219
+ # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
220
+ Naming/MethodParameterName:
221
+ Exclude:
222
+ - 'lib/postscript/color.rb'
223
+ - 'lib/postscript/matrix.rb'
224
+ - 'lib/postscript/model/operators/font.rb'
225
+ - 'lib/postscript/model/operators/path.rb'
226
+ - 'lib/postscript/model/operators/transformations.rb'
227
+ - 'lib/postscript/serializer.rb'
228
+
229
+ # Offense count: 2
230
+ # This cop supports unsafe autocorrection (--autocorrect-all).
231
+ # Configuration parameters: SafeMultiline.
232
+ Performance/DeletePrefix:
233
+ Exclude:
234
+ - 'lib/postscript/source/ast_builder.rb'
235
+
236
+ # Offense count: 2
237
+ RSpec/BeforeAfterAll:
238
+ Exclude:
239
+ - 'spec/postscript/model/operator_spec.rb'
240
+ - 'spec/postscript/model/operators_spec.rb'
241
+
242
+ # Offense count: 28
243
+ # This cop supports unsafe autocorrection (--autocorrect-all).
244
+ # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants.
245
+ # SupportedStyles: described_class, explicit
246
+ RSpec/DescribedClass:
247
+ Exclude:
248
+ - 'spec/postscript/color_spec.rb'
249
+ - 'spec/postscript/format_number_spec.rb'
250
+ - 'spec/postscript/matrix_spec.rb'
251
+ - 'spec/postscript/model/operators_spec.rb'
252
+
253
+ # Offense count: 6
254
+ # Configuration parameters: CountAsOne.
255
+ RSpec/ExampleLength:
256
+ Max: 14
257
+
258
+ # Offense count: 1
259
+ RSpec/IdenticalEqualityAssertion:
260
+ Exclude:
261
+ - 'spec/postscript/color_spec.rb'
262
+
263
+ # Offense count: 26
264
+ RSpec/MultipleExpectations:
265
+ Max: 6
266
+
267
+ # Offense count: 6
268
+ # This cop supports safe autocorrection (--autocorrect).
269
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
270
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
271
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
272
+ # FunctionalMethods: let, let!, subject, watch
273
+ # AllowedMethods: lambda, proc, it
274
+ Style/BlockDelimiters:
275
+ Exclude:
276
+ - 'lib/postscript/serializer.rb'
277
+ - 'lib/postscript/source/ast_builder.rb'
278
+ - 'spec/postscript/format_number_spec.rb'
279
+
280
+ # Offense count: 3
281
+ # This cop supports safe autocorrection (--autocorrect).
282
+ # Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns.
283
+ # SupportedStyles: annotated, template, unannotated
284
+ Style/FormatStringToken:
285
+ EnforcedStyle: unannotated
286
+
287
+ # Offense count: 1
288
+ # This cop supports safe autocorrection (--autocorrect).
289
+ Style/KeywordArgumentsMerging:
290
+ Exclude:
291
+ - 'lib/postscript/model/program.rb'
292
+
293
+ # Offense count: 4
294
+ # This cop supports safe autocorrection (--autocorrect).
295
+ Style/MultilineIfModifier:
296
+ Exclude:
297
+ - 'lib/postscript/format_number.rb'
298
+ - 'lib/postscript/serializer.rb'
299
+ - 'lib/postscript/source/ast_builder.rb'
300
+
301
+ # Offense count: 398
302
+ # This cop supports safe autocorrection (--autocorrect).
303
+ # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
304
+ # SupportedStyles: single_quotes, double_quotes
305
+ Style/StringLiterals:
306
+ Enabled: false
307
+
308
+ # Offense count: 5
309
+ # This cop supports safe autocorrection (--autocorrect).
310
+ # Configuration parameters: EnforcedStyleForMultiline.
311
+ # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
312
+ Style/TrailingCommaInArguments:
313
+ Exclude:
314
+ - 'lib/postscript/color.rb'
315
+ - 'spec/postscript/serializer_spec.rb'
316
+
317
+ # Offense count: 3
318
+ # This cop supports safe autocorrection (--autocorrect).
319
+ # Configuration parameters: EnforcedStyleForMultiline.
320
+ # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
321
+ Style/TrailingCommaInHashLiteral:
322
+ Exclude:
323
+ - 'lib/postscript/matrix.rb'
data/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to the postscript gem will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ### Added — 2026-07-28
8
+
9
+ **0.1.0 — initial release.**
10
+
11
+ The postscript gem is extracted from postsvg 0.3.0 as a standalone,
12
+ pure-Ruby PostScript (PS) / EPS parser, typed domain model, and
13
+ serializer. Independently reusable for any tool that needs to read,
14
+ write, or transform PostScript source.
15
+
16
+ * `Postscript::Source::Lexer` — comment-aware state-machine lexer
17
+ (preserves `%` chars inside string literals, which the legacy
18
+ postsvg Tokenizer's `gsub`-based comment stripping corrupted).
19
+ * `Postscript::Source::AstBuilder` — tokens → `Model::Program` with
20
+ per-operator `consumes` / `produces` stack-arity tracking so
21
+ chained operators parse cleanly.
22
+ * `Postscript::Source::OperandStack` — permissive pop (returns
23
+ `Computed` sentinel on underflow) so procedure bodies parse
24
+ without runtime context.
25
+ * `Postscript::Model::*` — typed PS records: `Program`,
26
+ `Literals::*` (Number, Name, StringLiteral, HexLiteral,
27
+ ArrayLiteral, Procedure, Dictionary), `Operators::*` (120 PS
28
+ operators across 13 PLRM chapters).
29
+ * `Postscript::Serializer` — `Model::Program` → PS / EPS source
30
+ text with DSC-conformant header (BoundingBox, HiResBoundingBox,
31
+ LanguageLevel, EPSF-3.0 marker). Idempotent round-trip
32
+ (serialize(parse(serialize(parse(x)))) == serialize(parse(x))).
33
+ * `Postscript::Matrix`, `Postscript::Color`,
34
+ `Postscript::FormatNumber` — general-purpose value types
35
+ (2D affine transforms, RGB/Gray/CMYK value object, PS-safe
36
+ float formatting).
37
+ * `Postscript::Error` hierarchy — typed errors for every failure
38
+ mode (ParseError, LexError, SyntaxError, StackUnderflowError,
39
+ UndefinedOperatorError, RecursionLimitError, etc.) plus
40
+ control-flow signals (ExitSignal, QuitSignal).
41
+ * Top-level convenience API: `Postscript.parse`,
42
+ `Postscript.serialize`, `Postscript.tokenize`.
43
+ * `exe/postscript` CLI: `parse`, `serialize`, `tokenize`,
44
+ `version` subcommands.
45
+
46
+ ### Architecture
47
+
48
+ Mirrors the `emf`/`emfsvg` split: a separate gem owns format
49
+ parsing/model/serializer; the consumer gem (postsvg) depends on it
50
+ and adds the rendering/conversion layer. This unlocks:
51
+
52
+ * Independent reuse (PS validators, formatters, future PS↔PDF).
53
+ * Single-responsibility per gem.
54
+ * Clean dependency direction (postsvg depends on postscript, never
55
+ the other way).
56
+
57
+ ### Code quality
58
+
59
+ - Pure Ruby autoload (no `require_relative` in lib/).
60
+ - No `respond_to?` for type checks, no `instance_variable_set/get`.
61
+ - No `send` to private methods.
62
+ - `# frozen_string_literal: true` on every `.rb` file.
63
+ - 149 specs, 0 failures.
64
+ - Round-trip property spec covering 6 representative PS programs.
65
+
66
+ ### Backwards compatibility
67
+
68
+ postsvg's BC aliases (`Postsvg::Source = Postscript::Source`,
69
+ `Postsvg::Matrix = Postscript::Matrix`, etc.) continue to expose
70
+ the PS-side constants under the `Postsvg::*` namespace for
71
+ existing user code.
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, Ribose Inc.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/exe/postscript ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "postscript"
5
+ require "postscript/cli"
6
+
7
+ Postscript::CLI.start(ARGV)
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Postscript
6
+ # CLI for the postscript gem. Available via the +postscript+
7
+ # executable. Provides parse/serialize/tokenize/version
8
+ # subcommands for ad-hoc PS source inspection without writing
9
+ # Ruby.
10
+ class CLI < Thor
11
+ package_name "postscript"
12
+
13
+ desc "parse INPUT", "Parse a PS/EPS file and dump the AST shape"
14
+ long_desc <<~DESC
15
+ Read INPUT, lex + parse to a Model::Program, and print one
16
+ line per body statement showing its class. Useful for
17
+ verifying that the parser sees the structure you expect.
18
+ DESC
19
+ def parse(input_path)
20
+ source = read_input(input_path)
21
+ program = Postscript.parse(source)
22
+ say "Program: #{program.body.length} statement(s)"
23
+ say "BoundingBox: #{program.header.bounding_box.inspect}" if program.header.bounding_box
24
+ program.body.each do |node|
25
+ say " #{node.class}"
26
+ end
27
+ rescue Postscript::Error => e
28
+ say "Parse error: #{e.message}", :red
29
+ exit 1
30
+ end
31
+
32
+ desc "serialize INPUT [OUTPUT]", "Round-trip a PS file through parse + serialize"
33
+ long_desc <<~DESC
34
+ Read INPUT, parse to a Model::Program, serialize back to PS
35
+ source. Writes to OUTPUT if given, otherwise stdout.
36
+ DESC
37
+ option :eps, type: :boolean, default: false, desc: "Emit EPSF-3.0 header"
38
+ def serialize(input_path, output_path = nil)
39
+ source = read_input(input_path)
40
+ program = Postscript.parse(source)
41
+ output = Postscript.serialize(program, eps: options[:eps])
42
+ if output_path
43
+ File.write(output_path, output)
44
+ say "Wrote #{output_path} (#{output.bytesize} bytes)", :green
45
+ else
46
+ puts output
47
+ end
48
+ rescue Postscript::Error => e
49
+ say "Error: #{e.message}", :red
50
+ exit 1
51
+ end
52
+
53
+ desc "tokenize INPUT", "Lex a PS file and dump the token stream"
54
+ long_desc <<~DESC
55
+ Read INPUT and dump one line per token showing type, value,
56
+ and source position. Useful for debugging lexer issues.
57
+ DESC
58
+ def tokenize(input_path)
59
+ source = read_input(input_path)
60
+ Postscript.tokenize(source).each do |token|
61
+ puts "%4d:%-4d %-12s %s" % [token.line, token.column, token.type, token.value]
62
+ end
63
+ rescue Postscript::Error => e
64
+ say "Lex error: #{e.message}", :red
65
+ exit 1
66
+ end
67
+
68
+ desc "version", "Show the postscript gem version"
69
+ def version
70
+ say "postscript #{Postscript::VERSION}"
71
+ end
72
+
73
+ private
74
+
75
+ def read_input(path)
76
+ unless File.exist?(path)
77
+ say "Error: input file '#{path}' not found", :red
78
+ exit 1
79
+ end
80
+ File.read(path)
81
+ end
82
+ end
83
+ end
@@ -10,7 +10,7 @@ module Postscript
10
10
  attr_reader :red, :green, :blue
11
11
 
12
12
  def self.clamp_byte(value)
13
- Integer === value ? value.clamp(0, 255) : value.to_i.clamp(0, 255)
13
+ value.is_a?(Integer) ? value.clamp(0, 255) : value.to_i.clamp(0, 255)
14
14
  end
15
15
 
16
16
  def self.scale_unit_to_byte(value)
@@ -58,9 +58,11 @@ module Postscript
58
58
  hex = ::Regexp.last_match(1)
59
59
  new((hex[0] * 2).to_i(16), (hex[1] * 2).to_i(16), (hex[2] * 2).to_i(16))
60
60
  when /\argb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/
61
- new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i)
61
+ new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i,
62
+ ::Regexp.last_match(3).to_i)
62
63
  when /\argb\(\s*(\d+)\s+(\d+)\s+(\d+)/
63
- new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i)
64
+ new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i,
65
+ ::Regexp.last_match(3).to_i)
64
66
  when "none", "transparent"
65
67
  nil
66
68
  else
@@ -123,7 +125,7 @@ module Postscript
123
125
  "teal" => [0, 128, 128], "lime" => [0, 255, 0],
124
126
  "aqua" => [0, 255, 255], "fuchsia" => [255, 0, 255],
125
127
  "orange" => [255, 165, 0], "pink" => [255, 192, 203],
126
- "brown" => [165, 42, 42],
128
+ "brown" => [165, 42, 42]
127
129
  }.freeze
128
130
 
129
131
  BLACK = Color.new(0, 0, 0).freeze
@@ -11,7 +11,10 @@ module Postscript
11
11
  module_function
12
12
 
13
13
  def call(value)
14
- raise ArgumentError, "non-finite number: #{value.inspect}" unless value.finite?
14
+ unless value.finite?
15
+ raise ArgumentError,
16
+ "non-finite number: #{value.inspect}"
17
+ end
15
18
 
16
19
  normalized = value.zero? ? 0.0 : value
17
20
  return normalized.to_i.to_s if normalized == normalized.to_i
@@ -49,7 +49,7 @@ module Postscript
49
49
  # output of +findfont+, +add+, +dup+). Carries no semantic value;
50
50
  # its purpose is to keep the parse-stack shape in sync with the
51
51
  # runtime stack so chained operators parse cleanly.
52
- class Computed < ::Struct.new(:operator_keyword, keyword_init: true)
52
+ Computed = ::Struct.new(:operator_keyword, keyword_init: true) do
53
53
  def to_s
54
54
  "<computed:#{operator_keyword}>"
55
55
  end