Permalink
Browse files

Cutting gems (turned script into gem)

  • Loading branch information...
1 parent 22c0e4e commit a57beff00af3b1106e1fbf5f5f5c8c3d8fe78be0 @borgand committed Aug 9, 2011
Showing with 236 additions and 4 deletions.
  1. +20 −0 Gemfile.lock
  2. +8 −2 README.rdoc
  3. +3 −2 Rakefile
  4. +47 −0 bin/inkscape_merge
  5. +4 −0 lib/inkscape_merge.rb
  6. +41 −0 lib/inkscape_merge/data_parsers.rb
  7. +113 −0 lib/inkscape_merge/processor.rb
View
@@ -0,0 +1,20 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ git (1.2.5)
+ jeweler (1.6.0)
+ bundler (~> 1.0.0)
+ git (>= 1.2.5)
+ rake
+ rake (0.9.2)
+ rcov (0.9.9)
+ shoulda (2.11.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ bundler (~> 1.0.0)
+ jeweler (~> 1.6.0)
+ rcov
+ shoulda
View
@@ -1,6 +1,13 @@
= inkscape_merge
-Description goes here.
+Script to merge SVG files with CSV data-files using Inkscape, to produce
+one outputfile (e.g. PDF) per data-row.
+
+Script inspired by and based on Aurélio A. Heckert excellent
+InkscapeGenerator (http://wiki.colivre.net/Aurium/InkscapeGenerator)
+
+Heckert's original script unfortunately broke for me several times and
+I took the opportunity to rewrite it and make it more extendable for future.
== Contributing to inkscape_merge
@@ -9,7 +16,6 @@ Description goes here.
* Fork the project
* Start a feature/bugfix branch
* Commit and push until you are happy with your contribution
-* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
== Copyright
View
@@ -17,8 +17,9 @@ Jeweler::Tasks.new do |gem|
gem.name = "inkscape_merge"
gem.homepage = "http://github.com/borgand/inkscape_merge"
gem.license = "MIT"
- gem.summary = %Q{TODO: one-line summary of your gem}
- gem.description = %Q{TODO: longer description of your gem}
+ gem.summary = %Q{Merge SVG files with CSV file using Inkscape}
+ gem.description = %Q{Script to merge SVG files with CSV data-files using Inkscape, to produce
+ one outputfile (e.g. PDF) per data-row.}
gem.email = "laas.toom@gmail.com"
gem.authors = ["Laas Toom"]
# dependencies defined in Gemfile
View
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
+require 'inkscape_merge'
+require 'optparse'
+
+
+processor = Inkscape::Merge::Processor.new
+options = processor.options
+
+# Parse command line arguments
+OptionParser.new do |opts|
+ opts.banner = "Usage: inkscape_csv_merge.rb [options]"
+ opts.separator ""
+ opts.separator "REQUIRED options"
+ opts.on("-d", "--data-file [DATA_FILE]", "Data-file to take values from.") {|d| options.data_file = d}
+ opts.on("-f", "--svg-file [SVG_FILE]", "SVG file to be used as template. Default: none") {|f| options.svg_file = f}
+ opts.on('-o', '--output_pattern PAT', 'Pattern to produce output file names. Undergoes the same substitutions as the SVG file and additionally %d can be used for current row number. Default: none') {|p|
+ options.output = p
+ }
+
+ opts.separator ""
+ opts.separator "Optional"
+ opts.on('-i', '--inkscape INKSCAPE_BIN', "Where Inkscape binary can be found") {|i| options.inkscape = i}
+ opts.on('--format FMT', "Output format. Must be supported export format of Inkscape. Default: #{options.format}"){|f| options.format = f}
+ opts.on("--dpi DPI", Integer, "Specify output DPI. Default: #{options.dpi}") {|d| options.dpi = d}
+ opts.on("-v", "--[no-]verbose", "Run verbosely") {|v| options.verbose = v}
+ opts.on("-l", "--limit [LIMIT]", Integer, "Only process LIMIT rows. 0 means no limit. Default: #{options.limit}") {|l|
+ options.limit = l
+ }
+
+ opts.separator ""
+ opts.separator "CSV parser options"
+ opts.on("--csv_col_sep SEP", "CSV column separator. Default: #{options.csv_options[:col_sep]}") {|v| opts.csv_options[:col_sep] = v}
+ opts.on("--csv_encoding ENC", "CSV file encoding. Default: #{options.csv_options[:encoding]}") {|e| opts.csv_options[:encoding] = e}
+
+ opts.separator ""
+
+ opts.on_tail("-h", "--help", "Show this help") { puts opts; exit }
+end.parse!
+
+# Run the processor
+begin
+ processor.run
+rescue => e
+ $stderr.puts "ERROR: #{e}"
+ exit 1
+end
View
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+require 'inkscape_merge/processor'
+
+
@@ -0,0 +1,41 @@
+require 'csv'
+
+
+# Module to detect and wrap data-files
+module Inkscape # :nodoc:
+ module Merge # :nodoc:
+ module DataParser
+
+ # Detect, which parser to use for given input file
+ def self.detect(options)
+ case options.data_file
+ when /.csv$/i
+ return ::Inkscape::Merge::DataParser::CSV.new(options.data_file, options.csv_options)
+ end
+ end
+
+ # CSV file parser
+ # Though Ruby's CSV would suffice, we explicitly wrap it in an API
+ # Other parsers must comply to this API
+ class CSV
+ include Enumerable
+
+ # Read file into memory
+ def initialize(data_file, csv_options)
+ opts = csv_options
+ @data = ::CSV.read data_file, opts
+ end
+
+ # Return headers as an array
+ def headers
+ @data.headers
+ end
+
+ # Wraps CSV#each for enumerable support
+ def each(&block)
+ @data.each &block
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,113 @@
+require 'ostruct'
+require 'tempfile'
+require 'fileutils'
+require 'inkscape_merge/data_parsers'
+
+module Inkscape # :nodoc:
+ module Merge # :nodoc:
+ # Main class to initialize processing
+ class Processor
+ attr_reader :options
+
+ # Initialize the processor, setting files and options
+ def initialize
+ @options = OpenStruct.new
+ # Default options
+ @options.format = "pdf"
+ @options.csv_options = {:headers => true, :col_sep => ',', :encoding => 'utf-8'}
+ @options.limit = 0
+ @options.dpi = 300
+ @options.inkscape = %x(which inkscape)
+ # If no Inkscape in PATH, try to guess from platform
+ if options.inkscape.empty?
+ options.inkscape = case RUBY_PLATFORM
+ when /darwin/
+ "/Applications/Inkscape.app/Contents/Resources/bin/inkscape"
+ end
+ end
+
+ end
+
+ # Iterate over all data rows and generate output files
+ # Optionally stop when LIMIT is reached
+ def run
+ validate_options
+
+ # Open the files
+ @svg = File.read options.svg_file
+ @data_file = DataParser.detect(options)
+
+ count = 0
+ headers = @data_file.headers
+ pattern = /%VAR_(#{headers.map(&:to_s).join("|")})%/
+ @data_file.each{|row|
+ break if @options.limit > 0 && count >= @options.limit
+ count += 1
+ puts "Row: #{count}"
+ tmp_file = Tempfile.new('inkscape_merge')
+ begin
+ (outfile,merged_svg) = [@options.output,@svg].map{|s|
+ s.gsub(pattern){|m|
+ puts $1 if @options.verbose
+ # return corresponding value from current row
+ row[$1]
+ }
+ }
+
+ # Write merged SVG out
+ tmp_file.puts merged_svg
+ tmp_file.close
+
+ # Sprintf outfile with current row number
+ outfile %= count
+
+ # Generate output path
+ FileUtils.mkdir_p(File.dirname outfile)
+
+ # Generate the file itself
+ ink_generate tmp_file.path, outfile, @options.format, @options.dpi
+ rescue => e
+ $stderr.puts "ERROR: #{e}"
+ $stderr.puts e.backtrace if @options.verbose
+ ensure
+ tmp_file.unlink
+ end
+ }
+ end
+
+ private
+
+ # Validate options and give error if something is missing
+ def validate_options
+ # TODO: replace with
+
+ # If inkscape can not be found or run, bail out
+ unless File.executable? @options.inkscape
+ raise ArgumentError, "Inkscape not found or not executable"
+ end
+
+ unless @options.svg_file
+ raise ArgumentError, "SVG file must be given"
+ end
+
+ unless @options.data_file
+ raise ArgumentError, "Data-file must be given"
+ end
+
+ unless @options.output
+ raise ArgumentError, "Output pattern must be given"
+ end
+ end
+
+ # Run Inkscape to generate files
+ def ink_generate(in_file, out_file, format='pdf', dpi="300")
+ cmd = %(#{@options.inkscape} --without-gui --export-#{format}=#{out_file} --export-dpi=#{dpi} #{in_file})
+ puts "INKSCAPE CMD: #{cmd}" if @options.verbose
+ ink_error = `#{cmd} 2>&1`
+ unless $?.success?
+ $stderr.puts "Inkscape ERROR (#{$?}): #{ink_error}"
+ end
+ end
+ end
+ end
+end

0 comments on commit a57beff

Please sign in to comment.