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.
@@ -19,7 +19,10 @@ module Postscript
19
19
  attr_reader :program, :eps, :creator
20
20
 
21
21
  def initialize(program, eps:, creator:)
22
- raise ArgumentError, "program must be a Model::Program" unless program.is_a?(Model::Program)
22
+ unless program.is_a?(Model::Program)
23
+ raise ArgumentError,
24
+ "program must be a Model::Program"
25
+ end
23
26
 
24
27
  @program = program
25
28
  @eps = eps
@@ -30,11 +33,17 @@ module Postscript
30
33
  buffer = String.new(capacity: 4096)
31
34
  emit_header(buffer)
32
35
  emit_body(buffer)
33
- buffer << "showpage\n"
36
+ buffer << "showpage\n" unless body_has_showpage?
34
37
  buffer << "%%EOF\n"
35
38
  buffer
36
39
  end
37
40
 
41
+ # Returns true if the program body already contains a +showpage+
42
+ # operator, so the serializer doesn't append a duplicate.
43
+ def body_has_showpage?
44
+ program.body.any? { |s| s.is_a?(Model::Operators::Device::Showpage) }
45
+ end
46
+
38
47
  # All methods below this point are public. They are intentionally
39
48
  # exposed (prefixed +emit_+) so dispatch can reach them via
40
49
  # +method_defined?+ without +respond_to?+.
@@ -45,11 +54,17 @@ module Postscript
45
54
  buffer << "%%Creator: #{creator}\n"
46
55
  bbox = program.header.bounding_box
47
56
  if bbox && !bbox.empty?
48
- buffer << "%%BoundingBox: " << bbox.map { |v| FormatNumber.call(v) }.join(" ") << "\n"
57
+ buffer << "%%BoundingBox: " << bbox.map { |v|
58
+ FormatNumber.call(v)
59
+ }.join(" ") << "\n"
49
60
  end
50
- if program.header.title
51
- buffer << "%%Title: #{program.header.title}\n"
61
+ hires = program.header.hires_bounding_box
62
+ if hires && !hires.empty?
63
+ buffer << "%%HiResBoundingBox: " << hires.map { |v|
64
+ FormatNumber.call(v)
65
+ }.join(" ") << "\n"
52
66
  end
67
+ buffer << "%%Title: #{program.header.title}\n" if program.header.title
53
68
  buffer << "%%LanguageLevel: #{DEFAULT_LANGUAGE_LEVEL}\n"
54
69
  buffer << "%%EndComments\n"
55
70
  end
@@ -73,7 +88,10 @@ module Postscript
73
88
  buffer << "<" << statement.value << ">\n"
74
89
  when Model::Literals::ArrayLiteral
75
90
  buffer << "[ "
76
- statement.elements.each { |e| emit_inline(e, buffer); buffer << " " }
91
+ statement.elements.each do |e|
92
+ emit_inline(e, buffer)
93
+ buffer << " "
94
+ end
77
95
  buffer << "]\n"
78
96
  when Model::Literals::Procedure
79
97
  buffer << "{\n"
@@ -106,220 +124,22 @@ module Postscript
106
124
  end
107
125
  end
108
126
 
109
- # Dispatch table: emit_<keyword>. Falls back to the generic
110
- # operand+keyword form when no specific method exists.
127
+ # Dispatch: just emit the keyword. Operands are already in the
128
+ # program body as separate literal statements (added by the
129
+ # AstBuilder). This keeps round-trip idempotent: serialized
130
+ # output re-parses to an identical AST.
111
131
  def emit_operator(operator, buffer)
112
- method = :"emit_#{operator.visit_name}"
113
- if self.class.public_method_defined?(method)
114
- public_send(method, operator, buffer)
115
- else
116
- emit_generic_operator(operator, buffer)
117
- end
118
- end
119
-
120
- def emit_generic_operator(operator, buffer)
121
132
  buffer << operator.keyword << "\n"
122
133
  end
123
134
 
124
135
  # Path
