Implementations of the command-line tool. | (ns cljam.tools.cli (:refer-clojure :exclude [sort merge]) (:require [clojure.string :as cstr] [clj-sub-command.core :refer [sub-command candidate-message]] [clojure.tools.cli :refer [parse-opts]] [cljam.io.sam :as sam] [cljam.io.sam.util :as sam-util] [cljam.io.sam.util.header :as header] [cljam.algo.bam-indexer :as bai] [cljam.algo.normal :as normal] [cljam.algo.sorter :as sorter] [cljam.algo.fasta-indexer :as fai] [cljam.algo.dict :as dict] [cljam.algo.depth :as depth] [cljam.algo.convert :as convert] [cljam.algo.level :as level] [cljam.algo.pileup :as plp] [cljam.util.region :as region] [clojure.java.io :as cio]) (:import [java.io Closeable BufferedWriter OutputStreamWriter])) |
CLI functions | |
Exits the program with the status after printing the message. | (defn- exit [^long status message] (binding [*out* (if (zero? status) *out* *err*)] (println message)) (System/exit status)) |
Returns error message strings from the errors. | (defn- error-msg [errors] (str "The following errors occurred while parsing your command:\n\n" (cstr/join \newline errors))) |
(defn- parse-region [region-str] (when region-str (if-let [reg (region/parse-region region-str)] reg (exit 1 (str "Invalid region format: " region-str))))) | |
Sub-commands | |
view command | |
(def ^:private view-cli-options [[nil "--header" "Include header"] ["-f" "--format FORMAT" "Input file format <auto|sam|bam>" :default "auto"] ["-r" "--region REGION" "Only print in region (e.g. chr6:1000-2000)"] ["-h" "--help" "Print help"]]) | |
(defn- view-usage [options-summary] (->> ["Extract/print all or sub alignments in SAM or BAM format." "Usage: cljam view [--header] [-f FORMAT] [-r REGION] <in.bam|sam>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and prints all or sub alignments of input sam/bam. | (defn view [args] (let [{:keys [options arguments errors summary]} (parse-opts args view-cli-options)] (cond (:help options) (exit 0 (view-usage summary)) (not= (count arguments) 1) (exit 1 (view-usage summary)) errors (exit 1 (error-msg errors))) (let [f (first arguments)] (with-open [^Closeable r (condp = (:format options) "auto" (sam/reader f) "sam" (sam/sam-reader f) "bam" (sam/bam-reader f))] (when (:header options) (println (header/stringify-header (sam/read-header r)))) (doseq [aln (if-let [region (parse-region (:region options))] (if (sam/indexed? r) (sam/read-alignments r region) (exit 1 "Random alignment retrieval only works for indexed BAM.")) (sam/read-alignments r))] (println (sam-util/stringify-alignment aln)))))) nil) |
convert command | |
(def ^:private convert-cli-options [["-t" "--thread THREAD" "Number of threads (0 is auto)" :default 0 :parse-fn #(Integer/parseInt %)] ["-i" "--index" "Create Index"] ["-h" "--help" "Print help"]]) | |
(defn- convert-usage [options-summary] (->> ["Convert file format based on the file extension." "Usage: cljam convert [-t THREAD] [-i] <in-file> <out-file> [<out-file-2> [<out-file-3>]]" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and converts file format based on the file extension. | (defn convert [args] (let [{:keys [options arguments errors summary]} (parse-opts args convert-cli-options)] (cond (:help options) (exit 0 (convert-usage summary)) (not (<= 2 (count arguments) 4)) (exit 1 (convert-usage summary)) errors (exit 1 (error-msg errors))) (let [[in & [out & more :as outs]] arguments] (convert/convert in (if more outs out) :n-threads (:thread options) :create-index? (:index options)))) nil) |
normalize command | |
(def ^:private normalize-cli-options [["-i" "--index" "Create Index"] ["-h" "--help"]]) | |
(defn- normalize-usage [options-summary] (->> ["Normalize references of alignments" "Usage: cljam normalize [-i] <in.bam|sam> <out.bam|sam>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and normalizes references of the SAM/BAM format. | (defn normalize [args] (let [{:keys [options arguments errors summary]} (parse-opts args normalize-cli-options)] (cond (:help options) (exit 0 (normalize-usage summary)) (not= (count arguments) 2) (exit 1 (normalize-usage summary)) errors (exit 1 (error-msg errors))) (let [[in out] arguments] (with-open [r (sam/reader in) w (sam/writer out (:index options))] (normal/normalize r w)))) nil) |
sort command | |
(def ^:private sort-cli-options [["-o" "--order ORDER" "Sorting order of alignments <coordinate|queryname>" :default "coordinate"] ["-c" "--chunk CHUNK" "Maximum number of alignments sorted per thread." :default sorter/default-chunk-size :parse-fn #(Integer/parseInt %)] ["-i" "--index" "Create Index"] ["-h" "--help" "Print help"]]) | |
(defn- sort-usage [options-summary] (->> ["Sort alignments by leftmost coordinates." "Usage: cljam sort [-o ORDER] [-c CHUNK] [-i] <in.bam|sam> <out.bam|sam>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and sorts alignments by the specified method. | (defn sort [args] (let [{:keys [options arguments errors summary]} (parse-opts args sort-cli-options)] (cond (:help options) (exit 0 (sort-usage summary)) (not= (count arguments) 2) (exit 1 (sort-usage summary)) errors (exit 1 (error-msg errors))) (let [[in out] arguments] (with-open [r (sam/reader in) w (sam/writer out (:index options))] (condp = (:order options) (clojure.core/name header/order-coordinate) (sorter/sort-by-pos r w {:chunk-size (:chunk options)}) (clojure.core/name header/order-queryname) (sorter/sort-by-qname r w {:chunk-size (:chunk options)}))))) nil) |
index command | |
(def ^:private index-cli-options [["-t" "--thread THREAD" "Number of threads (0 is auto)" :default 0 :parse-fn #(Integer/parseInt %)] ["-h" "--help"]]) | |
(defn- index-usage [options-summary] (->> ["Index sorted alignment for fast random access." "Usage: cljam index [-t THREAD] <in.bam>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and creates index file. | (defn index [args] (let [{:keys [options arguments errors summary]} (parse-opts args index-cli-options)] (cond (:help options) (exit 0 (index-usage summary)) (not= (count arguments) 1) (exit 1 (index-usage summary)) errors (exit 1 (error-msg errors))) (let [f (first arguments)] (bai/create-index f (str f ".bai") :n-threads (:thread options)))) nil) |
pileup command | |
(def ^:private pileup-cli-options [["-s" "--simple" "Output only pileup count."] ["-r" "--region REGION" "Only pileup in region. (e.g. chr6:1000-2000)"] ["-t" "--thread THREAD" "Number of threads (0 is auto)" :default 0 :parse-fn #(Integer/parseInt %)] ["-f" "--ref FASTA" "Reference file in the FASTA format." :default nil] ["-h" "--help"]]) | |
(defn- pileup-usage [options-summary] (->> ["Generate pileup for the BAM file." "Usage: cljam pileup [-s] [-r REGION] [-f FASTA] [-t THREAD] <in.bam>" "Options:" options-summary] (cstr/join \newline))) | |
(defn- depth [f region n-threads] (with-open [r (sam/reader f)] (when-not (sam/indexed? r) (exit 1 "Random alignment retrieval only works for indexed BAM.")) (when-not (sorter/sorted? r) (exit 1 "Not sorted")) (let [regs (or (some-> region parse-region vector) (map (fn [{:keys [len] name' :name}] {:chr name' :start 1 :end len}) (sam/read-refs r)))] (binding [*out* (BufferedWriter. (OutputStreamWriter. System/out)) *flush-on-newline* false] (doseq [reg regs line (depth/lazy-depth r reg {:n-threads n-threads})] (println line)) (flush))))) | |
Parses command line args and pileups BAM file. | (defn pileup [args] (let [{:keys [arguments errors summary] {:keys [help region simple thread] ref' :ref} :options} (parse-opts args pileup-cli-options)] (cond help (exit 0 (pileup-usage summary)) (not= (count arguments) 1) (exit 1 (pileup-usage summary)) errors (exit 1 (error-msg errors))) (let [f (first arguments)] (if simple (depth f region thread) (with-open [w (cio/writer (cio/output-stream System/out))] (plp/create-mpileup f ref' w (parse-region region))))))) |
faidx command | |
(def ^:private faidx-cli-options [["-h" "--help"]]) | |
(defn- faidx-usage [options-summary] (->> ["Index reference sequence in the FASTA format." "Usage: cljam faidx <ref.fasta>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and make fasta index. | (defn faidx [args] (let [{:keys [options arguments errors summary]} (parse-opts args faidx-cli-options)] (cond (:help options) (exit 0 (faidx-usage summary)) (not= (count arguments) 1) (exit 1 (faidx-usage summary)) errors (exit 1 (error-msg errors))) (let [f (first arguments)] (fai/create-index f (str f ".fai")))) nil) |
dict command | |
(def ^:private dict-cli-options [["-h" "--help"]]) | |
(defn- dict-usage [options-summary] (->> ["Create a FASTA sequence dictionary file." "Usage: cljam dict <ref.fasta> <out.dict>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and make fasta dictionary file. | (defn dict [args] (let [{:keys [options arguments errors summary]} (parse-opts args dict-cli-options)] (cond (:help options) (exit 0 (dict-usage summary)) (not= (count arguments) 2) (exit 1 (dict-usage summary)) errors (exit 1 (error-msg errors))) (let [[in out] arguments] (dict/create-dict in out))) nil) |
level command | |
(def ^:private level-cli-options [["-i" "--index" "Create Index"] ["-h" "--help"]]) | |
(defn- level-usage [options-summary] (->> ["Analyze a BAM file and add level information of alignments." "Usage: cljam level [-i] <in.bam> <out.bam>" "Options:" options-summary] (cstr/join \newline))) | |
Parses command line args and adds level of alignments. | (defn level [args] (let [{:keys [options arguments errors summary]} (parse-opts args level-cli-options)] (cond (:help options) (exit 0 (level-usage summary)) (not= (count arguments) 2) (exit 1 (level-usage summary)) errors (exit 1 (error-msg errors))) (let [[in out] arguments] (with-open [r (sam/reader in) w (sam/writer out (:index options))] (level/add-level r w)))) nil) |
version command | |
Prints this software version. | (defn version [_] (let [ver (with-open [r (-> "META-INF/maven/cljam/cljam/pom.properties" (cio/resource) (cio/reader))] (.getProperty (doto (java.util.Properties.) (.load r)) "version"))] (exit 0 ver))) |
Main command | |
Executes the specified subcommand. | (defn run [args] (let [[opts cmd args help cands] (sub-command args "Usage: cljam {view,convert,normalize,sort,index,pileup,faidx,dict,level,version} ..." :options [["-h" "--help" "Show help" :default false :flag true]] :commands [["view" "Extract/print all or sub alignments in SAM or BAM format."] ["convert" "Convert file format based on the file extension."] ["normalize" "Normalize references of alignments."] ["sort" "Sort alignments by leftmost coordinates."] ["index" "Index sorted alignment for fast random access."] ["pileup" "Generate pileup for the BAM file."] ["faidx" "Index reference sequence in the FASTA format."] ["dict" "Create a FASTA sequence dictionary file."] ["level" "Add level of alignments."] ["version" "Print version number."]])] (when (:help opts) (exit 0 help)) (case cmd :view (view args) :convert (convert args) :normalize (normalize args) :sort (sort args) :index (index args) :pileup (pileup args) :faidx (faidx args) :dict (dict args) :level (level args) :version (version args) (exit 1 (str "Invalid command. See 'cljam --help'." (when (seq cands) (str \newline (candidate-message cands)))))))) |