postsvg 0.1.0 → 0.3.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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -0
  3. data/CLAUDE.md +173 -0
  4. data/Gemfile +2 -3
  5. data/README.adoc +456 -179
  6. data/Rakefile +100 -0
  7. data/TODO.roadmap/00-architecture.md +139 -0
  8. data/TODO.roadmap/01-autoload-migration.md +39 -0
  9. data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
  10. data/TODO.roadmap/03-domain-model.md +66 -0
  11. data/TODO.roadmap/04-lexer.md +40 -0
  12. data/TODO.roadmap/05-parser.md +43 -0
  13. data/TODO.roadmap/06-graphics-state.md +45 -0
  14. data/TODO.roadmap/07-matrix-and-color.md +37 -0
  15. data/TODO.roadmap/08-svg-builder.md +51 -0
  16. data/TODO.roadmap/09-renderer.md +52 -0
  17. data/TODO.roadmap/10-visitor.md +58 -0
  18. data/TODO.roadmap/11-operator-coverage.md +103 -0
  19. data/TODO.roadmap/12-svg-domain.md +62 -0
  20. data/TODO.roadmap/13-translation-handlers.md +69 -0
  21. data/TODO.roadmap/14-ps-serializer.md +49 -0
  22. data/TODO.roadmap/15-cli-and-public-api.md +47 -0
  23. data/TODO.roadmap/16-specs.md +84 -0
  24. data/TODO.roadmap/17-docs-sync.md +47 -0
  25. data/TODO.roadmap/18-performance-and-determinism.md +47 -0
  26. data/TODO.roadmap/19-error-model.md +45 -0
  27. data/TODO.roadmap/20-font-and-text.md +46 -0
  28. data/TODO.roadmap/21-images.md +45 -0
  29. data/TODO.roadmap/22-forms-and-resources.md +25 -0
  30. data/TODO.roadmap/23-level2-level3.md +52 -0
  31. data/TODO.roadmap/24-ci-and-release.md +42 -0
  32. data/TODO.roadmap/README.md +77 -0
  33. data/docs/.gitignore +29 -0
  34. data/docs/CHANGELOG.md +114 -0
  35. data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
  36. data/docs/DEPLOYMENT.md +456 -0
  37. data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
  38. data/docs/DOCUMENTATION_PLAN.md +425 -0
  39. data/docs/FINAL_SUMMARY.md +657 -0
  40. data/docs/Gemfile +15 -0
  41. data/docs/README.md +327 -0
  42. data/docs/_config.yml +99 -0
  43. data/docs/advanced-topics.adoc +370 -0
  44. data/docs/api-reference/colors.adoc +705 -0
  45. data/docs/api-reference/converter.adoc +699 -0
  46. data/docs/api-reference/execution-context.adoc +1210 -0
  47. data/docs/api-reference/graphics-state.adoc +1070 -0
  48. data/docs/api-reference/interpreter.adoc +810 -0
  49. data/docs/api-reference/matrix.adoc +1179 -0
  50. data/docs/api-reference/path-builder.adoc +1284 -0
  51. data/docs/api-reference/postsvg-module.adoc +388 -0
  52. data/docs/api-reference/svg-generator.adoc +891 -0
  53. data/docs/api-reference/tokenizer.adoc +925 -0
  54. data/docs/api-reference.adoc +221 -0
  55. data/docs/architecture/command-registry.adoc +1191 -0
  56. data/docs/architecture/conversion-pipeline.adoc +746 -0
  57. data/docs/architecture/design-decisions.adoc +999 -0
  58. data/docs/architecture/generator-stage.adoc +1115 -0
  59. data/docs/architecture/graphics-state-model.adoc +1089 -0
  60. data/docs/architecture/interpreter-stage.adoc +1125 -0
  61. data/docs/architecture/parser-stage.adoc +1051 -0
  62. data/docs/architecture.adoc +354 -0
  63. data/docs/cli-reference/batch-command.adoc +616 -0
  64. data/docs/cli-reference/check-command.adoc +677 -0
  65. data/docs/cli-reference/cli-options.adoc +802 -0
  66. data/docs/cli-reference/convert-command.adoc +462 -0
  67. data/docs/cli-reference/version-command.adoc +296 -0
  68. data/docs/cli-reference.adoc +317 -0
  69. data/docs/concepts/conversion-pipeline.adoc +903 -0
  70. data/docs/concepts/coordinate-systems.adoc +836 -0
  71. data/docs/concepts/graphics-state.adoc +861 -0
  72. data/docs/concepts/path-operations.adoc +1076 -0
  73. data/docs/concepts/postscript-language.adoc +859 -0
  74. data/docs/concepts/svg-generation.adoc +937 -0
  75. data/docs/concepts.adoc +198 -0
  76. data/docs/contributing.adoc +443 -0
  77. data/docs/development.adoc +420 -0
  78. data/docs/faq.adoc +493 -0
  79. data/docs/getting-started/basic-usage.adoc +538 -0
  80. data/docs/getting-started/common-workflows.adoc +577 -0
  81. data/docs/getting-started/first-conversion.adoc +492 -0
  82. data/docs/getting-started/installation.adoc +534 -0
  83. data/docs/getting-started.adoc +94 -0
  84. data/docs/index.adoc +248 -0
  85. data/docs/optimization.adoc +196 -0
  86. data/docs/ps2svg_compatibility.adoc +149 -0
  87. data/docs/quick-reference.adoc +453 -0
  88. data/docs/sitemap.adoc +337 -0
  89. data/docs/troubleshooting.adoc +486 -0
  90. data/docs/validation.adoc +772 -0
  91. data/exe/postsvg +1 -0
  92. data/lib/postsvg/cli.rb +104 -57
  93. data/lib/postsvg/color.rb +132 -0
  94. data/lib/postsvg/errors.rb +68 -3
  95. data/lib/postsvg/format_number.rb +22 -0
  96. data/lib/postsvg/graphics_context.rb +80 -0
  97. data/lib/postsvg/graphics_stack.rb +43 -0
  98. data/lib/postsvg/model/literals/array.rb +41 -0
  99. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  100. data/lib/postsvg/model/literals/hex.rb +37 -0
  101. data/lib/postsvg/model/literals/name.rb +40 -0
  102. data/lib/postsvg/model/literals/number.rb +36 -0
  103. data/lib/postsvg/model/literals/procedure.rb +41 -0
  104. data/lib/postsvg/model/literals/string.rb +30 -0
  105. data/lib/postsvg/model/literals.rb +19 -0
  106. data/lib/postsvg/model/operator.rb +58 -0
  107. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  108. data/lib/postsvg/model/operators/boolean.rb +182 -0
  109. data/lib/postsvg/model/operators/color.rb +74 -0
  110. data/lib/postsvg/model/operators/container.rb +186 -0
  111. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  112. data/lib/postsvg/model/operators/device.rb +21 -0
  113. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  114. data/lib/postsvg/model/operators/font.rb +121 -0
  115. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  116. data/lib/postsvg/model/operators/painting.rb +29 -0
  117. data/lib/postsvg/model/operators/path.rb +169 -0
  118. data/lib/postsvg/model/operators/stack.rb +72 -0
  119. data/lib/postsvg/model/operators/transformations.rb +103 -0
  120. data/lib/postsvg/model/operators.rb +89 -0
  121. data/lib/postsvg/model/program.rb +68 -0
  122. data/lib/postsvg/model/token.rb +43 -0
  123. data/lib/postsvg/model.rb +17 -0
  124. data/lib/postsvg/options.rb +29 -0
  125. data/lib/postsvg/renderer.rb +85 -0
  126. data/lib/postsvg/serializer.rb +325 -0
  127. data/lib/postsvg/source/ast_builder.rb +308 -0
  128. data/lib/postsvg/source/lexer.rb +322 -0
  129. data/lib/postsvg/source/operand_stack.rb +55 -0
  130. data/lib/postsvg/source.rb +21 -0
  131. data/lib/postsvg/svg/attribute_parser.rb +45 -0
  132. data/lib/postsvg/svg/clip_path_registry.rb +44 -0
  133. data/lib/postsvg/svg/document.rb +22 -0
  134. data/lib/postsvg/svg/element.rb +84 -0
  135. data/lib/postsvg/svg/elements/circle.rb +36 -0
  136. data/lib/postsvg/svg/elements/clip_path.rb +26 -0
  137. data/lib/postsvg/svg/elements/defs.rb +24 -0
  138. data/lib/postsvg/svg/elements/ellipse.rb +38 -0
  139. data/lib/postsvg/svg/elements/group.rb +37 -0
  140. data/lib/postsvg/svg/elements/image.rb +35 -0
  141. data/lib/postsvg/svg/elements/line.rb +36 -0
  142. data/lib/postsvg/svg/elements/path.rb +32 -0
  143. data/lib/postsvg/svg/elements/polygon.rb +12 -0
  144. data/lib/postsvg/svg/elements/polyline.rb +32 -0
  145. data/lib/postsvg/svg/elements/rect.rb +44 -0
  146. data/lib/postsvg/svg/elements/svg.rb +39 -0
  147. data/lib/postsvg/svg/elements/text.rb +42 -0
  148. data/lib/postsvg/svg/elements.rb +31 -0
  149. data/lib/postsvg/svg/paint.rb +34 -0
  150. data/lib/postsvg/svg/parser.rb +39 -0
  151. data/lib/postsvg/svg/path_data/command.rb +27 -0
  152. data/lib/postsvg/svg/path_data/parser.rb +82 -0
  153. data/lib/postsvg/svg/path_data.rb +17 -0
  154. data/lib/postsvg/svg/stroke.rb +31 -0
  155. data/lib/postsvg/svg/transform_list.rb +59 -0
  156. data/lib/postsvg/svg.rb +22 -0
  157. data/lib/postsvg/svg_builder.rb +249 -0
  158. data/lib/postsvg/translation/arc_converter.rb +86 -0
  159. data/lib/postsvg/translation/bounding_box.rb +59 -0
  160. data/lib/postsvg/translation/context.rb +34 -0
  161. data/lib/postsvg/translation/handler_registry.rb +38 -0
  162. data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
  163. data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
  164. data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
  165. data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
  166. data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
  167. data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
  168. data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
  169. data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
  170. data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
  171. data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
  172. data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
  173. data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
  174. data/lib/postsvg/translation/handlers/shared.rb +110 -0
  175. data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
  176. data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
  177. data/lib/postsvg/translation/handlers.rb +25 -0
  178. data/lib/postsvg/translation/ps_renderer.rb +105 -0
  179. data/lib/postsvg/translation/record_emitter.rb +35 -0
  180. data/lib/postsvg/translation.rb +17 -0
  181. data/lib/postsvg/version.rb +1 -1
  182. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
  183. data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
  184. data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
  185. data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
  186. data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
  187. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
  188. data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
  189. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
  190. data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
  191. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
  192. data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
  193. data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
  194. data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
  195. data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
  196. data/lib/postsvg/visitors/ps_visitor.rb +208 -0
  197. data/lib/postsvg/visitors.rb +9 -0
  198. data/lib/postsvg.rb +93 -59
  199. data/lychee.toml +86 -0
  200. metadata +216 -11
  201. data/postsvg.gemspec +0 -38
