11//! Tool for searching for files in nixpkgs packages
2+ use std:: borrow:: Cow ;
23use std:: collections:: HashSet ;
34use std:: path:: PathBuf ;
45use std:: process;
56use std:: result;
67use std:: str;
78use std:: str:: FromStr ;
89
9- use clap:: { value_parser, Parser } ;
10+ use clap:: { value_parser, Parser , Args } ;
1011use error_chain:: error_chain;
1112use nix_index:: database;
1213use nix_index:: files:: { self , FileTreeEntry , FileType } ;
1314use owo_colors:: { OwoColorize , Stream } ;
1415use regex:: bytes:: Regex ;
1516use separator:: Separatable ;
17+ use serde:: Serialize ;
1618
1719error_chain ! {
1820 errors {
@@ -29,8 +31,14 @@ error_chain! {
2931 }
3032}
3133
34+ enum OutputFormat {
35+ Full ,
36+ Minimal ,
37+ JsonLines ,
38+ }
39+
3240/// The struct holding the parsed arguments for searching
33- struct Args {
41+ struct LocateArgs {
3442 /// Path of the nix-index database.
3543 database : PathBuf ,
3644 /// The pattern to search for. This is always in regex syntax.
@@ -41,11 +49,11 @@ struct Args {
4149 file_type : Vec < FileType > ,
4250 only_toplevel : bool ,
4351 color : bool ,
44- minimal : bool ,
52+ format : OutputFormat ,
4553}
4654
4755/// The main function of this module: searches with the given options in the database.
48- fn locate ( args : & Args ) -> Result < ( ) > {
56+ fn locate ( args : & LocateArgs ) -> Result < ( ) > {
4957 // Build the regular expression matcher
5058 let pattern = Regex :: new ( & args. pattern ) . chain_err ( || ErrorKind :: Grep ( args. pattern . clone ( ) ) ) ?;
5159 let package_pattern = if let Some ( ref pat) = args. package_pattern {
@@ -105,43 +113,74 @@ fn locate(args: &Args) -> Result<()> {
105113 attr = format ! ( "({})" , attr) ;
106114 }
107115
108- if args. minimal {
109- // only print each package once, even if there are multiple matches
110- if printed_attrs. insert ( attr. clone ( ) ) {
111- println ! ( "{}" , attr) ;
112- }
113- } else {
114- print ! (
115- "{:<40} {:>14} {:>1} {}" ,
116- attr,
117- size. separated_string( ) ,
118- typ,
119- store_path. as_str( )
120- ) ;
121-
122- let path = String :: from_utf8_lossy ( & path) ;
123-
124- if args. color {
125- let mut prev = 0 ;
126- for mat in pattern. find_iter ( path. as_bytes ( ) ) {
127- // if the match is empty, we need to make sure we don't use string
128- // indexing because the match may be "inside" a single multibyte character
129- // in that case (for example, the pattern may match the second byte of a multibyte character)
130- if mat. start ( ) == mat. end ( ) {
131- continue ;
116+ match args. format {
117+ OutputFormat :: JsonLines => {
118+ #[ derive( Debug , Serialize ) ]
119+ struct Info < ' a > {
120+ attr : & ' a str ,
121+ output_name : & ' a str ,
122+ toplevel : bool ,
123+ full_attr : String ,
124+ r#type : & ' static str ,
125+ size : u64 ,
126+ store_path : Cow < ' a , str > ,
127+ path : Cow < ' a , str > ,
128+ }
129+
130+ let o = store_path. origin ( ) ;
131+
132+ let info = Info {
133+ attr : o. attr . as_str ( ) ,
134+ output_name : o. output . as_str ( ) ,
135+ toplevel : o. toplevel ,
136+ full_attr : attr,
137+ r#type : typ,
138+ size,
139+ store_path : store_path. as_str ( ) ,
140+ path : String :: from_utf8_lossy ( & path) ,
141+ } ;
142+
143+ println ! ( "{}" , serde_json:: to_string( & info) . expect( "serialization should never fail, this is a bug" ) ) ;
144+ } ,
145+ OutputFormat :: Minimal => {
146+ // only print each package once, even if there are multiple matches
147+ if printed_attrs. insert ( attr. clone ( ) ) {
148+ println ! ( "{}" , attr) ;
149+ }
150+ } ,
151+ OutputFormat :: Full => {
152+ print ! (
153+ "{:<40} {:>14} {:>1} {}" ,
154+ attr,
155+ size. separated_string( ) ,
156+ typ,
157+ store_path. as_str( )
158+ ) ;
159+
160+ let path = String :: from_utf8_lossy ( & path) ;
161+
162+ if args. color {
163+ let mut prev = 0 ;
164+ for mat in pattern. find_iter ( path. as_bytes ( ) ) {
165+ // if the match is empty, we need to make sure we don't use string
166+ // indexing because the match may be "inside" a single multibyte character
167+ // in that case (for example, the pattern may match the second byte of a multibyte character)
168+ if mat. start ( ) == mat. end ( ) {
169+ continue ;
170+ }
171+ print ! (
172+ "{}{}" ,
173+ & path[ prev..mat. start( ) ] ,
174+ ( & path[ mat. start( ) ..mat. end( ) ] )
175+ . if_supports_color( Stream :: Stdout , |txt| txt. red( ) ) ,
176+ ) ;
177+ prev = mat. end ( ) ;
132178 }
133- print ! (
134- "{}{}" ,
135- & path[ prev..mat. start( ) ] ,
136- ( & path[ mat. start( ) ..mat. end( ) ] )
137- . if_supports_color( Stream :: Stdout , |txt| txt. red( ) ) ,
138- ) ;
139- prev = mat. end ( ) ;
179+ println ! ( "{}" , & path[ prev..] ) ;
180+ } else {
181+ println ! ( "{}" , path) ;
140182 }
141- println ! ( "{}" , & path[ prev..] ) ;
142- } else {
143- println ! ( "{}" , path) ;
144- }
183+ } ,
145184 }
146185 }
147186
@@ -151,7 +190,7 @@ fn locate(args: &Args) -> Result<()> {
151190/// Extract the parsed arguments for clap's arg matches.
152191///
153192/// Handles parsing the values of more complex arguments.
154- fn process_args ( matches : Opts ) -> result:: Result < Args , clap:: Error > {
193+ fn process_args ( matches : Opts ) -> result:: Result < LocateArgs , clap:: Error > {
155194 let pattern_arg = matches. pattern ;
156195 let package_arg = matches. package ;
157196
@@ -177,7 +216,7 @@ fn process_args(matches: Opts) -> result::Result<Args, clap::Error> {
177216 Color :: Never => false ,
178217 } ;
179218
180- let args = Args {
219+ let args = LocateArgs {
181220 database : matches. database ,
182221 group : !matches. no_group ,
183222 pattern : make_pattern ( & pattern_arg, true ) ,
@@ -188,7 +227,7 @@ fn process_args(matches: Opts) -> result::Result<Args, clap::Error> {
188227 . unwrap_or_else ( || files:: ALL_FILE_TYPES . to_vec ( ) ) ,
189228 only_toplevel : matches. top_level ,
190229 color,
191- minimal : matches. minimal ,
230+ format : matches. format . into_enum ( ) ,
192231 } ;
193232 Ok ( args)
194233}
@@ -223,11 +262,29 @@ Limitations
223262 but we know that `xmonad-with-packages.out` requires it.
224263"# ;
225264
226- fn cache_dir ( ) -> & ' static OsStr {
227- let base = xdg:: BaseDirectories :: with_prefix ( "nix-index" ) . unwrap ( ) ;
228- let cache_dir = Box :: new ( base. get_cache_home ( ) ) ;
229- let cache_dir = Box :: leak ( cache_dir) ;
230- cache_dir. as_os_str ( )
265+ #[ derive( Copy , Clone , Debug , Args ) ]
266+ #[ group( multiple = false ) ]
267+ struct FormatArgs {
268+ /// Only print attribute names of found files or directories. Other details such as size or
269+ /// store path are omitted. This is useful for scripts that use the output of nix-locate.
270+ #[ clap( long) ]
271+ minimal : bool ,
272+
273+ /// Print matches as json objects separated by newlines
274+ #[ clap( long) ]
275+ json_lines : bool ,
276+ }
277+
278+ impl FormatArgs {
279+ fn into_enum ( self ) -> OutputFormat {
280+ if self . json_lines {
281+ OutputFormat :: JsonLines
282+ } else if self . minimal {
283+ OutputFormat :: Minimal
284+ } else {
285+ OutputFormat :: Full
286+ }
287+ }
231288}
232289
233290/// Quickly finds the derivation providing a certain file
@@ -288,10 +345,8 @@ struct Opts {
288345 #[ clap( long) ]
289346 at_root : bool ,
290347
291- /// Only print attribute names of found files or directories. Other details such as size or
292- /// store path are omitted. This is useful for scripts that use the output of nix-locate.
293- #[ clap( long) ]
294- minimal : bool ,
348+ #[ clap( flatten) ]
349+ format : FormatArgs ,
295350}
296351
297352#[ derive( clap:: ValueEnum , Clone , Copy , Debug ) ]
0 commit comments