<commit_before>require 'tempfile'
require 'forwardable'

# Certain existing libraries have a PDF class; no sense in being unnecessarily
# incompatible.
module PDF #:nodoc:
end unless defined? PDF

# PDF::Toolkit can be used as a simple class, or derived from and tweaked.  The
# following two examples have identical results.
#
#   my_pdf = PDF::Toolkit.open("somefile.pdf")
#   my_pdf.updated_at = Time.now # ModDate
#   my_pdf["SomeAttribute"] = "Some value"
#   my_pdf.save!
#
#   class MyDocument < PDF::Toolkit
#     info_accessor :some_attribute
#     def before_save
#       self.updated_at = Time.now
#     end
#   end
#   my_pdf = MyDocument.open("somefile.pdf")
#   my_pdf.some_attribute = "Some value"
#   my_pdf.save!
#
# Note the use of a +before_save+ callback in the second example.  This is
# the only supported callback unless you use the experimental
# #loot_active_record class method.
#
# == Requirements
#
# PDF::Toolkit requires +pdftk+, which is available from
# http://www.accesspdf.com/pdftk.  For full functionality, also install
# +xpdf+ from http://www.foolabs.com/xpdf.  ActiveSupport (from Ruby on Rails)
# is also required but this dependency may be removed in the future.
#
# == Limitations
#
# Timestamps are written in UTF-16 by +pdftk+, which is not appropriately
# handled by +pdfinfo+.
#
# +pdftk+ requires the owner password, even for simply querying the document.
class PDF::Toolkit
  extend Forwardable

  VERSION = "0.5.0"

  # Raised when something fails with the toolkit
  class Error < ::StandardError; end

  # Raised when an invocation of `pdftk` fails under the cover
  class ExecutionError < Error
    attr_reader :command, :exit_status
    def initialize(msg = nil, cmd = nil, exit_status = nil)
      super(msg)
      @command = cmd
      @exit_status = exit_status
    end
  end

  # Raised when a .pdf file cannot be saved
  class FileNotSaved < Error; end

  require 'pdf/toolkit/native'
  extend Native

  class << self

    # Add an accessor for a key.  If the key is omitted, defaults to a
    # camelized version of the accessor (+foo_bar+ becomes +FooBar+).  The
    # example below illustrates the defaults.
    #
    #   class MyDocument < PDF::Toolkit
    #     info_accessor :created_at, "CreationDate"
    #     info_accessor :updated_at, "ModDate"
    #     info_accessor :author
    #     [:subject, :title, :keywords, :producer, :creator].each do |key|
    #       info_accessor key
    #     end
    #   end
    #
    #   MyDocument.open("document.pdf").created_at
    def info_accessor(accessor_name, info_key = nil)
      info_key ||= camelize_key(accessor_name)
      info_accessors[accessor_name] = info_key
      define_method accessor_name do
        self[info_key]
      end
      define_method "#{accessor_name}=" do |value|
        self[info_key] = value
      end
    end

    def info_accessors
      @info_accessors ||= Hash.new{|h,k|
        if h.has_key?(k.to_s.to_sym)
          h[k.to_s.to_sym]
        elsif k.kind_of?(Symbol)
          camelize_key(k)
        else
          k.dup
        end
      }
    end

    attr_accessor :default_permissions, :default_input_password
    attr_accessor :default_owner_password, :default_user_password
    protected :default_owner_password=, :default_user_password=

    def camelize_key(key)
      if key.to_s.respond_to?(:camelize)
        key.to_s.camelize
      else
        key.to_s.gsub(/_+([^_])/) {$1.upcase}.sub(/^./) {|l|l.upcase}
      end
    end

  end

  info_accessor :created_at, "CreationDate"
  info_accessor :updated_at, "ModDate"
  [:author, :subject, :title, :keywords, :creator, :producer].each do |key|
    info_accessor key
  end

  # Create a new object associated with +filename+ and read in the
  # associated metadata.
  #
  #   my_pdf = PDF::Toolkit.open("document.pdf")
  def self.open(filename,input_password = <PASSWORD>)
    object = new(filename,input_password)
    object.reload
    object
  end

  # Like +open+, only the attributes are lazily loaded.  Under most
  # circumstances,  +open+ is preferred.
  def initialize(filename,input_password = <PASSWORD>)
    @filename = if filename.respond_to?(:to_str)
                  filename.to_str
                elsif filename.kind_of?(self.class)
                  filename.instance_variable_get("@filename")
                elsif filename.respond_to?(:path)
                  filename.path
                else
                  filename
                end
    @input_password = <PASSWORD> || <PASSWORD>
    @owner_password = <PASSWORD>
    @user_password  = <PASSWORD>
    @permissions = default_permissions || []
    @new_info = {}
    callback(:after_initialize) if respond_to?(:after_initialize) && respond_to?(:callback)
    # reload
  end

  def_delegators :"self.class", :default_input_password,
                                :default_owner_password,
                                :default_user_password,
                                :default_permissions

  attr_reader :pdf_ids, :permissions
  attr_writer :owner_password, :user_password

  def page_count
    read_data unless @pages
    @pages
  end
  alias pages page_count

  # Path to the file.
  def path
    @new_filename || @filename
  end

  # Retrieve the file's version as a symbol.
  #
  #   my_pdf.version # => :"1.4"
  def version
    @version ||= File.open(@filename) do |io|
      io.read(8)[5..-1].to_sym
    end
  end

  # Reload (or load) the file's metadata.
  def reload
    @new_info = {}
    read_data
    # run_callbacks_for(:after_load)
    self
  end

  # Commit changes to the PDF.  The return value is a boolean reflecting the
  # success of the operation (This should always be true unless you're
  # utilizing #loot_active_record).
  def save
    create_or_update
  end

  # Like +save+, only raise an exception if the operation fails.<commit_msg>Follow to_path convention; simplify path coercion.<commit_after>require 'tempfile'
