rust-annovar 0.1.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +1034 -0
- data/Cargo.toml +33 -0
- data/LICENSE-APACHE +15 -0
- data/LICENSE-MIT +21 -0
- data/README.md +298 -0
- data/bin/rust-annovar +6 -0
- data/ext/rust_annovar/extconf.rb +34 -0
- data/lib/rust_annovar/version.rb +5 -0
- data/lib/rust_annovar.rb +35 -0
- data/src/database.rs +592 -0
- data/src/gene.rs +520 -0
- data/src/io.rs +217 -0
- data/src/lib.rs +12 -0
- data/src/main.rs +376 -0
- data/src/model.rs +123 -0
- data/src/pipeline.rs +288 -0
- metadata +68 -0
data/src/model.rs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
use anyhow::{Result, bail};
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
|
|
4
|
+
/// Canonical internal representation. `start == end` represents an insertion
|
|
5
|
+
/// between bases; all other records use a zero-based half-open interval.
|
|
6
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
7
|
+
pub struct Variant {
|
|
8
|
+
pub chrom: String,
|
|
9
|
+
pub output_chrom: String,
|
|
10
|
+
pub start: u64,
|
|
11
|
+
pub end: u64,
|
|
12
|
+
pub reference: String,
|
|
13
|
+
pub alternate: String,
|
|
14
|
+
pub extra: Vec<String>,
|
|
15
|
+
pub source_line: usize,
|
|
16
|
+
pub source_record: Option<usize>,
|
|
17
|
+
pub allele_index: usize,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl Variant {
|
|
21
|
+
pub fn new(
|
|
22
|
+
chrom: impl Into<String>,
|
|
23
|
+
start: u64,
|
|
24
|
+
end: u64,
|
|
25
|
+
reference: impl Into<String>,
|
|
26
|
+
alternate: impl Into<String>,
|
|
27
|
+
) -> Result<Self> {
|
|
28
|
+
if end < start {
|
|
29
|
+
bail!("variant end ({end}) precedes start ({start})");
|
|
30
|
+
}
|
|
31
|
+
let chrom = normalize_chrom(&chrom.into());
|
|
32
|
+
Ok(Self {
|
|
33
|
+
output_chrom: chrom.clone(),
|
|
34
|
+
chrom,
|
|
35
|
+
start,
|
|
36
|
+
end,
|
|
37
|
+
reference: reference.into().to_ascii_uppercase(),
|
|
38
|
+
alternate: alternate.into().to_ascii_uppercase(),
|
|
39
|
+
extra: Vec::new(),
|
|
40
|
+
source_line: 0,
|
|
41
|
+
source_record: None,
|
|
42
|
+
allele_index: 0,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pub fn key(&self) -> VariantKey<'_> {
|
|
47
|
+
VariantKey {
|
|
48
|
+
chrom: &self.chrom,
|
|
49
|
+
start: self.start,
|
|
50
|
+
end: self.end,
|
|
51
|
+
reference: &self.reference,
|
|
52
|
+
alternate: &self.alternate,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn avinput_fields(&self) -> [String; 5] {
|
|
57
|
+
let (start, end, reference, alternate) = if self.start == self.end {
|
|
58
|
+
(
|
|
59
|
+
self.start,
|
|
60
|
+
self.start,
|
|
61
|
+
"-".to_string(),
|
|
62
|
+
display_allele(&self.alternate),
|
|
63
|
+
)
|
|
64
|
+
} else {
|
|
65
|
+
(
|
|
66
|
+
self.start + 1,
|
|
67
|
+
self.end,
|
|
68
|
+
display_allele(&self.reference),
|
|
69
|
+
display_allele(&self.alternate),
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
[
|
|
73
|
+
self.output_chrom.clone(),
|
|
74
|
+
start.to_string(),
|
|
75
|
+
end.to_string(),
|
|
76
|
+
reference,
|
|
77
|
+
alternate,
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub fn overlaps(&self, start: u64, end: u64) -> bool {
|
|
82
|
+
if self.start == self.end {
|
|
83
|
+
self.start >= start && self.start <= end
|
|
84
|
+
} else {
|
|
85
|
+
self.start < end && start < self.end
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
91
|
+
pub struct VariantKey<'a> {
|
|
92
|
+
pub chrom: &'a str,
|
|
93
|
+
pub start: u64,
|
|
94
|
+
pub end: u64,
|
|
95
|
+
pub reference: &'a str,
|
|
96
|
+
pub alternate: &'a str,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
100
|
+
pub enum AnnotationKind {
|
|
101
|
+
Filter,
|
|
102
|
+
Region,
|
|
103
|
+
Gene,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
107
|
+
pub struct Annotation {
|
|
108
|
+
pub protocol: String,
|
|
109
|
+
pub kind: AnnotationKind,
|
|
110
|
+
pub values: Vec<String>,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn normalize_chrom(chrom: &str) -> String {
|
|
114
|
+
chrom.strip_prefix("chr").unwrap_or(chrom).to_string()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn display_allele(allele: &str) -> String {
|
|
118
|
+
if allele.is_empty() {
|
|
119
|
+
"-".to_string()
|
|
120
|
+
} else {
|
|
121
|
+
allele.to_string()
|
|
122
|
+
}
|
|
123
|
+
}
|
data/src/pipeline.rs
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
use crate::database::{FilterDatabase, RegionDatabase};
|
|
2
|
+
use crate::gene::GeneDatabase;
|
|
3
|
+
use crate::io::{VcfDocument, open_writer};
|
|
4
|
+
use crate::model::{Annotation, Variant};
|
|
5
|
+
use anyhow::{Context, Result, bail};
|
|
6
|
+
use rayon::prelude::*;
|
|
7
|
+
use std::collections::HashMap;
|
|
8
|
+
use std::io::Write;
|
|
9
|
+
use std::path::{Path, PathBuf};
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
12
|
+
pub enum Operation {
|
|
13
|
+
Gene,
|
|
14
|
+
Region,
|
|
15
|
+
Filter,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Operation {
|
|
19
|
+
pub fn parse(value: &str) -> Result<Self> {
|
|
20
|
+
match value {
|
|
21
|
+
"g" | "gx" | "gene" => Ok(Self::Gene),
|
|
22
|
+
"r" | "region" => Ok(Self::Region),
|
|
23
|
+
"f" | "filter" => Ok(Self::Filter),
|
|
24
|
+
_ => bail!("unknown operation {value:?}; use g, r, or f"),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Clone)]
|
|
30
|
+
pub struct Protocol {
|
|
31
|
+
pub name: String,
|
|
32
|
+
pub operation: Operation,
|
|
33
|
+
pub database: PathBuf,
|
|
34
|
+
pub fasta: Option<PathBuf>,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[derive(Debug)]
|
|
38
|
+
pub struct TableResult {
|
|
39
|
+
pub headers: Vec<String>,
|
|
40
|
+
pub rows: Vec<Vec<String>>,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub fn resolve_protocols(
|
|
44
|
+
db_dir: &Path,
|
|
45
|
+
build: &str,
|
|
46
|
+
names: &[String],
|
|
47
|
+
operations: &[Operation],
|
|
48
|
+
) -> Result<Vec<Protocol>> {
|
|
49
|
+
if names.len() != operations.len() {
|
|
50
|
+
bail!("protocol and operation counts differ");
|
|
51
|
+
}
|
|
52
|
+
names
|
|
53
|
+
.iter()
|
|
54
|
+
.zip(operations)
|
|
55
|
+
.map(|(name, operation)| {
|
|
56
|
+
let database = db_dir.join(format!("{build}_{name}.txt"));
|
|
57
|
+
if !database.exists() {
|
|
58
|
+
bail!("database does not exist: {}", database.display());
|
|
59
|
+
}
|
|
60
|
+
let fasta = (*operation == Operation::Gene)
|
|
61
|
+
.then(|| db_dir.join(format!("{build}_{name}Mrna.fa")))
|
|
62
|
+
.filter(|p| p.exists());
|
|
63
|
+
Ok(Protocol {
|
|
64
|
+
name: name.clone(),
|
|
65
|
+
operation: *operation,
|
|
66
|
+
database,
|
|
67
|
+
fasta,
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
.collect()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub fn annotate_table(
|
|
74
|
+
variants: &[Variant],
|
|
75
|
+
protocols: &[Protocol],
|
|
76
|
+
nastring: &str,
|
|
77
|
+
) -> Result<TableResult> {
|
|
78
|
+
enum Loaded {
|
|
79
|
+
Filter(FilterDatabase),
|
|
80
|
+
Region(RegionDatabase),
|
|
81
|
+
Gene(GeneDatabase),
|
|
82
|
+
}
|
|
83
|
+
let mut loaded = Vec::new();
|
|
84
|
+
let mut headers = vec!["Chr", "Start", "End", "Ref", "Alt"]
|
|
85
|
+
.into_iter()
|
|
86
|
+
.map(str::to_string)
|
|
87
|
+
.collect::<Vec<_>>();
|
|
88
|
+
for protocol in protocols {
|
|
89
|
+
let database = match protocol.operation {
|
|
90
|
+
Operation::Filter => {
|
|
91
|
+
let db = FilterDatabase::load_for_variants(&protocol.database, variants)?;
|
|
92
|
+
headers.extend(
|
|
93
|
+
db.headers
|
|
94
|
+
.iter()
|
|
95
|
+
.map(|header| qualified_header(header, &protocol.name)),
|
|
96
|
+
);
|
|
97
|
+
Loaded::Filter(db)
|
|
98
|
+
}
|
|
99
|
+
Operation::Region => {
|
|
100
|
+
let db = RegionDatabase::load(&protocol.database)?;
|
|
101
|
+
headers.extend(
|
|
102
|
+
db.headers
|
|
103
|
+
.iter()
|
|
104
|
+
.map(|header| qualified_header(header, &protocol.name)),
|
|
105
|
+
);
|
|
106
|
+
Loaded::Region(db)
|
|
107
|
+
}
|
|
108
|
+
Operation::Gene => {
|
|
109
|
+
headers.extend(
|
|
110
|
+
["Func", "Gene", "GeneDetail", "ExonicFunc", "AAChange"]
|
|
111
|
+
.map(|header| format!("{header}.{}", protocol.name)),
|
|
112
|
+
);
|
|
113
|
+
Loaded::Gene(GeneDatabase::load(
|
|
114
|
+
&protocol.database,
|
|
115
|
+
protocol.fasta.as_deref(),
|
|
116
|
+
)?)
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
loaded.push(database);
|
|
120
|
+
}
|
|
121
|
+
let extra_width = variants
|
|
122
|
+
.iter()
|
|
123
|
+
.map(|variant| variant.extra.len())
|
|
124
|
+
.max()
|
|
125
|
+
.unwrap_or(0);
|
|
126
|
+
headers.extend((1..=extra_width).map(|index| format!("Otherinfo{index}")));
|
|
127
|
+
let rows = variants
|
|
128
|
+
.par_iter()
|
|
129
|
+
.map(|variant| {
|
|
130
|
+
let mut row = variant.avinput_fields().to_vec();
|
|
131
|
+
for (protocol, database) in protocols.iter().zip(&loaded) {
|
|
132
|
+
let (annotation, width) = match database {
|
|
133
|
+
Loaded::Filter(db) => (db.annotate(variant, &protocol.name), db.headers.len()),
|
|
134
|
+
Loaded::Region(db) => {
|
|
135
|
+
(db.annotate(variant, &protocol.name, 0.0), db.headers.len())
|
|
136
|
+
}
|
|
137
|
+
Loaded::Gene(db) => (Some(db.annotate(variant, &protocol.name, 2, 1000)), 5),
|
|
138
|
+
};
|
|
139
|
+
append_annotation(&mut row, annotation, width, nastring);
|
|
140
|
+
}
|
|
141
|
+
row.extend(variant.extra.clone());
|
|
142
|
+
while row.len() < headers.len() {
|
|
143
|
+
row.push(nastring.to_string());
|
|
144
|
+
}
|
|
145
|
+
row
|
|
146
|
+
})
|
|
147
|
+
.collect();
|
|
148
|
+
Ok(TableResult { headers, rows })
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub fn write_table(result: &TableResult, path: &Path, csv: bool) -> Result<()> {
|
|
152
|
+
let mut writer = open_writer(path)?;
|
|
153
|
+
let delimiter = if csv { ',' } else { '\t' };
|
|
154
|
+
write_record(&mut writer, &result.headers, delimiter)?;
|
|
155
|
+
for row in &result.rows {
|
|
156
|
+
write_record(&mut writer, row, delimiter)?;
|
|
157
|
+
}
|
|
158
|
+
Ok(())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pub fn write_annotated_vcf(
|
|
162
|
+
document: &VcfDocument,
|
|
163
|
+
result: &TableResult,
|
|
164
|
+
protocols: &[Protocol],
|
|
165
|
+
path: &Path,
|
|
166
|
+
nastring: &str,
|
|
167
|
+
) -> Result<()> {
|
|
168
|
+
let mut writer = open_writer(path)?;
|
|
169
|
+
for header in &document.headers {
|
|
170
|
+
if header.starts_with("#CHROM") {
|
|
171
|
+
for protocol in protocols {
|
|
172
|
+
writeln!(
|
|
173
|
+
writer,
|
|
174
|
+
"##INFO=<ID=FA_{},Number=.,Type=String,Description=\"RustAnnovar {} annotation\">",
|
|
175
|
+
sanitize_info(&protocol.name),
|
|
176
|
+
protocol.name
|
|
177
|
+
)?;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
writeln!(writer, "{header}")?;
|
|
181
|
+
}
|
|
182
|
+
let mut by_record: HashMap<usize, Vec<&Vec<String>>> = HashMap::new();
|
|
183
|
+
for (variant, row) in document.variants.iter().zip(&result.rows) {
|
|
184
|
+
if let Some(record) = variant.source_record {
|
|
185
|
+
by_record.entry(record).or_default().push(row);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let mut offsets = Vec::new();
|
|
189
|
+
let mut cursor = 5usize;
|
|
190
|
+
for protocol in protocols {
|
|
191
|
+
let width = match protocol.operation {
|
|
192
|
+
Operation::Gene => 5,
|
|
193
|
+
_ => result.headers[cursor..]
|
|
194
|
+
.iter()
|
|
195
|
+
.take_while(|h| h.ends_with(&format!(".{}", protocol.name)))
|
|
196
|
+
.count()
|
|
197
|
+
.max(1),
|
|
198
|
+
};
|
|
199
|
+
offsets.push((cursor, width));
|
|
200
|
+
cursor += width;
|
|
201
|
+
}
|
|
202
|
+
for (record_index, original) in document.records.iter().enumerate() {
|
|
203
|
+
let mut record = original.clone();
|
|
204
|
+
if let Some(rows) = by_record.get(&record_index) {
|
|
205
|
+
let mut additions = Vec::new();
|
|
206
|
+
for (protocol, (start, width)) in protocols.iter().zip(&offsets) {
|
|
207
|
+
let alleles = rows
|
|
208
|
+
.iter()
|
|
209
|
+
.map(|row| row[*start..*start + *width].join("|"))
|
|
210
|
+
.collect::<Vec<_>>()
|
|
211
|
+
.join(",");
|
|
212
|
+
if alleles != nastring {
|
|
213
|
+
additions.push(format!(
|
|
214
|
+
"FA_{}={}",
|
|
215
|
+
sanitize_info(&protocol.name),
|
|
216
|
+
escape_info(&alleles)
|
|
217
|
+
));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if !additions.is_empty() {
|
|
221
|
+
if record[7] == "." {
|
|
222
|
+
record[7].clear();
|
|
223
|
+
} else {
|
|
224
|
+
record[7].push(';');
|
|
225
|
+
}
|
|
226
|
+
record[7].push_str(&additions.join(";"));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
writeln!(writer, "{}", record.join("\t"))?;
|
|
230
|
+
}
|
|
231
|
+
Ok(())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn append_annotation(
|
|
235
|
+
row: &mut Vec<String>,
|
|
236
|
+
annotation: Option<Annotation>,
|
|
237
|
+
width: usize,
|
|
238
|
+
nastring: &str,
|
|
239
|
+
) {
|
|
240
|
+
if let Some(annotation) = annotation {
|
|
241
|
+
let before = row.len();
|
|
242
|
+
row.extend(annotation.values);
|
|
243
|
+
while row.len() < before + width {
|
|
244
|
+
row.push(nastring.to_string());
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
row.extend(std::iter::repeat_n(nastring.to_string(), width));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fn qualified_header(header: &str, protocol: &str) -> String {
|
|
252
|
+
if header == "value" {
|
|
253
|
+
protocol.to_string()
|
|
254
|
+
} else {
|
|
255
|
+
format!("{header}.{protocol}")
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn write_record(writer: &mut dyn Write, fields: &[String], delimiter: char) -> Result<()> {
|
|
260
|
+
let encoded = fields
|
|
261
|
+
.iter()
|
|
262
|
+
.map(|value| {
|
|
263
|
+
if delimiter == ','
|
|
264
|
+
&& (value.contains(',') || value.contains('"') || value.contains('\n'))
|
|
265
|
+
{
|
|
266
|
+
format!("\"{}\"", value.replace('"', "\"\""))
|
|
267
|
+
} else {
|
|
268
|
+
value.clone()
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
.collect::<Vec<_>>();
|
|
272
|
+
writeln!(writer, "{}", encoded.join(&delimiter.to_string())).context("failed writing output")
|
|
273
|
+
}
|
|
274
|
+
fn sanitize_info(value: &str) -> String {
|
|
275
|
+
value
|
|
276
|
+
.chars()
|
|
277
|
+
.map(|c| {
|
|
278
|
+
if c.is_ascii_alphanumeric() || c == '_' {
|
|
279
|
+
c
|
|
280
|
+
} else {
|
|
281
|
+
'_'
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
.collect()
|
|
285
|
+
}
|
|
286
|
+
fn escape_info(value: &str) -> String {
|
|
287
|
+
value.replace([' ', ';', '='], "_")
|
|
288
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rust-annovar
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.beta.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Longtao Huangfu
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: |-
|
|
14
|
+
RustAnnovar is an open-beta genomic variant annotation command-line tool.
|
|
15
|
+
This source gem compiles the Rust executable during installation and reads
|
|
16
|
+
supported ANNOVAR humandb formats supplied separately by the user.
|
|
17
|
+
email:
|
|
18
|
+
- ydlongtao@gmail.com
|
|
19
|
+
executables:
|
|
20
|
+
- rust-annovar
|
|
21
|
+
extensions:
|
|
22
|
+
- ext/rust_annovar/extconf.rb
|
|
23
|
+
extra_rdoc_files: []
|
|
24
|
+
files:
|
|
25
|
+
- Cargo.lock
|
|
26
|
+
- Cargo.toml
|
|
27
|
+
- LICENSE-APACHE
|
|
28
|
+
- LICENSE-MIT
|
|
29
|
+
- README.md
|
|
30
|
+
- bin/rust-annovar
|
|
31
|
+
- ext/rust_annovar/extconf.rb
|
|
32
|
+
- lib/rust_annovar.rb
|
|
33
|
+
- lib/rust_annovar/version.rb
|
|
34
|
+
- src/database.rs
|
|
35
|
+
- src/gene.rs
|
|
36
|
+
- src/io.rs
|
|
37
|
+
- src/lib.rs
|
|
38
|
+
- src/main.rs
|
|
39
|
+
- src/model.rs
|
|
40
|
+
- src/pipeline.rs
|
|
41
|
+
homepage: https://github.com/ydlongtao/RustAnnovar
|
|
42
|
+
licenses:
|
|
43
|
+
- MIT
|
|
44
|
+
- Apache-2.0
|
|
45
|
+
metadata:
|
|
46
|
+
source_code_uri: https://github.com/ydlongtao/RustAnnovar
|
|
47
|
+
documentation_uri: https://github.com/ydlongtao/RustAnnovar#readme
|
|
48
|
+
bug_tracker_uri: https://github.com/ydlongtao/RustAnnovar/issues
|
|
49
|
+
post_install_message:
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '2.6'
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 1.3.1
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 3.0.3.1
|
|
65
|
+
signing_key:
|
|
66
|
+
specification_version: 4
|
|
67
|
+
summary: Rust implementation of core ANNOVAR-compatible workflows
|
|
68
|
+
test_files: []
|