125
- def emit_moveto(op, buf)
126
- buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} moveto\n"
127
- end
128
-
129
- def emit_rmoveto(op, buf)
130
- buf << "#{FormatNumber.call(op.dx)} #{FormatNumber.call(op.dy)} rmoveto\n"
131
- end
132
-
133
- def emit_lineto(op, buf)
134
- buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} lineto\n"
135
- end
136
-
137
- def emit_rlineto(op, buf)
138
- buf << "#{FormatNumber.call(op.dx)} #{FormatNumber.call(op.dy)} rlineto\n"
139
- end
140
-
141
- def emit_curveto(op, buf)
142
- buf << "#{FormatNumber.call(op.x1)} #{FormatNumber.call(op.y1)} " \
143
- "#{FormatNumber.call(op.x2)} #{FormatNumber.call(op.y2)} " \
144
- "#{FormatNumber.call(op.x3)} #{FormatNumber.call(op.y3)} curveto\n"
145
- end
146
-
147
- def emit_rcurveto(op, buf)
148
- buf << "#{FormatNumber.call(op.dx1)} #{FormatNumber.call(op.dy1)} " \
149
- "#{FormatNumber.call(op.dx2)} #{FormatNumber.call(op.dy2)} " \
150
- "#{FormatNumber.call(op.dx3)} #{FormatNumber.call(op.dy3)} rcurveto\n"
151
- end
152
-
153
- def emit_arc(op, buf)
154
- buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} " \
155
- "#{FormatNumber.call(op.radius)} " \
156
- "#{FormatNumber.call(op.angle1)} #{FormatNumber.call(op.angle2)} arc\n"
157
- end
158
-
159
- def emit_arcn(op, buf)
160
- buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} " \
161
- "#{FormatNumber.call(op.radius)} " \
162
- "#{FormatNumber.call(op.angle1)} #{FormatNumber.call(op.angle2)} arcn\n"
163
- end
164
-
165
- def emit_closepath(_op, buf)
166
- buf << "closepath\n"
167
- end
168
-
169
- def emit_newpath(_op, buf)
170
- buf << "newpath\n"
171
- end
172
-
173
- # Painting
174
- def emit_stroke(_op, buf) = buf << "stroke\n"
175
- def emit_fill(_op, buf) = buf << "fill\n"
176
- def emit_eofill(_op, buf) = buf << "eofill\n"
177
- def emit_clip(_op, buf) = buf << "clip\n"
178
- def emit_eoclip(_op, buf) = buf << "eoclip\n"
179
-
180
- # Color
181
- def emit_setrgbcolor(op, buf)
182
- buf << "#{FormatNumber.call(op.red)} #{FormatNumber.call(op.green)} #{FormatNumber.call(op.blue)} setrgbcolor\n"
183
- end
184
-
185
- def emit_setgray(op, buf)
186
- buf << "#{FormatNumber.call(op.gray)} setgray\n"
187
- end
188
-
189
- def emit_setcmykcolor(op, buf)
190
- buf << "#{FormatNumber.call(op.cyan)} #{FormatNumber.call(op.magenta)} " \
191
- "#{FormatNumber.call(op.yellow)} #{FormatNumber.call(op.key)} setcmykcolor\n"
192
- end
193
-
194
- def emit_sethsbcolor(op, buf)
195
- buf << "#{FormatNumber.call(op.hue)} #{FormatNumber.call(op.saturation)} " \
196
- "#{FormatNumber.call(op.brightness)} sethsbcolor\n"
197
- end
198
-
199
- # Graphics state
200
- def emit_gsave(_op, buf) = buf << "gsave\n"
201
- def emit_grestore(_op, buf) = buf << "grestore\n"
202
- def emit_grestoreall(_op, buf) = buf << "grestoreall\n"
203
-
204
- def emit_setlinewidth(op, buf)
205
- buf << "#{FormatNumber.call(op.width)} setlinewidth\n"
206
- end
207
-
208
- def emit_setlinecap(op, buf)
209
- buf << "#{op.cap_code} setlinecap\n"
210
- end
211
-
212
- def emit_setlinejoin(op, buf)
213
- buf << "#{op.join_code} setlinejoin\n"
214
- end
215
-
216
- def emit_setmiterlimit(op, buf)
217
- buf << "#{FormatNumber.call(op.limit)} setmiterlimit\n"
218
- end
219
-
220
- def emit_setdash(op, buf)
221
- pattern_str =
222
- case op.pattern
223
- when Array then "[#{op.pattern.map { |v| FormatNumber.call(v.to_f) }.join(' ')}]"
224
- when Numeric then FormatNumber.call(op.pattern.to_f)
225
- else "[]"
226
- end
227
- buf << "#{pattern_str} #{FormatNumber.call(op.offset)} setdash\n"
228
- end
229
-
230
- # Transformations
231
- def emit_translate(op, buf)
232
- buf << "#{FormatNumber.call(op.tx)} #{FormatNumber.call(op.ty)} translate\n"
233
- end
234
-
235
- def emit_scale(op, buf)
236
- buf << "#{FormatNumber.call(op.sx)} #{FormatNumber.call(op.sy)} scale\n"
237
- end
238
-
239
- def emit_rotate(op, buf)
240
- buf << "#{FormatNumber.call(op.angle)} rotate\n"
241
- end
242
-
243
- def emit_concat(op, buf)
244
- m = op.matrix
245
- arr = m.is_a?(Array) ? m : [m.a, m.b, m.c, m.d, m.e, m.f]
246
- buf << "[#{arr.map { |v| FormatNumber.call(v.to_f) }.join(' ')}] concat\n"
247
- end
248
-
249
136
  def escape_string(text)
