1+ var os = require ( "os" ) ;
2+ var path = require ( "path" ) ;
3+ var _ = require ( "lodash" ) ;
4+ var Q = require ( "q" ) ;
5+ var spawn = require ( 'child_process' ) . spawn ;
6+
7+ var types = require ( "./types" ) ;
8+
9+ var config = {
10+ grepCmd : "grep" ,
11+ perlCmd : "perl" ,
12+ platform : os . platform ( )
13+ } ;
14+
15+ var assembleCommand = function ( options ) {
16+ var include = "" ;
17+ var cmd = config . grepCmd + " -s -r --color=never --binary-files=without-match -n " +
18+ ( ! options . casesensitive ? "-i " : "" ) +
19+ ( process . platform != "darwin" ? "-P " : "" ) ;
20+
21+ if ( options . pattern ) { // handles grep peculiarities with --include
22+ if ( options . pattern . split ( "," ) . length > 1 )
23+ include = "{" + options . pattern + "}" ;
24+ else
25+ include = options . pattern ;
26+ }
27+ else {
28+ include = ( process . platform != "darwin" ? "\\" : "" ) + "*{" + types . PATTERN_EXT + "}" ;
29+ }
30+
31+ if ( options . maxresults )
32+ cmd += "-m " + parseInt ( options . maxresults , 10 ) ;
33+ if ( options . wholeword )
34+ cmd += " -w" ;
35+
36+ var query = options . query ;
37+ if ( ! query )
38+ return ;
39+
40+ // grep has a funny way of handling new lines (that is to say, it's non-existent)
41+ // if we're not doing a regex search, then we must split everything between the
42+ // new lines, escape the content, and then smush it back together; due to
43+ // new lines, this is also why we're now passing -P as default to grep
44+ if ( ! options . replaceAll && ! options . regexp ) {
45+ var splitQuery = query . split ( "\\n" ) ;
46+
47+ for ( var q in splitQuery ) {
48+ splitQuery [ q ] = types . grepEscapeRegExp ( splitQuery [ q ] ) ;
49+ }
50+ query = splitQuery . join ( "\\n" ) ;
51+ }
52+
53+ query = query . replace ( new RegExp ( "\\\'" , "g" ) , "'\\''" ) ; // ticks must be double escaped for BSD grep
54+
55+ cmd += " " + types . PATTERN_EDIR + " " +
56+ " --include=" + include +
57+ " '" + query . replace ( / - / g, "\\-" ) + "'" +
58+ " \"" + types . escapeShell ( options . path ) + "\"" ;
59+
60+ if ( options . replaceAll ) {
61+ if ( ! options . replacement )
62+ options . replacement = "" ;
63+
64+ if ( options . regexp )
65+ query = types . escapeRegExp ( query ) ;
66+
67+ // pipe the grep results into perl
68+ cmd += " -l | xargs " + config . perlCmd +
69+ // print the grep result to STDOUT (to arrange in parseSearchResult())
70+ " -pi -e 'print STDOUT \"$ARGV:$.:$_\"" +
71+ // do the actual replace
72+ " if s/" + query + "/" + options . replacement + "/mg" + ( options . casesensitive ? "" : "i" ) + ";'" ;
73+ }
74+
75+ var args = [ "-c" , cmd ] ;
76+ args . command = "bash" ;
77+ return args ;
78+ } ;
79+
80+
81+ var search = function ( root , args ) {
82+ var d = Q . defer ( ) , prevfile = null ;
83+ var results = { } ;
84+ var nMatches = 0 ;
85+
86+ args = _ . defaults ( args || { } , {
87+ pattern : null ,
88+ casesensitive : false ,
89+ maxresults : null ,
90+ wholeword : false ,
91+ regexp : null ,
92+
93+ // replace
94+ replaceAll : false ,
95+ replacement : null
96+ } ) ;
97+
98+ if ( ! args . query ) return Q . reject ( new Error ( "Need a query to search for code" ) ) ;
99+
100+ var command = assembleCommand ( _ . extend ( { } , args , {
101+ path : root
102+ } ) ) ;
103+
104+ var proc = spawn ( command . command , command ) ;
105+ proc . stdout . on ( 'data' , function ( data ) {
106+ data = data . toString ( ) ;
107+ var lines = data . toString ( ) . split ( / ( [ \n \r ] + ) / g) ;
108+
109+ for ( var i = 0 , l = lines . length ; i < l ; ++ i ) {
110+ var parts = lines [ i ] . split ( ":" ) ;
111+ if ( parts . length < 3 ) continue ;
112+
113+ var _path = path . normalize ( parts . shift ( ) . replace ( root , "" ) . trimRight ( ) ) ;
114+ var _line = parseInt ( parts . shift ( ) ) ;
115+ if ( ! _line ) {
116+ if ( prevfile ) {
117+ results [ prevfile ] [ results [ prevfile ] . length - 1 ] . content += "\n\r" + lines [ i ] ;
118+ }
119+ continue ;
120+ }
121+
122+ prevfile = _path ;
123+ results [ _path ] = results [ _path ] || [ ] ;
124+ results [ _path ] . push ( {
125+ 'line' : _line ,
126+ 'content' : parts . join ( ":" )
127+ } ) ;
128+ nMatches = nMatches + 1 ;
129+ }
130+ } ) ;
131+
132+ proc . on ( 'error' , function ( err ) {
133+ d . reject ( err )
134+ } ) ;
135+ proc . on ( 'exit' , function ( code ) {
136+ if ( code !== 0 ) {
137+ d . reject ( new Error ( "ack exited with code " + code ) ) ;
138+ } else {
139+ d . resolve ( {
140+ 'options' : args ,
141+ 'files' : results ,
142+ 'matches' : nMatches
143+ } ) ;
144+ }
145+ } ) ;
146+
147+ return d . promise ;
148+ } ;
149+
150+ module . exports = search ;
0 commit comments