2626
2727from collections import OrderedDict
2828from functools import partial
29- import json
3029import os
3130import sys
3231from types import GeneratorType
3332
3433import click
3534from click .termui import style
35+ import simplejson as json
3636
3737from commoncode import ignore
3838from commoncode import fileutils
4343from scancode import __version__ as version
4444from scancode import utils
4545
46+ from scancode .cache import get_scans_cache
47+
4648from scancode .format import as_template
4749from scancode .format import as_html_app
4850from scancode .format import create_html_app_assets
@@ -260,15 +262,27 @@ def scancode(ctx, input, output_file, copyright, license, package,
260262 license = True
261263 package = True
262264
263- results = scan (input , copyright , license , package , email , url , info , license_score , verbose , quiet )
264- save_results (results , format , input , output_file )
265+ scans_cache = get_scans_cache ()
266+ try :
267+ files_count , results = scan (input , copyright , license , package , email , url , info , license_score , verbose , quiet , scans_cache )
268+ save_results (files_count , results , format , input , output_file )
269+ finally :
270+ # cleanup
271+ scans_cache .clear ()
265272
266273
267274def scan (input_path , copyright = True , license = True , package = True ,
268- email = False , url = False , info = True , license_score = 0 , verbose = False , quiet = False ):
275+ email = False , url = False , info = True , license_score = 0 ,
276+ verbose = False , quiet = False ,
277+ scans_cache = None ):
269278 """
270- Do the scans proper, return a list of file_results.
279+ Return a tuple of (file_count, scan_results) where scan_results is an iterable.
280+ Run each requested scan proper: each individual file scan is cached on disk to
281+ free memory. Then the whole set of scans is loaded from the cache and streamed at
282+ the end.
271283 """
284+ assert scans_cache
285+
272286 # save paths to report paths relative to the original input
273287 original_input = fileutils .as_posixpath (input_path )
274288 abs_input = fileutils .as_posixpath (os .path .abspath (os .path .expanduser (input_path )))
@@ -278,16 +292,14 @@ def scan(input_path, copyright=True, license=True, package=True,
278292 # note: "flag and function" expressions return the function if flag is True
279293 # note: the order of the scans matters to show things in logical order
280294 scanners = OrderedDict ([
281- ('infos' , info and get_file_infos ),
295+ # ('infos' , info and get_file_infos),
282296 ('licenses' , license and get_licenses_with_score ),
283297 ('copyrights' , copyright and get_copyrights ),
284298 ('packages' , package and get_package_infos ),
285299 ('emails' , email and get_emails ),
286300 ('urls' , url and get_urls ),
287301 ])
288302
289- file_results = []
290-
291303 # note: we inline progress display functions to close on some args
292304
293305 def scan_start ():
@@ -323,48 +335,64 @@ def scan_end():
323335 quiet = quiet
324336 ) as progressive_resources :
325337
326- for resource in progressive_resources :
338+ for files_count , resource in enumerate (progressive_resources ):
339+ # actual path of the file being scanned
327340 res = fileutils .as_posixpath (resource )
328-
329341 # fix paths: keep the path as relative to the original input
330342 relative_path = utils .get_relative_path (original_input , abs_input , res )
331- scan_result = OrderedDict (path = relative_path )
332- # Should we yield instead?
333- scan_result .update (scan_one (res , scanners ))
334- file_results .append (scan_result )
335343
336- # TODO: eventually merge scans for the same files path...
337- # TODO: fix absolute paths as relative to original input argument...
344+ # always fetch infos and cache.
345+ infos = scan_infos (res )
346+ is_cached = scans_cache .put_infos (relative_path , infos )
338347
339- return file_results
348+ # Skip other scans if already cached
349+ if is_cached :
350+ continue
351+ scan_result = scan_one (res , scanners )
352+ scans_cache .put_scan (relative_path , infos , scan_result )
353+ files_count += 1
354+ return files_count , scans_cache .iterate (with_infos = info )
355+
356+
357+ def scan_infos (input_file ):
358+ """
359+ Scan one file or directory and return file_infos data.
360+ """
361+ infos = OrderedDict ()
362+ try :
363+ infos = get_file_infos (input_file , as_list = False )
364+ except Exception , e :
365+ raise
366+ # never fail but instead add an error message.
367+ # FIXME: this should not be stored at the individual scan level
368+ return dict (errors = e .message )
369+ return infos
340370
341371
342372def scan_one (input_file , scans ):
343373 """
344374 Scan one file or directory and return a scanned data, calling every scan in
345375 the `scans` mapping of (scan name -> scan function).
346376 """
347- scanned_file = OrderedDict ()
377+ scan_result = OrderedDict ()
348378 for scan_name , scan_func in scans .items ():
349379 if not scan_func :
350380 continue
351381 try :
352382 scan = scan_func (input_file )
383+ # consume generators
353384 if isinstance (scan , GeneratorType ):
354385 scan = list (scan )
355- # this is special and we flatten these as direct attributes of a file object
356- if scan_name == 'infos' :
357- for file_infos in scan :
358- scanned_file .update (file_infos .items ())
359- else :
360- scanned_file [scan_name ] = scan
386+ scan_result [scan_name ] = scan
361387 except Exception , e :
388+ raise
362389 # never fail but instead add an error message.
363- scanned_file [scan_name ] = {'errors' : e .message }
364- return scanned_file
390+ # FIXME: this should not be stored at the individual scan level
391+ scan_result [scan_name ] = {'errors' : e .message }
392+ return scan_result
365393
366394
367- def save_results (scanned_files , format , input , output_file ):
395+ def save_results (files_count , scanned_files , format , input , output_file ):
368396 """
369397 Save results to file or screen.
370398 """
@@ -397,9 +425,10 @@ def save_results(scanned_files, format, input, output_file):
397425 meta = OrderedDict ()
398426 meta ['scancode_notice' ] = acknowledgment_text_json
399427 meta ['scancode_version' ] = version
400- meta ['files_count' ] = len ( scanned_files )
428+ meta ['files_count' ] = files_count
401429 # TODO: add scanning options to meta
402430 meta ['files' ] = scanned_files
403- output_file .write (json .dumps (meta , indent = 2 ))
431+ # json.dump(meta, output_file, indent=2)
432+ json .dump (meta , output_file , indent = 2 * ' ' , iterable_as_array = True )
404433 else :
405434 raise Exception ('unknown format' )
0 commit comments