lib/mix/tasks/random/dna.ex

defmodule Mix.Tasks.Bio.Random.Dna do
  @moduledoc """
  Bio.Random.Dna will generate random sequences of DNA. Sequences are written to
  a file with 1 per line, and are separated by a `\n` character.

  ## Command line options
  * `--seed/-s` - RNG seed (defaults to RNG default seeding)

  * `--algorithm/-a` - RNG algorithm (defaults to exsss)

  * `--seq-count/-c` - integer number of sequences to generate (required)

  * `--seq-size/-z` - integer size of sequence to generate (required)

  * `--outfile/-f` - output filename or path (default: random_sequences.txt)

  ## Examples

      $ mix bio.random.dna -s 0 -c 100 -z 50

  This would write 100 sequences of 50 nucleotides to a file called
  `random_sequences.txt`.

      $ mix bio.random.dna -s 0 -c 100 -z 50 -f my_random_sequences.txt

  This would write the same 100 sequences of 50 nucleotides to a file called
  `my_random_sequences.txt`.
  """

  @shortdoc "Generate random dna sequences"
  use Mix.Task

  @options [
    seed: :integer,
    algorithm: :string,
    outfile: :string,
    seq_size: :integer,
    seq_count: :integer
  ]
  @aliases [
    s: :seed,
    a: :algorithm,
    f: :outfile,
    z: :seq_size,
    c: :seq_count
  ]

  def run(options) do
    {opts, _, _} = OptionParser.parse(options, aliases: @aliases, strict: @options)

    algorithm = Keyword.get(opts, :algorithm, "exsss")
    size = Keyword.get(opts, :seq_size)
    count = Keyword.get(opts, :seq_count)
    filename = Keyword.get(opts, :outfile, "random_sequences.txt")

    case {size, count} do
      {nil, nil} -> Mix.raise("Please provide values for --seq-size/-z and --seq-count/-c")
      {_, nil} -> Mix.raise("Please provide value for --seq-count/-c")
      {nil, _} -> Mix.raise("Please provide values for --seq-size/-z")
      _ -> nil
    end

    case opts[:seed] do
      nil -> :rand.seed(String.to_atom(algorithm))
      seed -> :rand.seed(String.to_atom(algorithm), seed)
    end

    File.write(
      filename,
      0..count
      |> Enum.map(fn _ ->
        0..size
        |> Enum.map(fn _ ->
          Enum.random('atgc')
        end)
        |> List.to_string()
      end)
      |> Enum.reduce("", fn line, lines ->
        lines <> "#{line}\n"
      end)
    )
  end
end