250
- escaped = text.to_s.gsub(/[\(\)\\]/) { |c| "\\#{c}" }
251
- .gsub("\n", "\\n")
252
- .gsub("\r", "\\r")
253
- .gsub("\t", "\\t")
137
+ escaped = text.to_s.gsub(/[()\\]/) { |c| "\\#{c}" }
138
+ .gsub("\n", '\\n')
139
+ .gsub("\r", '\\r')
140
+ .gsub("\t", '\\t')
254
141
  "(#{escaped})"
255
142
  end
256
143
 
257
- # Font / text
258
- def emit_findfont(op, buf)
259
- name = font_name_value(op.name)
260
- buf << "/#{name} findfont\n"
261
- end
262
-
263
- def emit_scalefont(op, buf)
264
- buf << "#{FormatNumber.call(op.size)} scalefont\n"
265
- end
266
-
267
- def emit_setfont(_op, buf)
268
- buf << "setfont\n"
269
- end
270
-
271
- def emit_show(op, buf)
272
- buf << "#{escape_string(string_value(op.text))} show\n"
273
- end
274
-
275
- def emit_xyshow(_op, buf)
276
- buf << "% xyshow: per-glyph advances not serialized\n"
277
- end
278
-
279
- def emit_stringwidth(_op, buf)
280
- buf << "% stringwidth: no runtime result captured\n"
281
- end
282
-
283
- def emit_charpath(_op, buf)
284
- buf << "% charpath: requires font metrics\n"
285
- end
286
-
287
- # Container operators
288
- def emit_length(_op, buf) = buf << "length\n"
289
- def emit_get(_op, buf) = buf << "get\n"
290
- def emit_put(_op, buf) = buf << "put\n"
291
- def emit_getinterval(_op, buf) = buf << "getinterval\n"
292
- def emit_putinterval(_op, buf) = buf << "putinterval\n"
293
- def emit_forall(_op, buf) = buf << "forall\n"
294
- def emit_astore(_op, buf) = buf << "astore\n"
295
- def emit_search(_op, buf) = buf << "search\n"
296
- def emit_anchorsearch(_op, buf) = buf << "anchorsearch\n"
297
- def emit_token(_op, buf) = buf << "token\n"
298
- def emit_string(_op, buf) = buf << "string\n"
299
- def emit_cvs(_op, buf) = buf << "cvs\n"
300
-
301
- # Dictionary operators (currentdict etc.)
302
- def emit_currentdict(_op, buf) = buf << "currentdict\n"
303
- def emit_countdictstack(_op, buf) = buf << "countdictstack\n"
304
- def emit_dictstack(_op, buf) = buf << "dictstack\n"
305
- def emit_maxlength(_op, buf) = buf << "maxlength\n"
306
-
307
- def font_name_value(value)
308
- case value
309
- when Model::Literals::Name then value.value
310
- when Model::Literals::StringLiteral then value.value
311
- when String then value
312
- else value.to_s
313
- end
314
- end
315
-
316
- def string_value(value)
317
- case value
318
- when Model::Literals::StringLiteral then value.value
319
- when Model::Literals::HexLiteral then value.bytes
320
- when String then value
321
- else value.to_s
322
- end
323
- end
324
144
  end
