Skip to content

Commit 9a3b4e5

Browse files
committed
Polished the mass alignment output a lot
1 parent da9b13c commit 9a3b4e5

5 files changed

Lines changed: 340 additions & 195 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "align-cli"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
authors = ["Douwe Schulte <d.schulte@uu.nl>"]
66
description = "A command line interface for easily aligning two sequences."
@@ -17,4 +17,4 @@ path = "src/main.rs"
1717
bio = "1.2"
1818
clap ={version="4", features=["derive"]}
1919
colored = "2"
20-
rustyms = "0.1"
20+
rustyms = "0.1.1"

src/main.rs

Lines changed: 12 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use bio::alignment::{
2-
pairwise::{Aligner, Scoring},
3-
Alignment, AlignmentOperation,
4-
};
1+
use bio::alignment::pairwise::{Aligner, Scoring};
52
use clap::Parser;
6-
use colored::Colorize;
73
use rustyms::{MonoIsotopic, Peptide};
8-
use std::fmt::Write;
4+
5+
mod render;
6+
mod stats;
7+
8+
use render::*;
99

1010
#[derive(Parser, Debug)]
1111
#[command(author, version, about, long_about)]
@@ -41,8 +41,7 @@ struct Args {
4141

4242
fn main() {
4343
let args = Args::parse();
44-
if args.global & args.semi_global || args.global && args.local || args.semi_global && args.local
45-
{
44+
if args.global as u8 + args.semi_global as u8 + args.local as u8 > 1 {
4645
panic!("Cannot have multiple alignment types at the same time")
4746
}
4847
if args.mass {
@@ -56,7 +55,11 @@ fn main() {
5655
rustyms::align::Type::Global
5756
};
5857
let alignment = rustyms::align::align::<MonoIsotopic>(a, b, rustyms::align::BLOSUM62, ty);
59-
println!("{:?}\n\n{}", alignment, alignment.summary());
58+
show_mass_alignment(
59+
&alignment,
60+
ty == rustyms::align::Type::Global,
61+
args.line_width,
62+
);
6063
} else if args.x.contains(',') {
6164
for (x, y) in args.x.split(',').zip(args.y.split(',')) {
6265
align(&args, x.as_bytes(), y.as_bytes());
@@ -79,121 +82,6 @@ fn align(args: &Args, x: &[u8], y: &[u8]) {
7982
show_alignment(&alignment, x, y, args.semi_global, args.line_width);
8083
}
8184

82-
fn show_alignment(
83-
alignment: &Alignment,
84-
sequence_x: &[u8],
85-
sequence_y: &[u8],
86-
semi_global: bool,
87-
line_width: usize,
88-
) {
89-
let (identical, similar, gaps, length) = score_stats(alignment, sequence_x, sequence_y);
90-
91-
println!(
92-
"Identity: {} {}, Similarity: {} {}, Gaps: {:} {}, Score: {}{}\n",
93-
format!("{:.3}", identical as f64 / length as f64).blue(),
94-
format!("({}/{})", identical, length).dimmed(),
95-
format!("{:.3}", similar as f64 / length as f64).cyan(),
96-
format!("({}/{})", similar, length).dimmed(),
97-
format!("{:.3}", gaps as f64 / length as f64).green(),
98-
format!("({}/{})", gaps, length).dimmed(),
99-
format!("{}", alignment.score).yellow(),
100-
if semi_global {
101-
format!("\nCIGAR: {}", alignment.cigar(false))
102-
} else {
103-
String::new()
104-
}
105-
);
106-
107-
macro_rules! line {
108-
($lines:ident, $x:expr, $y: expr, $c:expr, $colour:ident) => {
109-
write!(&mut $lines.0, "{}", $x.$colour()).unwrap();
110-
write!(&mut $lines.1, "{}", $y.$colour()).unwrap();
111-
write!(&mut $lines.2, "{}", $c.$colour()).unwrap();
112-
};
113-
}
114-
115-
let mut lines = (String::new(), String::new(), String::new());
116-
let mut numbers = String::new();
117-
let mut x = alignment.xstart;
118-
let mut y = alignment.ystart;
119-
// Similar: · Gap: ◯ Identical: ∘ ⏺ ∘◯◦ ⚪ ⚫ ⬤ ⭘ 🞄 ∘ ○ ● ◦ ◯ ⴰ ⨉⨯+-
120-
for (index, step) in alignment.operations.iter().enumerate() {
121-
match step {
122-
AlignmentOperation::Del => {
123-
line!(
124-
lines,
125-
"-",
126-
String::from_utf8_lossy(&[sequence_y[y]]),
127-
"+",
128-
yellow
129-
);
130-
y += 1;
131-
}
132-
AlignmentOperation::Ins => {
133-
line!(
134-
lines,
135-
String::from_utf8_lossy(&[sequence_x[x]]),
136-
"-",
137-
"+",
138-
yellow
139-
);
140-
x += 1;
141-
}
142-
AlignmentOperation::Subst => {
143-
if SIMILAR.contains(&(sequence_x[x], sequence_y[y])) {
144-
line!(
145-
lines,
146-
String::from_utf8_lossy(&[sequence_x[x]]),
147-
String::from_utf8_lossy(&[sequence_y[y]]),
148-
"-",
149-
green
150-
);
151-
} else {
152-
line!(
153-
lines,
154-
String::from_utf8_lossy(&[sequence_x[x]]),
155-
String::from_utf8_lossy(&[sequence_y[y]]),
156-
"⨯",
157-
red
158-
);
159-
}
160-
x += 1;
161-
y += 1;
162-
}
163-
AlignmentOperation::Match => {
164-
line!(
165-
lines,
166-
String::from_utf8_lossy(&[sequence_x[x]]),
167-
String::from_utf8_lossy(&[sequence_y[y]]),
168-
" ",
169-
normal
170-
);
171-
x += 1;
172-
y += 1;
173-
}
174-
AlignmentOperation::Xclip(_) => todo!(),
175-
AlignmentOperation::Yclip(_) => todo!(),
176-
}
177-
write!(&mut numbers, " ").unwrap();
178-
if (index + 1) % 10 == 0 {
179-
numbers.truncate(numbers.len() - number_length(index + 1));
180-
write!(&mut numbers, "{}", index + 1).unwrap();
181-
}
182-
if (index + 1) % line_width == 0 {
183-
println!("{}", numbers.dimmed());
184-
println!("{}", lines.0);
185-
println!("{}", lines.1);
186-
println!("{}", lines.2);
187-
lines = (String::new(), String::new(), String::new());
188-
numbers = String::new();
189-
}
190-
}
191-
println!("{}", numbers.dimmed());
192-
println!("{}", lines.0);
193-
println!("{}", lines.1);
194-
println!("{}", lines.2);
195-
}
196-
19785
pub fn get_blosum62(gap_open: i32, gap_extend: i32) -> Scoring<impl Fn(u8, u8) -> i32> {
19886
const TRANSLATION_TABLE: &[usize] = include!("translation_table.txt");
19987
const BLOSUM62: &[&[i32]] = include!("blosum62.txt");
@@ -210,69 +98,3 @@ pub fn get_blosum62(gap_open: i32, gap_extend: i32) -> Scoring<impl Fn(u8, u8) -
21098
};
21199
Scoring::new(gap_open, gap_extend, match_fn)
212100
}
213-
214-
pub fn score_stats(
215-
alignment: &Alignment,
216-
sequence_x: &[u8],
217-
sequence_y: &[u8],
218-
) -> (usize, usize, usize, usize) {
219-
let x_len = sequence_x.len();
220-
let y_len = sequence_y.len();
221-
let mut x = alignment.xstart;
222-
let mut y = alignment.ystart;
223-
let mut identical = 0;
224-
let mut similar = 0;
225-
let mut gaps = 0;
226-
for step in &alignment.operations {
227-
match step {
228-
AlignmentOperation::Del => {
229-
y += 1;
230-
gaps += 1;
231-
}
232-
AlignmentOperation::Ins => {
233-
x += 1;
234-
gaps += 1;
235-
}
236-
AlignmentOperation::Subst => {
237-
if SIMILAR.contains(&(sequence_x[x], sequence_y[y])) {
238-
similar += 1;
239-
}
240-
x += 1;
241-
y += 1;
242-
}
243-
AlignmentOperation::Match => {
244-
x += 1;
245-
y += 1;
246-
identical += 1;
247-
}
248-
AlignmentOperation::Xclip(_) => todo!(),
249-
AlignmentOperation::Yclip(_) => todo!(),
250-
}
251-
}
252-
debug_assert!(x == alignment.xend);
253-
debug_assert!(y == alignment.yend);
254-
(identical, similar + identical, gaps, (x_len).max(y_len))
255-
}
256-
257-
fn number_length(i: usize) -> usize {
258-
if i == 0 {
259-
1
260-
} else {
261-
i.ilog10() as usize + 1
262-
}
263-
}
264-
265-
const SIMILAR: &[(u8, u8)] = &[(b'I', b'L'), (b'L', b'I'), (b'D', b'N'), (b'N', b'D')];
266-
267-
#[test]
268-
fn number_length_test() {
269-
assert_eq!(number_length(0), 1);
270-
assert_eq!(number_length(1), 1);
271-
assert_eq!(number_length(9), 1);
272-
assert_eq!(number_length(10), 2);
273-
assert_eq!(number_length(11), 2);
274-
assert_eq!(number_length(99), 2);
275-
assert_eq!(number_length(100), 3);
276-
assert_eq!(number_length(1000), 4);
277-
assert_eq!(number_length(10000), 5);
278-
}

0 commit comments

Comments
 (0)