<commit_before>module Mellon
  class Keychain
    class << self
      def find(name)
        quoted = Regexp.quote(name)
        regexp = Regexp.new(quoted, Regexp::IGNORECASE)

        keychain = list.find do |keychain|
          keychain.path =~ regexp
        end

        if keychain.nil?
          raise KeyError, "Could not find keychain “#{name}” in #{list.map(&:name).join(", ")}"
        end

        keychain
      end

      def default
        keychain_path = Mellon.security("default-keychain")[KEYCHAIN_REGEXP, 1]
        Keychain.new(keychain_path)
      end

      def list
        Mellon.security("list-keychains").scan(KEYCHAIN_REGEXP).map do |(keychain_path)|
          Keychain.new(keychain_path)
        end
      end
    end

    def initialize(path)
      @path = path
      @name = File.basename(path, ".keychain")
    end

    attr_reader :path
    attr_reader :name

    def open
      command "unlock-keychain"
      yield
    ensure
      command "lock-keychain"
    end

    private

    def command(*command, &block)
      command += [path]
      Mellon.security *command, &block
    end
  end
end<commit_msg>Add Keychain#read<commit_after>require "plist"

module Mellon
  class Keychain
    class << self
      def find(name)
        quoted = Regexp.quote(name)
        regexp = Regexp.new(quoted, Regexp::IGNORECASE)

        keychain = list.find do |keychain|
          keychain.path =~ regexp
        end

        if keychain.nil?
          raise KeyError, "Could not find keychain “#{name}” in #{list.map(&:name).join(", ")}"
        end

        keychain
      end

      def default
        keychain_path = Mellon.security("default-keychain")[KEYCHAIN_REGEXP, 1]
        Keychain.new(keychain_path)
      end

      def list
        Mellon.security("list-keychains").scan(KEYCHAIN_REGEXP).map do |(keychain_path)|
          Keychain.new(keychain_path)
        end
      end
    end

    def initialize(path)
      @path = path
      @name = File.basename(path, ".keychain")
    end

    attr_reader :path
    attr_reader :name

    def open
      command "unlock-keychain"
      yield
    ensure
      command "lock-keychain"
    end

    def read(key)
      command "find-generic-password", "-g", "-l", key do |info, password_info|
        [parse_info(info), parse_password(password_info)]
      end
    end
    private

    def command(*command, &block)
      command += [path]
      Mellon.security *command, &block
    end

    def parse_info(info)
      extract = lambda { |key| info[/#{key}.+=(?:<NULL>|[^"]*"(.+)")/, 1].to_s }
      {
        account_name: extract["acct"],
        kind: extract["desc"],
        type: extract["type"],
        label: extract["0x00000007"],
        service_name: extract["svce"],
      }
    end

    def parse_password(password_info)
      unpacked = password_info[/password: 0x([a-f0-9]+)/i, 1]
      password = [unpacked].pack("H*")

      parsed = Plist.parse_xml(password)
      if parsed and parsed["NOTE"]
        parsed["NOTE"]
      else
        password
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.11734028683181226, 'fraction_numerical': 0.005215123859191656, 'mean_word_length': 2.144467213114754, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 1}, 'pii_count': 0, 'substrings_counts': 1, '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': '1320906', 'n_tokens_mistral': 1019, 'n_tokens_neox': 980, 'n_words': 219}