@@ -0,0 +1,577 @@
1
+ = Common Workflows
2
+ :page-nav_order: 4
3
+ :page-parent: Getting Started
4
+
5
+ == Purpose
6
+
7
+ This guide demonstrates common real-world workflows for using Postsvg in various scenarios. Each workflow includes complete, tested examples that you can adapt to your needs.
8
+
9
+ == References
10
+
11
+ * link:../index.adoc[Documentation Home]
12
+ * link:../getting-started.adoc[Getting Started Overview]
13
+ * link:basic-usage.adoc[Basic Usage]
14
+ * link:first-conversion.adoc[First Conversion Tutorial]
15
+ * link:../api-reference.adoc[API Reference]
16
+
17
+ == Concepts
18
+
19
+ **Workflow**:: A sequence of steps to accomplish a specific task using Postsvg.
20
+
21
+ **Batch Processing**:: Converting multiple files in an automated manner.
22
+
23
+ **Validation Pipeline**:: Checking files before conversion to ensure quality.
24
+
25
+ **Error Handling**:: Gracefully handling conversion failures and partial successes.
26
+
27
+ == Workflow 1: Single File Conversion with Validation
28
+
29
+ Convert a single file after validating it first.
30
+
31
+ [source,sh]
32
+ ----
33
+ #!/bin/bash
34
+ # validate-and-convert.sh
35
+
36
+ INPUT="document.ps"
37
+ OUTPUT="document.svg"
38
+
39
+ # Step 1: Validate
40
+ echo "Validating $INPUT..."
41
+ if postsvg check --level=semantic "$INPUT"; then
42
+ echo "✓ Validation passed"
43
+
44
+ # Step 2: Convert
45
+ echo "Converting to SVG..."
46
+ if postsvg convert "$INPUT" "$OUTPUT"; then
47
+ echo "✓ Conversion complete: $OUTPUT"
48
+ else
49
+ echo "✗ Conversion failed"
50
+ exit 1
51
+ fi
52
+ else
53
+ echo "✗ Validation failed"
54
+ exit 1
55
+ fi
56
+ ----
57
+
58
+ **Usage:**
59
+ [source,sh]
60
+ ----
61
+ chmod +x validate-and-convert.sh
62
+ ./validate-and-convert.sh
63
+ ----
64
+
65
+ == Workflow 2: Batch Conversion with Error Logging
66
+
67
+ Convert multiple files and log errors for later review.
68
+
69
+ [source,ruby]
70
+ ----
71
+ #!/usr/bin/env ruby
72
+ # batch-convert.rb
73
+
74
+ require 'postsvg'
75
+ require 'logger'
76
+
77
+ # Setup logging
78
+ logger = Logger.new('conversion.log')
79
+ logger.info "Batch conversion started"
80
+
81
+ # Find all PS files
82
+ ps_files = Dir.glob('input/*.{ps,eps}', File::FNM_CASEFOLD)
83
+ logger.info "Found #{ps_files.size} files to convert"
84
+
85
+ # Track statistics
86
+ stats = { success: 0, failed: 0, partial: 0 }
87
+
88
+ # Convert each file
89
+ ps_files.each do |input_file|
90
+ output_file = input_file.sub(/\.(ps|eps)$/i, '.svg')
91
+ .sub('input/', 'output/')
92
+
93
+ begin
94
+ # Try strict conversion first
95
+ converter = Postsvg::Converter.new(
96
+ File.read(input_file),
97
+ strict_mode: true
98
+ )
99
+ svg = converter.convert
100
+ File.write(output_file, svg)
101
+
102
+ stats[:success] += 1
103
+ logger.info "✓ #{input_file} → #{output_file}"
104
+ puts "✓ #{File.basename(input_file)}"
105
+
106
+ rescue Postsvg::Error => e
107
+ # Try lenient mode
108
+ begin
109
+ converter = Postsvg::Converter.new(
110
+ File.read(input_file),
111
+ strict_mode: false
112
+ )
113
+ svg = converter.convert
114
+ File.write(output_file, svg)
115
+
116
+ stats[:partial] += 1
117
+ logger.warn "⚠ #{input_file}: #{e.message} (used lenient mode)"
118
+ puts "⚠ #{File.basename(input_file)} (partial)"
119
+
120
+ rescue StandardError => e2
121
+ stats[:failed] += 1
122
+ logger.error "✗ #{input_file}: #{e2.message}"
123
+ puts "✗ #{File.basename(input_file)}"
124
+ end
125
+ end
126
+ end
127
+
128
+ # Summary
129
+ logger.info "Conversion complete: #{stats[:success]} success, " \
130
+ "#{stats[:partial]} partial, #{stats[:failed]} failed"
131
+ puts "\nSummary:"
132
+ puts " Success: #{stats[:success]}"
133
+ puts " Partial: #{stats[:partial]}"
134
+ puts " Failed: #{stats[:failed]}"
135
+ puts "\nSee conversion.log for details"
136
+ ----
137
+
138
+ **Usage:**
139
+ [source,sh]
140
+ ----
141
+ chmod +x batch-convert.rb
142
+ ./batch-convert.rb
143
+ ----
144
+
145
+ == Workflow 3: CI/CD Integration
146
+
147
+ Validate PostScript files in a continuous integration pipeline.
148
+
149
+ **GitHub Actions:**
150
+
151
+ [source,yaml]
152
+ ----
153
+ # .github/workflows/validate-ps.yml
154
+ name: Validate PostScript Files
155
+
156
+ on:
157
+ pull_request:
158
+ paths:
159
+ - '**.ps'
160
+ - '**.eps'
161
+
162
+ jobs:
163
+ validate:
164
+ runs-on: ubuntu-latest
165
+ steps:
166
+ - uses: actions/checkout@v3
167
+
168
+ - name: Setup Ruby
169
+ uses: ruby/setup-ruby@v1
170
+ with:
171
+ ruby-version: '3.2'
172
+
173
+ - name: Install Postsvg
174
+ run: gem install postsvg
175
+
176
+ - name: Find PostScript files
177
+ id: find-files
178
+ run: |
179
+ FILES=$(find . -name "*.ps" -o -name "*.eps" | tr '\n' ' ')
180
+ echo "files=$FILES" >> $GITHUB_OUTPUT
181
+
182
+ - name: Validate files
183
+ run: |
184
+ postsvg check --format=json --no-color \
185
+ --level=full --fail-fast \
186
+ ${{ steps.find-files.outputs.files }}
187
+
188
+ - name: Convert files
189
+ if: success()
190
+ run: |
191
+ for file in ${{ steps.find-files.outputs.files }}; do
192
+ postsvg convert "$file" "${file%.*}.svg"
193
+ done
194
+
195
+ - name: Upload SVG artifacts
196
+ uses: actions/upload-artifact@v3
197
+ with:
198
+ name: svg-files
199
+ path: '**/*.svg'
200
+ ----
201
+
202
+ **GitLab CI:**
203
+
204
+ [source,yaml]
205
+ ----
206
+ # .gitlab-ci.yml
207
+ validate-ps:
208
+ image: ruby:3.2
209
+ stage: test
210
+ script:
211
+ - gem install postsvg
212
+ - find . -name "*.ps" -o -name "*.eps" |
213
+ xargs postsvg check --format=json --level=full
214
+ only:
215
+ changes:
216
+ - "**/*.ps"
217
+ - "**/*.eps"
218
+
219
+ convert-ps:
220
+ image: ruby:3.2
221
+ stage: build
222
+ script:
223
+ - gem install postsvg
224
+ - |
225
+ for file in $(find . -name "*.ps" -o -name "*.eps"); do
226
+ postsvg convert "$file" "${file%.*}.svg"
227
+ done
228
+ artifacts:
229
+ paths:
230
+ - "**/*.svg"
231
+ expire_in: 1 week
232
+ only:
233
+ changes:
234
+ - "**/*.ps"
235
+ - "**/*.eps"
236
+ ----
237
+
238
+ == Workflow 4: Rails Integration
239
+
240
+ Integrate Postsvg into a Rails application for on-demand conversion.
241
+
242
+ **Service Object:**
243
+
244
+ [source,ruby]
245
+ ----
246
+ # app/services/postscript_converter_service.rb
247
+ class PostscriptConverterService
248
+ class ConversionError < StandardError; end
249
+
250
+ def initialize(file_or_content, options = {})
251
+ @content = file_or_content.is_a?(String) ?
252
+ file_or_content :
253
+ file_or_content.read
254
+ @options = options
255
+ end
256
+
257
+ def convert
258
+ validator = validate_content
259
+ raise ConversionError, validator[:errors].join(', ') unless validator[:valid]
260
+
261
+ converter = Postsvg::Converter.new(@content, strict_mode: @options[:strict])
262
+ svg_content = converter.convert
263
+
264
+ {
265
+ success: true,
266
+ svg: svg_content,
267
+ size: svg_content.bytesize,
268
+ validated: true
269
+ }
270
+ rescue Postsvg::Error => e
271
+ {
272
+ success: false,
273
+ error: e.message,
274
+ error_type: e.class.name
275
+ }
276
+ end
277
+
278
+ private
279
+
280
+ def validate_content
281
+ # Basic validation
282
+ return { valid: false, errors: ['Empty content'] } if @content.empty?
283
+ return { valid: false, errors: ['Not PostScript'] } unless postscript?
284
+
285
+ { valid: true, errors: [] }
286
+ end
287
+
288
+ def postscript?
289
+ @content.start_with?('%!PS') || @content.start_with?('%!<')
290
+ end
291
+ end
292
+ ----
293
+
294
+ **Controller:**
295
+
296
+ [source,ruby]
297
+ ----
298
+ # app/controllers/conversions_controller.rb
299
+ class ConversionsController < ApplicationController
300
+ def create
301
+ uploaded_file = params[:file]
302
+
303
+ result = PostscriptConverterService.new(
304
+ uploaded_file,
305
+ strict: params[:strict] == 'true'
306
+ ).convert
307
+
308
+ if result[:success]
309
+ send_data result[:svg],
310
+ filename: "#{uploaded_file.original_filename.sub(/\.(ps|eps)$/i, '')}.svg",
311
+ type: 'image/svg+xml',
312
+ disposition: 'attachment'
313
+ else
314
+ flash[:error] = "Conversion failed: #{result[:error]}"
315
+ redirect_to root_path
316
+ end
317
+ end
318
+ end
319
+ ----
320
+
321
+ **View:**
322
+
323
+ [source,erb]
324
+ ----
325
+ <!-- app/views/conversions/new.html.erb -->
326
+ <%= form_with url: conversions_path, multipart: true do |f| %>
327
+ <div class="field">
328
+ <%= f.label :file, "PostScript File" %>
329
+ <%= f.file_field :file, accept: '.ps,.eps' %>
330
+ </div>
331
+
332
+ <div class="field">
333
+ <%= f.check_box :strict %>
334
+ <%= f.label :strict, "Strict Mode (fail on unknown operators)" %>
335
+ </div>
336
+
337
+ <%= f.submit "Convert to SVG" %>
338
+ <% end %>
339
+ ----
340
+
341
+ == Workflow 5: Automated Directory Watch
342
+
343
+ Automatically convert PostScript files when they appear in a directory.
344
+
345
+ [source,ruby]
346
+ ----
347
+ #!/usr/bin/env ruby
348
+ # ps-watcher.rb
349
+
350
+ require 'postsvg'
351
+ require 'listen'
352
+
353
+ INPUT_DIR = 'input/'
354
+ OUTPUT_DIR = 'output/'
355
+
356
+ # Ensure output directory exists
357
+ Dir.mkdir(OUTPUT_DIR) unless Dir.exist?(OUTPUT_DIR)
358
+
359
+ puts "Watching #{INPUT_DIR} for PostScript files..."
360
+ puts "Press Ctrl+C to stop"
361
+
362
+ listener = Listen.to(INPUT_DIR, only: /\.(ps|eps)$/i) do |modified, added, removed|
363
+ # Handle new or modified files
364
+ (modified + added).each do |file|
365
+ next unless File.exist?(file) # Skip if file was removed
366
+
367
+ output_file = File.join(
368
+ OUTPUT_DIR,
369
+ File.basename(file).sub(/\.(ps|eps)$/i, '.svg')
370
+ )
371
+
372
+ begin
373
+ puts "Converting #{File.basename(file)}..."
374
+ Postsvg.convert_file(file, output_file)
375
+ puts "✓ Created #{File.basename(output_file)}"
376
+ rescue => e
377
+ puts "✗ Failed #{File.basename(file)}: #{e.message}"
378
+ end
379
+ end
380
+ end
381
+
382
+ listener.start
383
+ sleep
384
+ ----
385
+
386
+ **Usage:**
387
+ [source,sh]
388
+ ----
389
+ gem install listen
390
+ chmod +x ps-watcher.rb
391
+ ./ps-watcher.rb
392
+ ----
393
+
394
+ == Workflow 6: Quality Assurance Pipeline
395
+
396
+ Comprehensive quality checks before production deployment.
397
+
398
+ [source,ruby]
399
+ ----
400
+ #!/usr/bin/env ruby
401
+ # qa-pipeline.rb
402
+
403
+ require 'postsvg'
404
+ require 'fileutils'
405
+
406
+ class QAPipeline
407
+ def initialize(input_dir, output_dir)
408
+ @input_dir = input_dir
409
+ @output_dir = output_dir
410
+ @report = { passed: [], failed: [], warnings: [] }
411
+ end
412
+
413
+ def run
414
+ puts "Starting QA Pipeline..."
415
+ puts "=" * 50
416
+
417
+ files = Dir.glob(File.join(@input_dir, '*.{ps,eps}'))
418
+ puts "Found #{files.size} files"
419
+ puts
420
+
421
+ files.each do |file|
422
+ process_file(file)
423
+ end
424
+
425
+ print_report
426
+ end
427
+
428
+ private
429
+
430
+ def process_file(file)
431
+ basename = File.basename(file)
432
+ puts "Processing: #{basename}"
433
+
434
+ # Step 1: Syntax validation
435
+ unless validate_syntax(file)
436
+ @report[:failed] << "#{basename}: Syntax validation failed"
437
+ return
438
+ end
439
+
440
+ # Step 2: Semantic validation
441
+ unless validate_semantic(file)
442
+ @report[:failed] << "#{basename}: Semantic validation failed"
443
+ return
444
+ end
445
+
446
+ # Step 3: Conversion
447
+ output_file = File.join(@output_dir, basename.sub(/\.(ps|eps)$/i, '.svg'))
448
+ unless convert_file(file, output_file)
449
+ @report[:failed] << "#{basename}: Conversion failed"
450
+ return
451
+ end
452
+
453
+ # Step 4: Verify output
454
+ unless verify_output(output_file)
455
+ @report[:warnings] << "#{basename}: Output verification warnings"
456
+ end
457
+
458
+ @report[:passed] << basename
459
+ puts "✓ Passed all checks"
460
+ puts
461
+ end
462
+
463
+ def validate_syntax(file)
464
+ system("postsvg check --level=syntax --quiet #{file}")
465
+ end
466
+
467
+ def validate_semantic(file)
468
+ system("postsvg check --level=semantic --quiet #{file}")
469
+ end
470
+
471
+ def convert_file(input, output)
472
+ Postsvg.convert_file(input, output)
473
+ true
474
+ rescue => e
475
+ puts " Error: #{e.message}"
476
+ false
477
+ end
478
+
479
+ def verify_output(file)
480
+ content = File.read(file)
481
+ content.include?('<svg') && content.include?('</svg>')
482
+ end
483
+
484
+ def print_report
485
+ puts "=" * 50
486
+ puts "QA Pipeline Report"
487
+ puts "=" * 50
488
+ puts "Passed: #{@report[:passed].size}"
489
+ puts "Failed: #{@report[:failed].size}"
490
+ puts "Warnings: #{@report[:warnings].size}"
491
+ puts
492
+
493
+ if @report[:failed].any?
494
+ puts "Failed files:"
495
+ @report[:failed].each { |f| puts " ✗ #{f}" }
496
+ puts
497
+ end
498
+
499
+ if @report[:warnings].any?
500
+ puts "Warnings:"
501
+ @report[:warnings].each { |w| puts " ⚠ #{w}" }
502
+ puts
503
+ end
504
+
505
+ exit(@report[:failed].empty? ? 0 : 1)
506
+ end
507
+ end
508
+
509
+ # Run pipeline
510
+ QAPipeline.new('input/', 'output/').run
511
+ ----
512
+
513
+ == Workflow 7: Performance Monitoring
514
+
515
+ Monitor conversion performance for large batches.
516
+
517
+ [source,ruby]
518
+ ----
519
+ #!/usr/bin/env ruby
520
+ # performance-monitor.rb
521
+
522
+ require 'postsvg'
523
+ require 'benchmark'
524
+
525
+ files = Dir.glob('*.ps')
526
+ results = []
527
+
528
+ puts "Performance Monitoring Report"
529
+ puts "=" * 60
530
+ puts
531
+
532
+ files.each do |file|
533
+ size = File.size(file)
534
+
535
+ time = Benchmark.measure do
536
+ Postsvg.convert_file(file, file.sub('.ps', '.svg'))
537
+ end
538
+
539
+ results << {
540
+ file: file,
541
+ size: size,
542
+ time: time.real,
543
+ rate: size / time.real / 1024 # KB/s
544
+ }
545
+
546
+ printf "%-30s %8d bytes %6.2f sec %8.1f KB/s\n",
547
+ File.basename(file), size, time.real, size / time.real / 1024
548
+ end
549
+
550
+ puts
551
+ puts "=" * 60
552
+ puts "Summary:"
553
+ puts " Total files: #{results.size}"
554
+ puts " Total size: #{results.sum { |r| r[:size] } / 1024} KB"
555
+ puts " Total time: #{'%.2f' % results.sum { |r| r[:time] }} sec"
556
+ puts " Average: #{'%.1f' % (results.sum { |r| r[:rate] } / results.size)} KB/s"
557
+ puts " Fastest: #{'%.1f' % results.max_by { |r| r[:rate] }[:rate]} KB/s"
558
+ puts " Slowest: #{'%.1f' % results.min_by { |r| r[:rate] }[:rate]} KB/s"
559
+ ----
560
+
561
+ == Next Steps
562
+
563
+ After mastering these common workflows:
564
+
565
+ * Learn link:../advanced-topics.adoc[Advanced Topics] for complex scenarios
566
+ * Review link:../api-reference.adoc[API Reference] for detailed API usage
567
+ * Check link:../cli-reference.adoc[CLI Reference] for all command options
568
+ * See link:../development.adoc[Development Guide] to extend Postsvg
569
+ * Visit link:../troubleshooting.adoc[Troubleshooting] if you encounter issues
570
+
571
+ == Bibliography
572
+
573
+ * link:basic-usage.adoc[Basic Usage Guide]
574
+ * link:first-conversion.adoc[First Conversion Tutorial]
575
+ * link:../api-reference.adoc[API Reference]
576
+ * link:../cli-reference.adoc[CLI Reference]
577
+ * link:../advanced-topics.adoc[Advanced Topics]