require 'forwardable'

# Certain existing libraries have a PDF class; no sense in being unnecessarily
# incompatible.
module PDF #:nodoc:
end unless defined? PDF

# PDF::Toolkit can be used as a simple class, or derived from and tweaked.  The
# following two examples have identical results.
#
#   my_pdf = PDF::Toolkit.open("somefile.pdf")
#   my_pdf.updated_at = Time.now # ModDate
#   my_pdf["SomeAttribute"] = "Some value"
#   my_pdf.save!
#
#   class MyDocument < PDF::Toolkit
#     info_accessor :some_attribute
#     def before_save
#       self.updated_at = Time.now
#     end
#   end
#   my_pdf = MyDocument.open("somefile.pdf")
#   my_pdf.some_attribute = "Some value"
#   my_pdf.save!
#
# Note the use of a +before_save+ callback in the second example.  This is
# the only supported callback unless you use the experimental
# #loot_active_record class method.
#
# == Requirements
#
# PDF::Toolkit requires +pdftk+, which is available from
# http://www.accesspdf.com/pdftk.  For full functionality, also install
# +xpdf+ from http://www.foolabs.com/xpdf.  ActiveSupport (from Ruby on Rails)
# is also required but this dependency may be removed in the future.
#
# == Limitations
#
# Timestamps are written in UTF-16 by +pdftk+, which is not appropriately
# handled by +pdfinfo+.
#
# +pdftk+ requires the owner password, even for simply querying the document.
class PDF::Toolkit
  extend Forwardable

  VERSION = "0.5.0"

  # Raised when something fails with the toolkit
  class Error < ::StandardError; end

  # Raised when an invocation of `pdftk` fails under the cover
  class ExecutionError < Error
    attr_reader :command, :exit_status
    def initialize(msg = nil, cmd = nil, exit_status = nil)
      super(msg)
      @command = cmd
      @exit_status = exit_status
    end
  end

  # Raised when a .pdf file cannot be saved
  class FileNotSaved < Error; end

  require 'pdf/toolkit/native'
  extend Native

  class << self

    # Add an accessor for a key.  If the key is omitted, defaults to a
    # camelized version of the accessor (+foo_bar+ becomes +FooBar+).  The
    # example below illustrates the defaults.
    #
    #   class MyDocument < PDF::Toolkit
    #     info_accessor :created_at, "CreationDate"
    #     info_accessor :updated_at, "ModDate"
    #     info_accessor :author
    #     [:subject, :title, :keywords, :producer, :creator].each do |key|
    #       info_accessor key
    #     end
    #   end
    #
    #   MyDocument.open("document.pdf").created_at
    def info_accessor(accessor_name, info_key = nil)
      info_key ||= camelize_key(accessor_name)
      info_accessors[accessor_name] = info_key
      define_method accessor_name do
        self[info_key]
      end
      define_method "#{accessor_name}=" do |value|
        self[info_key] = value
      end
    end

    def info_accessors
      @info_accessors ||= Hash.new{|h,k|
        if h.has_key?(k.to_s.to_sym)
          h[k.to_s.to_sym]
        elsif k.kind_of?(Symbol)
          camelize_key(k)
        else
          k.dup
        end
      }
    end

    attr_accessor :default_permissions, :default_input_password
    attr_accessor :default_owner_password, :<PASSWORD>user_password
    protected :default_owner_password=, :default_user_password=

    def camelize_key(key)
      if key.to_s.respond_to?(:camelize)
        key.to_s.camelize
      else
        key.to_s.gsub(/_+([^_])/) {$1.upcase}.sub(/^./) {|l|l.upcase}
      end
    end

  end

  info_accessor :created_at, "CreationDate"
  info_accessor :updated_at, "ModDate"
  [:author, :subject, :title, :keywords, :creator, :producer].each do |key|
    info_accessor key
  end

  # Create a new object associated with +filename+ and read in the
  # associated metadata.
  #
  #   my_pdf = PDF::Toolkit.open("document.pdf")
  def self.open(filename,input_password = <PASSWORD>)
    object = new(filename,input_password)
    object.reload
    object
  end

  # Like +open+, only the attributes are lazily loaded.  Under most
  # circumstances,  +open+ is preferred.
  def initialize(filename,input_password = <PASSWORD>)
    coercer = [:to_path, :to_str, :path].find{|meth| filename.respond_to? meth}
    @filename = coercer ? filename.send(coercer) : filename
    @input_password = <PASSWORD>_password || <PASSWORD>
    @owner_password = <PASSWORD>
    @user_password  = <PASSWORD>
    @permissions = default_permissions || []
    @new_info = {}
    callback(:after_initialize) if respond_to?(:after_initialize) && respond_to?(:callback)
    # reload
  end

  def_delegators :"self.class", :default_input_password,
                                :default_owner_password,
                                :default_user_password,
                                :default_permissions

  attr_reader :pdf_ids, :permissions
  attr_writer :owner_password, :user_password

  def page_count
    read_data unless @pages
    @pages
  end
  alias pages page_count

  # Path to the file.
  def path
    @new_filename || @filename
  end
  alias :to_path :path

  # Retrieve the file's version as a symbol.
  #
  #   my_pdf.version # => :"1.4"
  def version
    @version ||= File.open(@filename) do |io|
      io.read(8)[5..-1].to_sym
    end
  end

  # Reload (or load) the file's metadata.
  def reload
    @new_info = {}
    read_data
    # run_callbacks_for(:after_load)
    self
  end

  # Commit changes to the PDF.  The return value is a boolean reflecting the
  # success of the operation (This should always be true unless you're
  # utilizing #loot_active_record).
  def save
    create_or_update
  end

  # Like +save+, only raise an exception if the operation fails.
{'fraction_non_alphanumeric': 0.09711172604635074, 'fraction_numerical': 0.0019024558976132827, 'mean_word_length': 2.9044564483457123, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3579474', 'n_tokens_mistral': 3783, 'n_tokens_neox': 3654, 'n_words': 1036}