///|
fn parse_strand(raw : String) -> Strand {
match raw {
"+" => Plus
"-" => Minus
"?" => Unknown
_ => NotApplicable
}
}
///|
fn optional_field(raw : String) -> String? {
if raw == "." || raw.is_empty() {
None
} else {
Some(raw)
}
}
///|
fn parse_feature_line(
line : String,
line_number : Int,
format : Format,
) -> Feature raise {
let fields = line.split("\t").to_array()
guard fields.length() == 9 else {
fail("line \{line_number}: expected 9 tab-separated fields")
}
let start = @string.parse_int(fields[3].trim())
let end = @string.parse_int(fields[4].trim())
guard start <= end else { fail("line \{line_number}: start must be <= end") }
let attributes = match format {
GFF3 => parse_gff3_attributes(fields[8].to_owned())
GTF => parse_gtf_attributes(fields[8].to_owned())
}
{
seqid: fields[0].to_owned(),
source: fields[1].to_owned(),
feature_type: fields[2].to_owned(),
start,
end,
score: optional_field(fields[5].to_owned()),
strand: parse_strand(fields[6].to_owned()),
phase: optional_field(fields[7].to_owned()),
attributes,
}
}
///|
fn parse_annotation(text : String, format : Format) -> Annotation raise {
let features : Array[Feature] = []
let diagnostics : Array[Diagnostic] = []
for index, line_view in text.split("\n").to_array() {
let line = line_view.trim(chars="\r\n").to_owned()
if !line.is_empty() && !line.has_prefix("#") {
features.push(parse_feature_line(line, index + 1, format))
}
}
{ format, features, diagnostics }
}
///|
pub fn parse_gff3(text : String) -> Annotation raise {
parse_annotation(text, GFF3)
}
///|
pub fn parse_gtf(text : String) -> Annotation raise {
parse_annotation(text, GTF)
}
///|
pub fn parse_auto(text : String) -> Annotation raise {
for line_view in text.split("\n") {
let line = line_view.trim(chars="\r\n").to_owned()
if line.has_prefix("##gff-version") {
return parse_gff3(text)
}
if !line.is_empty() && !line.has_prefix("#") {
if line.contains("gene_id \"") || line.contains("transcript_id \"") {
return parse_gtf(text)
} else {
return parse_gff3(text)
}
}
}
parse_gff3(text)
}