325
145
  end
@@ -33,7 +33,10 @@ module Postscript
33
33
  private
34
34
 
35
35
  def consume_until(end_index, terminators: [], depth: 0)
36
- raise RecursionLimitError, "procedure depth exceeded #{MAX_PROC_DEPTH}" if depth > MAX_PROC_DEPTH
36
+ if depth > MAX_PROC_DEPTH
37
+ raise RecursionLimitError,
38
+ "procedure depth exceeded #{MAX_PROC_DEPTH}"
39
+ end
37
40
 
38
41
  i = 0
39
42
  collected = []
@@ -65,7 +68,8 @@ module Postscript
65
68
  append_statement(node, collected)
66
69
  1
67
70
  when :name
68
- node = Model::Literals::Name.new(token.value, literal: token.literal || false)
71
+ node = Model::Literals::Name.new(token.value,
72
+ literal: token.literal || false)
69
73
  @stack.push(node)
70
74
  append_statement(node, collected)
71
75
  1
@@ -101,11 +105,12 @@ module Postscript
101
105
  def build_number(token)
102
106
  raw = token.value
103
107
  value =
104
- if raw =~ /\A[-+]?\d+\z/
108
+ case raw
109
+ when /\A[-+]?\d+\z/
105
110
  raw.to_i
106
- elsif raw =~ /\A[-+]?\d+\.\d+\z/ || raw =~ /\A[-+]?(\.\d+|\d+\.)\z/
111
+ when /\A[-+]?\d+\.\d+\z/, /\A[-+]?(\.\d+|\d+\.)\z/
107
112
  raw.to_f
108
- elsif raw =~ /\A[-+]?(\d+\.?\d*|\.\d+)[eE][-+]?\d+\z/
113
+ when /\A[-+]?(\d+\.?\d*|\.\d+)[eE][-+]?\d+\z/
109
114
  raw.to_f
110
115
  else
111
116
  raw.to_f
@@ -129,9 +134,12 @@ module Postscript
129
134
 
130
135
  def consume_array(after:, collected:)
131
136
  start_index = find_index(after)
132
- body_tokens, end_index = match_until(start_index + 1, :array_close, :array_open)
137
+ body_tokens, end_index = match_until(start_index + 1, :array_close,
138
+ :array_open)
133
139
  body_nodes = sub_build(body_tokens)
134
- array = Model::Literals::ArrayLiteral.new(body_nodes.select { |n| literal?(n) })
140
+ array = Model::Literals::ArrayLiteral.new(body_nodes.select do |n|
141
+ literal?(n)
142
+ end)
135
143
  @stack.push(array)
136
144
  append_statement(array, collected)
137
145
  end_index - start_index + 1
@@ -139,7 +147,8 @@ module Postscript
139
147
 
140
148
  def consume_dict(after:, collected:)
141
149
  start_index = find_index(after)
142
- body_tokens, end_index = match_until(start_index + 1, :dict_close, :dict_open)
150
+ body_tokens, end_index = match_until(start_index + 1, :dict_close,
151
+ :dict_open)
143
152
  entries = {}
144
153
  pending_key = nil
145
154
  sub_nodes = sub_build(body_tokens)
@@ -221,13 +230,11 @@ module Postscript
221
230
  keyword = token.value
222
231
 
223
232
  # Resolve user-defined names to InvokeProcedure.
