Utility functions for SAM header.

(ns cljam.io.sam.util.header
  (:refer-clojure :exclude [sorted?])
  (:require [clojure.string :as cstr]
            [cljam.io.sam.common :as sam-common])
  (:import clojure.lang.IEditableCollection))

parse

e.g. "LN:45 SN:ref" -> {:LN 45, :SN "ref"}

(defn- parse-header-keyvalues
  [keyvalues]
  (into
   {}
   (map (fn [kv]
          (let [[k v] (cstr/split kv #":" 2)]
            [(keyword k)
             (case k
               "LN" (Integer/parseInt v)
               "PI" (Integer/parseInt v)
               v)])))
   keyvalues))

e.g. "@SQ SN:ref LN:45" => [:SQ {:SN "ref" :LN 45}]

(defn parse-header-line
  [line]
  (let [[typ & kvs] (cstr/split line #"\t")]
    [(keyword (subs typ 1)) (parse-header-keyvalues kvs)]))

Wrap a reducing function with finalizing function.

(defn- finalize-rf
  [rf f]
  (fn finalize-rf-inner
    ([] (rf))
    ([x] (f (rf x)))
    ([x y] (rf x y))))

Returns a reducing function which returns a new coll with elements conjoined.

(defn- into-rf
  [to]
  (if (instance? IEditableCollection to)
    (fn into-rf-editable
      ([] (transient to))
      ([x] (with-meta (persistent! x) (meta to)))
      ([r x] (conj! r x)))
    (fn into-rf-non-editable
      ([] to)
      ([x] x)
      ([r x] (conj r x)))))

Returns a reducing function acts like group-by. Second argument rf is a reducing function applied to each group. Default is (into-rf []).

(defn- group-by-rf
  ([keyfn] (group-by-rf keyfn (into-rf [])))
  ([keyfn rf]
   (fn group-by-rf-inner
     ([] (transient {}))
     ([x] (into {} (map (fn group-by-rf-finalize [[k v]] [k (rf v)])) (persistent! x)))
     ([x y] (let [k (keyfn y)]
              (if-let [old (get x k)]
                (assoc! x k (rf old y))
                (assoc! x k (rf (rf) y))))))))

A reducing function which builds a map from parsed headers.

(def into-header
  (-> (group-by-rf first ((map second) (into-rf [])))
      (finalize-rf (fn [m] (if (some? (:HD m)) (update m :HD first) m)))))

Parse a header string, returning a map of the header.

(defn parse-header
  [s]
  (->> (cstr/split-lines s)
       (transduce
        (map parse-header-line)
        into-header)))

stringify

(defn- stringify-header-keyvalues [kv-map]
  (cstr/join \tab
             (map (fn [kv]
                    (let [[k v] (seq kv)]
                      (str (name k) \: v)))
                  kv-map)))

Converts parsed header lines to a string.

(defn stringify-header
  [hdr]
  (cstr/join \newline
             (map (fn [h]
                    (let [[typ kvs] h]
                      (if (= typ :HD)
                        (str "@HD" \tab (stringify-header-keyvalues kvs))
                        (cstr/join \newline
                                   (map #(str \@ (name typ) \tab (stringify-header-keyvalues %)) kvs)))))
                  (seq hdr))))

@HD

Overwrites format version in SAM header.

(defn update-version
  [header]
  (assoc-in header [:HD :VN] sam-common/sam-version))

Unknown sorting order of alignments.

Unsorted of alignments.

Coordinate sorting order of alignments.

queryname sorting order of alignments.

(def ^:const order-unknown
  :unknown)
(def ^:const order-unsorted
  :unsorted)
(def ^:const order-coordinate
  :coordinate)
(def ^:const order-queryname
  :queryname)

Replaces the sorting order field in SAM header.

(defn sorted-by
  [order header]
  (assoc-in header [:HD :SO] (name order)))

Returns sorting order of the sam as Keyword. Returning order is one of the following: :queryname, :coordinate, :unsorted, :unknown.

(defn sort-order
  [header]
  (or (keyword (:SO (:HD header))) order-unknown))

Returns true if the sam is sorted, false if not. It is detected by @HD SO:*** tag in the header.

(defn sorted?
  [header]
  (let [so (:SO (:HD header))]
    (or (= so (name order-queryname))
        (= so (name order-coordinate)))))