@@ -1100,7 +1100,7 @@ impl Interpreter {
11001100 /// pair.1/pair.2 by their flag bits and lone reads become singletons.
11011101 fn execute_as_reads ( & self , expr : & NGLessObject ) -> NgResult < NGLessObject > {
11021102 let ( name, path) = match expr {
1103- NGLessObject :: MappedReadSet { name, path } => ( name. clone ( ) , path. clone ( ) ) ,
1103+ NGLessObject :: MappedReadSet { name, path, .. } => ( name. clone ( ) , path. clone ( ) ) ,
11041104 other => {
11051105 return Err ( NgError :: should_not_occur ( format ! (
11061106 "as_reads expected a mapped read set, got {other:?}"
@@ -1194,8 +1194,8 @@ impl Interpreter {
11941194 . collect :: < NgResult < Vec < _ > > > ( ) ?;
11951195 return Ok ( NGLessObject :: List ( counted) ) ;
11961196 }
1197- let ( name, path) = mapped_read_set ( expr, "count" ) ?;
1198- let opts = parse_count_opts ( args) ?;
1197+ let ( name, path, mapped_ref ) = mapped_read_set ( expr, "count" ) ?;
1198+ let opts = parse_count_opts ( args, mapped_ref . as_deref ( ) , self . ngl_version ) ?;
11991199
12001200 // Stream the SAM in read-name groups (mirroring the conduit pipeline) to keep memory
12011201 // bounded — no more than one read group is held at a time. All `@SQ` headers precede the
@@ -1243,7 +1243,7 @@ impl Interpreter {
12431243 /// a group is *aligned* if any read is aligned, and *unique* if it is aligned and all its
12441244 /// records share one reference name.
12451245 fn execute_mapstats ( & self , expr : & NGLessObject ) -> NgResult < NGLessObject > {
1246- let ( name, path) = mapped_read_set ( expr, "mapstats" ) ?;
1246+ let ( name, path, _ ) = mapped_read_set ( expr, "mapstats" ) ?;
12471247 let ( total, aligned, unique) = sam_group_stats_stream ( & path. to_string_lossy ( ) ) ?;
12481248
12491249 let tsv = format ! ( "\t {name}\n total\t {total}\n aligned\t {aligned}\n unique\t {unique}\n " ) ;
@@ -1314,20 +1314,25 @@ impl Interpreter {
13141314 return Ok ( NGLessObject :: MappedReadSet {
13151315 name,
13161316 path : PathBuf :: from ( fname) ,
1317+ reference : None ,
13171318 } ) ;
13181319 }
13191320 // Sourcing `headers` then `fname` and re-emitting line-by-line is, for newline-terminated
13201321 // files, the same as concatenating their bytes.
13211322 let mut data = read_sam_text ( & headers) ?;
13221323 data. push_str ( & read_sam_text ( & fname) ?) ;
13231324 let path = self . write_sam_temp ( & data) ?;
1324- Ok ( NGLessObject :: MappedReadSet { name, path } )
1325+ Ok ( NGLessObject :: MappedReadSet {
1326+ name,
1327+ path,
1328+ reference : None ,
1329+ } )
13251330 }
13261331
13271332 /// `map(reads, fafile=/reference=, mode_all=, mapper=, block_size_megabases=, __extra_args=)`:
13281333 /// map a read set against a reference (mirrors `executeMap`). This build supports the `bwa`
1329- /// and `minimap2` mappers; the `soap` mapper is not implemented. Packaged `reference=`
1330- /// databases must already be installed locally (no download ).
1334+ /// and `minimap2` mappers; the `soap` mapper is not implemented. A packaged `reference=`
1335+ /// database is resolved to its FASTA (downloading the pack if it is not already installed ).
13311336 fn execute_map (
13321337 & self ,
13331338 expr : & NGLessObject ,
@@ -1371,9 +1376,11 @@ impl Interpreter {
13711376 } ;
13721377
13731378 // lookupReference: exactly one of `reference`/`fafile` (mirrors `lookupReference`).
1379+ // A packaged `reference=` also becomes the mapped read set's `mappedRef` (used later as
1380+ // `count`'s default annotation source), mirroring `indexReference`'s `Just r`.
13741381 let reference = lookup_opt_string ( args, "reference" ) ;
13751382 let fafile = lookup_opt_string ( args, "fafile" ) ;
1376- let fafile = match ( reference, fafile) {
1383+ let ( fafile, mapped_ref ) = match ( reference, fafile) {
13771384 ( None , None ) => {
13781385 return Err ( NgError :: script ( "Either reference or fafile must be passed" ) )
13791386 }
@@ -1384,8 +1391,8 @@ impl Interpreter {
13841391 }
13851392 // A packaged `reference=` is resolved to its FASTA in the data directories; the FASTA
13861393 // then flows through the same indexing/mapping path as `fafile=`.
1387- ( Some ( refname) , None ) => self . resolve_reference ( & refname) ?,
1388- ( None , Some ( fa) ) => self . expand_path ( & fa) ?,
1394+ ( Some ( refname) , None ) => ( self . resolve_reference ( & refname) ?, Some ( refname ) ) ,
1395+ ( None , Some ( fa) ) => ( self . expand_path ( & fa) ?, None ) ,
13891396 } ;
13901397
13911398 let mode_all = lookup_bool ( args, "mode_all" , false ) ?;
@@ -1395,7 +1402,9 @@ impl Interpreter {
13951402 }
13961403 let block_size = lookup_int ( args, "block_size_megabases" , 0 ) ?;
13971404
1398- self . perform_map ( mapper, & fafile, block_size, & name, & rs, & bwa_args)
1405+ self . perform_map (
1406+ mapper, & fafile, block_size, & name, & rs, & bwa_args, mapped_ref,
1407+ )
13991408 }
14001409
14011410 /// Resolve a packaged `reference=` database name to its FASTA file (mirrors
@@ -1405,7 +1414,10 @@ impl Interpreter {
14051414 /// then the global one. A missing builtin reference is downloaded from the configured base URL
14061415 /// (mirrors `installData`), using the script's language version for the URL's version directory.
14071416 fn resolve_reference ( & self , refname : & str ) -> NgResult < String > {
1408- crate :: reference:: ensure_data_present ( refname, self . ngl_version )
1417+ let paths = crate :: reference:: ensure_data_present ( refname, self . ngl_version ) ?;
1418+ paths
1419+ . fa_file
1420+ . ok_or_else ( || NgError :: script ( format ! ( "Could not find reference '{refname}'." ) ) )
14091421 }
14101422
14111423 /// Run an external-module command function (mirrors `executeCommand`): encode the unnamed input
@@ -1486,6 +1498,7 @@ impl Interpreter {
14861498 ( Some ( p) , NGLType :: MappedReadSet ) => Ok ( NGLessObject :: MappedReadSet {
14871499 name : group_name,
14881500 path : PathBuf :: from ( p) ,
1501+ reference : None ,
14891502 } ) ,
14901503 ( Some ( _) , other) => Err ( NgError :: should_not_occur ( format ! (
14911504 "External module return type {other:?} is not implemented in this build yet."
@@ -1645,6 +1658,7 @@ impl Interpreter {
16451658 name : & str ,
16461659 rs : & ReadSet ,
16471660 extra_args : & [ String ] ,
1661+ mapped_ref : Option < String > ,
16481662 ) -> NgResult < NGLessObject > {
16491663 let refs = self . ensure_index_exists ( mapper, block_size, fafile) ?;
16501664 let ( path, reference) = match refs. as_slice ( ) {
@@ -1665,6 +1679,7 @@ impl Interpreter {
16651679 Ok ( NGLessObject :: MappedReadSet {
16661680 name : name. to_string ( ) ,
16671681 path,
1682+ reference : mapped_ref,
16681683 } )
16691684 }
16701685
@@ -1939,6 +1954,7 @@ impl Interpreter {
19391954 Ok ( NGLessObject :: MappedReadSet {
19401955 name : "test" . to_string ( ) ,
19411956 path,
1957+ reference : None ,
19421958 } )
19431959 }
19441960
@@ -1964,7 +1980,7 @@ impl Interpreter {
19641980 expr : & NGLessObject ,
19651981 args : & [ ( String , NGLessObject ) ] ,
19661982 ) -> NgResult < NGLessObject > {
1967- let ( name, path) = mapped_read_set ( expr, "select" ) ?;
1983+ let ( name, path, reference ) = mapped_read_set ( expr, "select" ) ?;
19681984 let paired = lookup_bool ( args, "paired" , true ) ?;
19691985 let keep_if = lookup_symbol_list ( args, "keep_if" ) ;
19701986 let drop_if = lookup_symbol_list ( args, "drop_if" ) ;
@@ -2009,6 +2025,7 @@ impl Interpreter {
20092025 Ok ( NGLessObject :: MappedReadSet {
20102026 name,
20112027 path : outpath,
2028+ reference,
20122029 } )
20132030 }
20142031
@@ -2021,7 +2038,7 @@ impl Interpreter {
20212038 args : & [ ( String , NGLessObject ) ] ,
20222039 block : & Block ,
20232040 ) -> NgResult < NGLessObject > {
2024- let ( name, path) = mapped_read_set ( expr, "select" ) ?;
2041+ let ( name, path, reference ) = mapped_read_set ( expr, "select" ) ?;
20252042 let paired = lookup_bool ( args, "paired" , true ) ?;
20262043 let var = & block. variable . 0 ;
20272044 // `.sam.zst` => `StreamWriter` compresses with zstd on a background thread (mirroring
@@ -2069,6 +2086,7 @@ impl Interpreter {
20692086 Ok ( NGLessObject :: MappedReadSet {
20702087 name,
20712088 path : outpath,
2089+ reference,
20722090 } )
20732091 }
20742092
@@ -2105,7 +2123,7 @@ impl Interpreter {
21052123 expr : & NGLessObject ,
21062124 args : & [ ( String , NGLessObject ) ] ,
21072125 ) -> NgResult < NGLessObject > {
2108- let ( name, path) = mapped_read_set ( expr, "samtools_sort" ) ?;
2126+ let ( name, path, reference ) = mapped_read_set ( expr, "samtools_sort" ) ?;
21092127 let by_name = match lookup_symbol ( args, "by" , "coordinate" ) ?. as_str ( ) {
21102128 "coordinate" => false ,
21112129 "name" => true ,
@@ -2126,7 +2144,11 @@ impl Interpreter {
21262144 let out = self . new_temp_path ( "sorted_" , oformat) ?;
21272145 let temp_prefix = self . new_temp_path ( "samtools_sort_temp" , "tmp" ) ?;
21282146 crate :: samtools:: sort ( & input, & out, oformat, by_name, & temp_prefix) ?;
2129- Ok ( NGLessObject :: MappedReadSet { name, path : out } )
2147+ Ok ( NGLessObject :: MappedReadSet {
2148+ name,
2149+ path : out,
2150+ reference,
2151+ } )
21302152 }
21312153
21322154 /// `samtools_view(mapped, bed_file=...)` (from the `samtools` module, mirrors `executeView`):
@@ -2136,7 +2158,7 @@ impl Interpreter {
21362158 expr : & NGLessObject ,
21372159 args : & [ ( String , NGLessObject ) ] ,
21382160 ) -> NgResult < NGLessObject > {
2139- let ( name, path) = mapped_read_set ( expr, "samtools_view" ) ?;
2161+ let ( name, path, reference ) = mapped_read_set ( expr, "samtools_view" ) ?;
21402162 let bed = match lookup_arg ( args, "bed_file" ) {
21412163 Some ( NGLessObject :: String ( s) ) => s. clone ( ) ,
21422164 _ => {
@@ -2148,7 +2170,11 @@ impl Interpreter {
21482170 let input = self . samtools_input ( & path) ?;
21492171 let out = self . new_temp_path ( "subset_" , "sam" ) ?;
21502172 crate :: samtools:: view_bed ( & input, & bed, & out, "sam" ) ?;
2151- Ok ( NGLessObject :: MappedReadSet { name, path : out } )
2173+ Ok ( NGLessObject :: MappedReadSet {
2174+ name,
2175+ path : out,
2176+ reference,
2177+ } )
21522178 }
21532179
21542180 /// `write(...)`: dispatch on the value type (mirrors `executeWrite`). Read sets and counts are
@@ -4952,7 +4978,11 @@ fn normalize_count_file(text: &str) -> String {
49524978}
49534979
49544980/// Build [`count::CountOpts`] from the keyword arguments (mirrors `parseOptions`).
4955- fn parse_count_opts ( args : & [ ( String , NGLessObject ) ] ) -> NgResult < crate :: count:: CountOpts > {
4981+ fn parse_count_opts (
4982+ args : & [ ( String , NGLessObject ) ] ,
4983+ mapped_ref : Option < & str > ,
4984+ ngl_version : ( i64 , i64 ) ,
4985+ ) -> NgResult < crate :: count:: CountOpts > {
49564986 use crate :: count:: { AnnotationMode , CountOpts , IntersectMode , MMMethod , NMode , StrandMode } ;
49574987
49584988 let features =
@@ -5016,16 +5046,38 @@ fn parse_count_opts(args: &[(String, NGLessObject)]) -> NgResult<crate::count::C
50165046 } ;
50175047 let include_minus1 = lookup_bool ( args, "include_minus1" , true ) ?;
50185048
5049+ // Mirrors `parseAnnotationMode`: seqname wins, then an explicit `functional_map`, then an
5050+ // explicit `gff_file`, then a `reference` — which defaults to the mapped read set's own reference
5051+ // (`mappedref`) when not given. A packaged reference is resolved (downloading it if necessary) to
5052+ // its bundled annotation file, choosing GFF or functional-map by which one the pack ships.
50195053 let annotation_mode = if features == [ "seqname" ] {
50205054 AnnotationMode :: SeqName
50215055 } else if let Some ( m) = lookup_opt_string ( args, "functional_map" ) {
50225056 AnnotationMode :: FunctionalMap ( m)
50235057 } else if let Some ( g) = lookup_opt_string ( args, "gff_file" ) {
50245058 AnnotationMode :: Gff ( g)
5025- } else if lookup_opt_string ( args, "reference" ) . is_some ( ) {
5026- return Err ( NgError :: script (
5027- "count(): the `reference` argument (automatic annotation download) is not supported in this build yet." . to_string ( ) ,
5028- ) ) ;
5059+ } else if let Some ( refname) =
5060+ lookup_opt_string ( args, "reference" ) . or_else ( || mapped_ref. map ( str:: to_string) )
5061+ {
5062+ crate :: output:: info ( 0 , & format ! ( "Annotate with reference: {refname:?}" ) ) ;
5063+ let paths = crate :: reference:: ensure_data_present ( & refname, ngl_version) ?;
5064+ match ( paths. gff_file , paths. functional_map ) {
5065+ ( Some ( g) , None ) => AnnotationMode :: Gff ( g) ,
5066+ ( None , Some ( f) ) => AnnotationMode :: FunctionalMap ( f) ,
5067+ ( None , None ) => {
5068+ return Err ( NgError :: script ( format ! (
5069+ "Could not find annotation file for '{refname}'"
5070+ ) ) )
5071+ }
5072+ ( Some ( _) , Some ( _) ) => {
5073+ return Err ( NgError :: new (
5074+ NgErrorType :: DataError ,
5075+ format ! (
5076+ "Reference {refname} has both a GFF and a functional map file. Cannot figure out what to do."
5077+ ) ,
5078+ ) )
5079+ }
5080+ }
50295081 } else {
50305082 return Err ( NgError :: script (
50315083 "For counting, you must use seqname mode, pass a `gff_file`, or pass a `functional_map`." . to_string ( ) ,
@@ -5046,9 +5098,13 @@ fn parse_count_opts(args: &[(String, NGLessObject)]) -> NgResult<crate::count::C
50465098}
50475099
50485100/// Destructure a mapped read set value into its name and backing file path.
5049- fn mapped_read_set ( v : & NGLessObject , who : & str ) -> NgResult < ( String , PathBuf ) > {
5101+ fn mapped_read_set ( v : & NGLessObject , who : & str ) -> NgResult < ( String , PathBuf , Option < String > ) > {
50505102 match v {
5051- NGLessObject :: MappedReadSet { name, path } => Ok ( ( name. clone ( ) , path. clone ( ) ) ) ,
5103+ NGLessObject :: MappedReadSet {
5104+ name,
5105+ path,
5106+ reference,
5107+ } => Ok ( ( name. clone ( ) , path. clone ( ) , reference. clone ( ) ) ) ,
50525108 other => Err ( NgError :: should_not_occur ( format ! (
50535109 "{who} expected a mapped read set, got {other:?}"
50545110 ) ) ) ,
0 commit comments