224
- if (proc_value = lookup_user_definition(keyword))
225
- if proc_value.is_a?(Model::Literals::Procedure)
226
- inv = Model::InvokeProcedure.new(name: keyword, procedure: proc_value)
227
- @body << inv
228
- collected << inv
229
- return 1
230
- end
233
+ if (proc_value = lookup_user_definition(keyword)) && proc_value.is_a?(Model::Literals::Procedure)
234
+ inv = Model::InvokeProcedure.new(name: keyword, procedure: proc_value)
235
+ @body << inv
236
+ collected << inv
237
+ return 1
231
238
  end
232
239
 
233
240
  operator_class = Model::Operators[keyword]
@@ -262,7 +269,8 @@ module Postscript
262
269
  def apply_side_effects(operator)
263
270
  case operator
264
271
  when Model::Operators::Dictionary::Def
265
- @dict_stack.last[operator.key.to_s.sub(/\A\//, "")] = operator.value
272
+ @dict_stack.last[operator.key.to_s.delete_prefix("/")] =
273
+ operator.value
266
274
  end
267
275
  end
268
276
 
@@ -278,14 +286,14 @@ module Postscript
278
286
  case text
279
287
  when /\ABoundingBox:\s*(-?[\d.eE+-]+)\s+(-?[\d.eE+-]+)\s+(-?[\d.eE+-]+)\s+(-?[\d.eE+-]+)\z/
280
288
  @header = @header.with(bounding_box: [
281
- ::Regexp.last_match(1).to_f, ::Regexp.last_match(2).to_f,
282
- ::Regexp.last_match(3).to_f, ::Regexp.last_match(4).to_f,
283
- ])
289
+ ::Regexp.last_match(1).to_f, ::Regexp.last_match(2).to_f,
290
+ ::Regexp.last_match(3).to_f, ::Regexp.last_match(4).to_f
291
+ ])
284
292
  when /\AHiResBoundingBox:\s*(-?[\d.eE+-]+)\s+(-?[\d.eE+-]+)\s+(-?[\d.eE+-]+)\s+(-?[\d.eE+-]+)\z/
285
293
  @header = @header.with(hires_bounding_box: [
286
- ::Regexp.last_match(1).to_f, ::Regexp.last_match(2).to_f,
287
- ::Regexp.last_match(3).to_f, ::Regexp.last_match(4).to_f,
288
- ])
294
+ ::Regexp.last_match(1).to_f, ::Regexp.last_match(2).to_f,
295
+ ::Regexp.last_match(3).to_f, ::Regexp.last_match(4).to_f
296
+ ])
289
297
  when /\ATitle:\s*(.+)\z/
290
298
  @header = @header.with(title: ::Regexp.last_match(1))
291
299
  when /\ACreator:\s*(.+)\z/
@@ -121,18 +121,15 @@ module Postscript
121
121
  start_pos = @position
122
122
  start_line = @line
123
123
  start_col = @column
124
- until eos? || @source.getbyte(@position) == 10
125
- advance
126
- end
124
+ advance until eos? || @source.getbyte(@position) == 10
127
125
  text = @source.byteslice(start_pos, @position - start_pos)
128
- @tokens << Model::Token.new(:dsc, text.chomp, line: start_line, column: start_col)
126
+ @tokens << Model::Token.new(:dsc, text.chomp, line: start_line,
127
+ column: start_col)
129
128
  @state = :top
130
129
  end
131
130
 
132
131
  def scan_comment_body
133
- until eos? || @source.getbyte(@position) == 10
134
- advance
135
- end
132
+ advance until eos? || @source.getbyte(@position) == 10
136
133
  @state = :top
137
134
  end
138
135
 
@@ -148,16 +145,14 @@ module Postscript
148
145
  def scan_string_body
149
146
  until eos?
150
147
  byte = @source.getbyte(@position)
148
+ advance
151
149
  case byte
152
150
  when 92 # backslash
153
- advance
154
151
  handle_escape
155
152
  when 40 # (
156
- advance
157
153
  @string_depth += 1
158
154
  @string_start[:bytes] << "("
