Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/reader/streaming_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ impl<T: Read> StreamParser<T> {

pub fn read_one_record(&mut self) -> Result<Option<Seq>, GbParserError> {
// skip preamble such as the header of Genbank .SEQ files
self.try_run_parser(&skip_preamble, false)?;
let locus = match self.run_parser(&locus, true) {
self.try_run_parser(skip_preamble, false)?;
let locus = match self.run_parser(locus, true) {
Ok(locus) => locus,
Err(StreamParserError::EOF) => {
return Ok(None);
Expand All @@ -257,14 +257,14 @@ impl<T: Read> StreamParser<T> {
division: locus.division,
..Seq::empty()
};
let fields = self.run_parser_many0(&any_field)?;
let fields = self.run_parser_many0(any_field)?;
let mut seq = fill_seq_fields(seq, fields).map_err(GbParserError::SyntaxError)?; //TODO: Proper error handling
if self.try_run_parser(&features_header, true)?.is_some() {
seq.features = self.run_parser_many0(&feature)?;
if self.try_run_parser(features_header, true)?.is_some() {
seq.features = self.run_parser_many0(feature)?;
}
self.try_run_parser(&base_count, true)?;
seq.contig = self.try_run_parser(&contig_text, true)?;
if self.try_run_parser(&origin_tag, true)?.is_some() {
self.try_run_parser(base_count, true)?;
seq.contig = self.try_run_parser(contig_text, true)?;
if self.try_run_parser(origin_tag, true)?.is_some() {
seq.seq = self.parse_seq_data(seq.len)?;
}

Expand All @@ -274,8 +274,8 @@ impl<T: Read> StreamParser<T> {
return Ok(Some(seq));
}

self.run_parser(&double_slash, true)?;
self.run_parser_many0(&line_ending_type_hack)?;
self.run_parser(double_slash, true)?;
self.run_parser_many0(line_ending_type_hack)?;
Ok(Some(seq))
}
}
16 changes: 8 additions & 8 deletions src/seq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ use crate::dna::revcomp;
pub use crate::{FeatureKind, QualifierKey};

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
/// A very simple Date struct so we don't have to rely on chrono
pub struct Date {
year: i32,
month: u32,
day: u32,
}

#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DateError;

impl Date {
Expand Down Expand Up @@ -66,7 +66,7 @@ impl fmt::Display for Date {
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum GapLength {
/// gap(n)
Known(i64),
Expand All @@ -77,10 +77,10 @@ pub enum GapLength {
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Before(pub bool);
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct After(pub bool);

/// Represents a Genbank "location", used to specify the location of
Expand Down Expand Up @@ -351,7 +351,7 @@ impl Feature {
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Topology {
Linear,
Circular,
Expand All @@ -368,14 +368,14 @@ impl fmt::Display for Topology {
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Source {
pub source: String,
pub organism: Option<String>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Reference {
pub description: String,
pub authors: Option<String>,
Expand Down
46 changes: 39 additions & 7 deletions src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,10 @@ fn wrap_location<T: Write>(
}

/// Wrap a line of text in a Genbank file. The line is appended to `line`, which
/// may already contain up to `max_len` - 1 bytes. Wrapping occurs on a space if
/// possible. Outputs valid UTF-8, however lines are wrapped to their length in
/// bytes, not characters, and UTF-8 characters that consist of multiple `char`s
/// are likely to get mangled. Returns the rest of `input`. If `quote` is set,
/// `"` will be escaped with `""`
/// may already contain up to `max_len` - 1 bytes. Outputs valid UTF-8, however
/// lines are wrapped to their length in bytes, not characters, and UTF-8
/// characters that consist of multiple `char`s are likely to get mangled.
/// Returns the rest of `input`. If `quote` is set, `"` will be escaped with `""`
fn wrap_get_line<'a>(line: &mut String, input: &'a str, max_len: usize, quote: bool) -> &'a str {
assert!(line.len() < max_len);
let mut consumed = 0;
Expand Down Expand Up @@ -356,7 +355,7 @@ fn wrap_get_line<'a>(line: &mut String, input: &'a str, max_len: usize, quote: b
// try to wrap at last space
if let Some(last_space_in) = last_space_in {
line.truncate(last_space_out);
&input[last_space_in + 1..]
&input[last_space_in..]
} else {
// there's no way to split the line nicely
&input[consumed..]
Expand Down Expand Up @@ -400,7 +399,40 @@ fn wrap_text<T: Write>(
pub mod tests {

use super::*;
use std::io::BufRead;
use crate::reader::SeqReader;
use crate::seq::{Feature, Location, Seq};
use std::io::Read;
use std::io::{BufRead, BufReader};

#[test]
fn multiline_spaces_retained_after_roundtrip() {
// ensures that qualifier values that are split across multiple lines when writing
// don't lose any spaces when they're parsed again
let product = "Lipopolysaccharide export system ATP-binding protein LptB".to_string();
let feat = Feature {
kind: feature_kind!("CDS"),
location: Location::simple_range(100, 200),
qualifiers: vec![(qualifier_key!("product"), Some(product.clone()))],
};
let mut seq = Seq::empty();
seq.features = vec![feat];
let mut out = Vec::new();
SeqWriter::new(&mut out).write(&seq).unwrap();
let mut text = String::new();
std::io::Cursor::new(&out)
.read_to_string(&mut text)
.unwrap();

let buf = BufReader::new(text.as_bytes());
let mut roundtrip_seq = SeqReader::new(buf);
let record = roundtrip_seq.next().unwrap().unwrap();
let roundtrip_product = record.features[0]
.qualifier_values(qualifier_key!("product"))
.next()
.unwrap()
.replace('\n', "");
assert_eq!(product, roundtrip_product);
}

#[test]
fn truncate_locus() {
Expand Down