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, Args , Parser } ;
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,42 +113,77 @@ 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) ;
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 ! (
144+ "{}" ,
145+ serde_json:: to_string( & info)
146+ . expect( "serialization should never fail, this is a bug" )
147+ ) ;
112148 }
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 ;
149+ OutputFormat :: Minimal => {
150+ // only print each package once, even if there are multiple matches
151+ if printed_attrs. insert ( attr. clone ( ) ) {
152+ println ! ( "{}" , attr) ;
153+ }
154+ }
155+ OutputFormat :: Full => {
156+ print ! (
157+ "{:<40} {:>14} {:>1} {}" ,
158+ attr,
159+ size. separated_string( ) ,
160+ typ,
161+ store_path. as_str( )
162+ ) ;
163+
164+ let path = String :: from_utf8_lossy ( & path) ;
165+
166+ if args. color {
167+ let mut prev = 0 ;
168+ for mat in pattern. find_iter ( path. as_bytes ( ) ) {
169+ // if the match is empty, we need to make sure we don't use string
170+ // indexing because the match may be "inside" a single multibyte character
171+ // in that case (for example, the pattern may match the second byte of a multibyte character)
172+ if mat. start ( ) == mat. end ( ) {
173+ continue ;
174+ }
175+ print ! (
176+ "{}{}" ,
177+ & path[ prev..mat. start( ) ] ,
178+ ( & path[ mat. start( ) ..mat. end( ) ] )
179+ . if_supports_color( Stream :: Stdout , |txt| txt. red( ) ) ,
180+ ) ;
181+ prev = mat. end ( ) ;
132182 }
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 ( ) ;
183+ println ! ( "{}" , & path[ prev..] ) ;
184+ } else {
185+ println ! ( "{}" , path) ;
140186 }
141- println ! ( "{}" , & path[ prev..] ) ;
142- } else {
143- println ! ( "{}" , path) ;
144187 }
145188 }
146189 }
@@ -151,7 +194,7 @@ fn locate(args: &Args) -> Result<()> {
151194/// Extract the parsed arguments for clap's arg matches.
152195///
153196/// Handles parsing the values of more complex arguments.
154- fn process_args ( matches : Opts ) -> result:: Result < Args , clap:: Error > {
197+ fn process_args ( matches : Opts ) -> result:: Result < LocateArgs , clap:: Error > {
155198 let pattern_arg = matches. pattern ;
156199 let package_arg = matches. package ;
157200
@@ -177,7 +220,7 @@ fn process_args(matches: Opts) -> result::Result<Args, clap::Error> {
177220 Color :: Never => false ,
178221 } ;
179222
180- let args = Args {
223+ let args = LocateArgs {
181224 database : matches. database ,
182225 group : !matches. no_group ,
183226 pattern : make_pattern ( & pattern_arg, true ) ,
@@ -188,7 +231,7 @@ fn process_args(matches: Opts) -> result::Result<Args, clap::Error> {
188231 . unwrap_or_else ( || files:: ALL_FILE_TYPES . to_vec ( ) ) ,
189232 only_toplevel : matches. top_level ,
190233 color,
191- minimal : matches. minimal ,
234+ format : matches. format . into_enum ( ) ,
192235 } ;
193236 Ok ( args)
194237}
@@ -223,11 +266,29 @@ Limitations
223266 but we know that `xmonad-with-packages.out` requires it.
224267"# ;
225268
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 ( )
269+ #[ derive( Copy , Clone , Debug , Args ) ]
270+ #[ group( multiple = false ) ]
271+ struct FormatArgs {
272+ /// Only print attribute names of found files or directories. Other details such as size or
273+ /// store path are omitted. This is useful for scripts that use the output of nix-locate.
274+ #[ clap( long) ]
275+ minimal : bool ,
276+
277+ /// Print matches as json objects separated by newlines
278+ #[ clap( long) ]
279+ json_lines : bool ,
280+ }
281+
282+ impl FormatArgs {
283+ fn into_enum ( self ) -> OutputFormat {
284+ if self . json_lines {
285+ OutputFormat :: JsonLines
286+ } else if self . minimal {
287+ OutputFormat :: Minimal
288+ } else {
289+ OutputFormat :: Full
290+ }
291+ }
231292}
232293
233294/// Quickly finds the derivation providing a certain file
@@ -288,10 +349,8 @@ struct Opts {
288349 #[ clap( long) ]
289350 at_root : bool ,
290351
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 ,
352+ #[ clap( flatten) ]
353+ format : FormatArgs ,
295354}
296355
297356#[ derive( clap:: ValueEnum , Clone , Copy , Debug ) ]
0 commit comments