159
155
  when 41 # )
160
- advance
161
156
  @string_depth -= 1
162
157
  if @string_depth.zero?
163
158
  emit_string
@@ -165,12 +160,12 @@ module Postscript
165
160
  end
166
161
  @string_start[:bytes] << ")"
167
162
  else
168
- advance
169
163
  @string_start[:bytes] << byte.chr
170
164
  end
171
165
  end
172
166
  raise LexError.new("unterminated string literal",
173
- source_position: [@string_start[:line], @string_start[:column]])
167
+ source_position: [@string_start[:line],
168
+ @string_start[:column]])
174
169
  end
175
170
 
176
171
  def handle_escape
@@ -193,17 +188,17 @@ module Postscript
193
188
  else
194
189
  char
195
190
  end
196
- if replacement.is_a?(String)
197
- advance
198
- @string_start[:bytes] << replacement
199
- end
191
+ return unless replacement.is_a?(String)
192
+
193
+ advance
194
+ @string_start[:bytes] << replacement
200
195
  end
201
196
 
202
197
  def consume_octal_escape
203
198
  digits = +""
204
199
  3.times do
205
200
  byte = peek
206
- break unless byte && (byte >= 48 && byte <= 55) # 0..7
201
+ break unless byte && byte >= 48 && byte <= 55 # 0..7
207
202
 
208
203
  digits << byte.chr
209
204
  advance
@@ -244,7 +239,8 @@ module Postscript
244
239
  end
245
240
  end
246
241
  raise LexError.new("unterminated hex string literal",
247
- source_position: [@hex_start[:line], @hex_start[:column]])
242
+ source_position: [@hex_start[:line],
243
+ @hex_start[:column]])
248
244
  end
249
245
 
250
246
  def emit_hexstring
@@ -266,7 +262,7 @@ module Postscript
266
262
  end
267
263
  end
268
264
 
269
- def start_name_with_slash(prefix)
265
+ def start_name_with_slash(_prefix)
270
266
  start_line = @line
271
267
  start_col = @column
272
268
  advance
@@ -278,7 +274,8 @@ module Postscript
278
274
  bytes << byte.chr
279
275
  advance
280
276
  end
281
- @tokens << Model::Token.new(:name, bytes, line: start_line, column: start_col, literal: true)
277
+ @tokens << Model::Token.new(:name, bytes, line: start_line,
278
+ column: start_col, literal: true)
282
279
  end
283
280
 
284
281
  def delimiter_byte?(byte)
@@ -312,7 +309,7 @@ module Postscript
312
309
  @tokens << Model::Token.new(type, bytes, line: line, column: col)
313
310
  end
314
311
 
315
- NUMBER_RE = /\A[-+]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][-+]?\d+)?\z/.freeze
312
+ NUMBER_RE = /\A[-+]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][-+]?\d+)?\z/
316
313
 
317
314
  def emit(type, value)
318
315
  @tokens << Model::Token.new(type, value, line: @line, column: @column)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Postscript
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/postscript.rb CHANGED
@@ -30,4 +30,34 @@ module Postscript
30
30
 
31
31
  # PS source serializer
32
32
  autoload :Serializer, "postscript/serializer"
33
+
34
+ # CLI (only loaded when explicitly required or via the +postscript+
35
+ # executable). Avoids pulling in +thor+ for users who only need
36
+ # the library API.
37
+ autoload :CLI, "postscript/cli"
38
+
39
+ class << self
40
+ # One-shot: PS source string -> +Model::Program+.
41
+ #
42
+ # Example:
43
+ # program = Postscript.parse(ps_source)
44
+ # program.body.each { |node| puts node.class }
45
+ def parse(source)
46
+ Source.parse(source)
47
+ end
48
+
49
+ # One-shot: +Model::Program+ -> PS source string.
50
+ #
51
+ # Example:
52
+ # ps = Postscript.serialize(program, eps: true)
53
+ def serialize(program, **opts)
54
+ Serializer.call(program, **opts)
55
+ end
56
+
57
+ # One-shot: PS source string -> array of +Model::Token+.
58
+ # Useful for debugging or building alternative parsers.
59
+ def tokenize(source)
60
+ Source::Lexer.tokenize(source)
61
+ end
62
+ end
33
63
  end
