<commit_before>(ns multihash.hex
  "Functions to encode and decode bytes as hexadecimal."
  (:require
    [clojure.string :as str]))


(defn- pad-bytes
  "Pads a byte array with leading zeroes (or trims them) to ensure it has the
  given length. Returns the same array if the length is already correct, or a
  new resized array if not."
  [width ^bytes array]
  (let [array-length (alength array)]
    (if (= width array-length)
      array
      (let [array' (byte-array width)]
        (if (< array-length width)
          (System/arraycopy array 0 array' (- width array-length) array-length)
          (System/arraycopy array (- array-length width) array' 0 width))
        array'))))


(defn encode
  "Converts a byte array into a lowercase hexadecimal string. Returns nil for
  empty inputs."
  ^String
  [^bytes value]
  (when (and value (pos? (alength value)))
    (->> (range (alength value))
         (map #(let [b (aget value %)
                     hex #?(:clj (Integer/toHexString (if (neg? b) (+ 256 b) b))
                            :cljs (.toString b 16))]
                 (if (= 1 (count hex))
                   (str "0" hex)
                   hex)))
         (str/join))))


(defn decode
  "Parses a hexadecimal string into a byte array. Ensures that the resulting
  array is zero-padded to match the hex string length."
  ^bytes
  [^String value]
  (when value
    (if (empty? value)
      (byte-array 0)
      (let [length (/ (count value) 2)
            int-bytes (.toByteArray (BigInteger. value 16))]
        (pad-bytes length int-bytes)))))


(defn validate
  "Checks a string to determine whether it's well-formed hexadecimal digest.
  Returns an error message if the argument is invalid."
  ^String
  [digest]
  (cond
    (not (string? digest))
      (str "Value is not a string: " (pr-str digest))

    (not (re-matches #"^[0-9a-fA-F]*$" digest))
      (str "String '" digest "' is not a valid digest: "<commit_msg>Factor out byte/hex helper functions.<commit_after>(ns multihash.hex
  "Functions to encode and decode bytes as hexadecimal."
  (:require
    [clojure.string :as str]))


(defn- byte->hex
  "Converts a single byte value to a two-character hex string."
  [value]
  (let [hex #?(:clj (Integer/toHexString (if (neg? value) (+ 256 value) value))
               :cljs (.toString value 16))]
    (if (= 1 (count hex))
      (str "0" hex)
      hex)))


(defn- hex->byte
  "Converts a two-character hex string into a byte value."
  [hex]
  #?(:clj (let [value (Integer/parseInt hex 16)]
            (if (< 127 value) (- value 256) value))
     :cljs (js/parseInt hex 16)))


(defn encode
  "Converts a byte array into a lowercase hexadecimal string. Returns nil for
  empty inputs."
  ^String
  [^bytes data]
  (when (and data (pos? (alength data)))
    (->> (range (alength data))
         (map #(byte->hex (aget data %)))
         (str/join))))


(defn decode
  "Parses a hexadecimal string into a byte array. Ensures that the resulting
  array is zero-padded to match the hex string length."
  ^bytes
  [^String data]
  (when-not (empty? data)
    (let [length (/ (count data) 2)
          data #?(:clj (byte-array length)
                  :cljs (js/Uint8Array. (js/ArrayBuffer. length)))]
      (dotimes [i length]
        (let [hex (subs data (* 2 i) (* 2 (inc i)))
              b (hex->byte hex)]
          (aset data i (byte b))))
      data)))


(defn validate
  "Checks a string to determine whether it's well-formed hexadecimal digest.
  Returns an error message if the argument is invalid."
  ^String
  [digest]
  (cond
    (not (string? digest))
      (str "Value is not a string: " (pr-str digest))

    (not (re-matches #"^[0-9a-fA-F]*$" digest))
      (str "String '" digest "' is not a valid digest: "
{'fraction_non_alphanumeric': 0.13122171945701358, 'fraction_numerical': 0.010114453021027415, 'mean_word_length': 2.4731977818853976, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '1840697', 'n_tokens_mistral': 1259, 'n_tokens_neox': 1185, 'n_words': 455}