metadata CHANGED
@@ -1,14 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postscript
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ date: 2026-07-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
12
27
  description: |
13
28
  Postscript is a pure-Ruby PostScript (PS) / EPS parser, typed
14
29
  domain model, and serializer. It is the lower layer of the
@@ -16,15 +31,22 @@ description: |
16
31
  that needs to read, write, or transform PostScript source.
17
32
  email:
18
33
  - open.source@ribose.com
19
- executables: []
34
+ executables:
35
+ - postscript
20
36
  extensions: []
21
37
  extra_rdoc_files: []
22
38
  files:
23
39
  - ".rspec"
40
+ - ".rubocop.yml"
41
+ - ".rubocop_todo.yml"
42
+ - CHANGELOG.md
24
43
  - Gemfile
44
+ - LICENSE
25
45
  - README.adoc
26
46
  - Rakefile
47
+ - exe/postscript
27
48
  - lib/postscript.rb
49
+ - lib/postscript/cli.rb
28
50
  - lib/postscript/color.rb
29
51
  - lib/postscript/errors.rb
30
52
  - lib/postscript/format_number.rb
@@ -61,7 +83,6 @@ files:
61
83
  - lib/postscript/source/lexer.rb
62
84
  - lib/postscript/source/operand_stack.rb
63
85
  - lib/postscript/version.rb
64
- - postscript.gemspec
65
86
  homepage: https://github.com/claricle/postscript
66
87
  licenses:
67
88
  - BSD-2-Clause
@@ -71,6 +92,7 @@ metadata:
71
92
  changelog_uri: https://github.com/claricle/postscript/blob/main/CHANGELOG.md
72
93
  bug_tracker_uri: https://github.com/claricle/postscript/issues
73
94
  rubygems_mfa_required: 'true'
95
+ post_install_message:
74
96
  rdoc_options: []
75
97
  require_paths:
76
98
  - lib
@@ -85,7 +107,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
107
  - !ruby/object:Gem::Version
86
108
  version: '0'
87
109
  requirements: []
88
- rubygems_version: 4.0.16
110
+ rubygems_version: 3.5.22
111
+ signing_key:
89
112
  specification_version: 4
90
113
  summary: Pure Ruby PostScript (PS) / EPS parser, domain model, and serializer
91
114
  test_files: []
data/postscript.gemspec DELETED
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/postscript/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "postscript"
7
- spec.version = Postscript::VERSION
8
- spec.authors = ["Ribose Inc."]
9
- spec.email = ["open.source@ribose.com"]
10
-
11
- spec.summary = "Pure Ruby PostScript (PS) / EPS parser, domain model, and serializer"
12
- spec.description = <<~HEREDOC
13
- Postscript is a pure-Ruby PostScript (PS) / EPS parser, typed
14
- domain model, and serializer. It is the lower layer of the
15
- postsvg converter and is independently reusable for any tool
16
- that needs to read, write, or transform PostScript source.
17
- HEREDOC
18
-
19
- spec.homepage = "https://github.com/claricle/postscript"
20
- spec.license = "BSD-2-Clause"
21
- spec.required_ruby_version = ">= 3.0.0"
22
-
23
- spec.metadata["homepage_uri"] = spec.homepage
24
- spec.metadata["source_code_uri"] = "https://github.com/claricle/postscript"
25
- spec.metadata["changelog_uri"] = "https://github.com/claricle/postscript/blob/main/CHANGELOG.md"
26
- spec.metadata["bug_tracker_uri"] = "https://github.com/claricle/postscript/issues"
27
- spec.metadata["rubygems_mfa_required"] = "true"
28
-
29
- spec.files = Dir.chdir(__dir__) do
30
- `git ls-files -z`.split("\x0").reject do |f|
31
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
32
- end
33
- end
34
- spec.bindir = "exe"
35
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
- spec.require_paths = ["lib"]
37
- end