Skip to content

Commit a2b82a3

Browse files
committed
Move extractcode CLI to extractcode module #2033
Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
1 parent 8819c0b commit a2b82a3

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed

src/extractcode/cli.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#
2+
# Copyright (c) 2018 nexB Inc. and others. All rights reserved.
3+
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
4+
# The ScanCode software is licensed under the Apache License version 2.0.
5+
# Data generated with ScanCode require an acknowledgment.
6+
# ScanCode is a trademark of nexB Inc.
7+
#
8+
# You may not use this software except in compliance with the License.
9+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
10+
# Unless required by applicable law or agreed to in writing, software distributed
11+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations under the License.
14+
#
15+
# When you publish or redistribute any data created with ScanCode or any ScanCode
16+
# derivative work, you must accompany this data with the following acknowledgment:
17+
#
18+
# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
19+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
20+
# ScanCode should be considered or used as legal advice. Consult an Attorney
21+
# for any legal advice.
22+
# ScanCode is a free software code scanning tool from nexB Inc. and others.
23+
# Visit https://github.com/nexB/scancode-toolkit/ for support and download.
24+
25+
from __future__ import print_function
26+
from __future__ import absolute_import
27+
from __future__ import unicode_literals
28+
29+
from functools import partial
30+
import os
31+
32+
import click
33+
click.disable_unicode_literals_warning = True
34+
35+
from commoncode import compat
36+
from commoncode import fileutils
37+
from commoncode import filetype
38+
from commoncode.text import toascii
39+
40+
from scancode_config import __version__
41+
from scancode.api import extract_archives
42+
from scancode import print_about
43+
from scancode import utils
44+
45+
46+
echo_stderr = partial(click.secho, err=True)
47+
48+
49+
def print_version(ctx, param, value):
50+
if not value or ctx.resilient_parsing:
51+
return
52+
echo_stderr('ScanCode extractcode version ' + __version__)
53+
ctx.exit()
54+
55+
56+
epilog_text = '''\b\bExamples:
57+
58+
(Note for Windows: use '\\' backslash instead of '/' slash for paths.)
59+
60+
\b
61+
Extract all archives found in the 'samples' directory tree:
62+
63+
extractcode samples
64+
65+
Note: If an archive contains other archives, all contained archives will be
66+
extracted recursively. Extraction is done directly in the 'samples' directory,
67+
side-by-side with each archive. Files are extracted in a directory named after
68+
the archive with an '-extract' suffix added to its name, created side-by-side
69+
with the corresponding archive file.
70+
71+
\b
72+
Extract a single archive. Files are extracted in the directory
73+
'samples/arch/zlib.tar.gz-extract/':
74+
75+
extractcode samples/arch/zlib.tar.gz
76+
'''
77+
78+
79+
class ExtractCommand(utils.BaseCommand):
80+
short_usage_help = '''
81+
Try 'extractcode --help' for help on options and arguments.'''
82+
83+
84+
@click.command(name='extractcode', epilog=epilog_text, cls=ExtractCommand)
85+
@click.pass_context
86+
87+
@click.argument('input', metavar='<input>', type=click.Path(exists=True, readable=True, path_type=fileutils.PATH_TYPE))
88+
89+
@click.option('--verbose', is_flag=True, default=False, help='Print verbose file-by-file progress messages.')
90+
@click.option('--quiet', is_flag=True, default=False, help='Do not print any summary or progress message.')
91+
@click.option('--shallow', is_flag=True, default=False, help='Do not extract recursively nested archives (e.g. not archives in archives).')
92+
@click.option('--replace-originals', is_flag=True, default=False, help='Replace extracted archives by the extracted content.')
93+
@click.option('--ignore', default=[], multiple=True, help='Ignore files/directories following a glob-pattern.')
94+
95+
@click.help_option('-h', '--help')
96+
@click.option('--about', is_flag=True, is_eager=True, callback=print_about, help='Show information about ScanCode and licensing and exit.')
97+
@click.option('--version', is_flag=True, is_eager=True, callback=print_version, help='Show the version and exit.')
98+
def extractcode(ctx, input, verbose, quiet, shallow, replace_originals, ignore, *args, **kwargs): # NOQA
99+
"""extract archives and compressed files found in the <input> file or directory tree.
100+
101+
Use this command before scanning proper as an <input> preparation step.
102+
Archives found inside an extracted archive are extracted recursively.
103+
Extraction is done in-place in a directory named '-extract' side-by-side with an archive.
104+
"""
105+
106+
abs_location = fileutils.as_posixpath(os.path.abspath(os.path.expanduser(input)))
107+
108+
def extract_event(item):
109+
"""
110+
Display an extract event.
111+
"""
112+
if quiet:
113+
return ''
114+
if not item:
115+
return ''
116+
source = item.source
117+
if not isinstance(source, compat.unicode):
118+
source = toascii(source, translit=True).decode('utf-8', 'replace')
119+
if verbose:
120+
if item.done:
121+
return ''
122+
line = source and get_relative_path(path=source, len_base_path=len_base_path, base_is_dir=base_is_dir) or ''
123+
else:
124+
line = source and fileutils.file_name(source) or ''
125+
if not isinstance(line, compat.unicode):
126+
line = toascii(line, translit=True).decode('utf-8', 'replace')
127+
return 'Extracting: %(line)s' % locals()
128+
129+
def display_extract_summary():
130+
"""
131+
Display a summary of warnings and errors if any.
132+
"""
133+
has_warnings = False
134+
has_errors = False
135+
summary = []
136+
for xev in extract_result_with_errors:
137+
has_errors = has_errors or bool(xev.errors)
138+
has_warnings = has_warnings or bool(xev.warnings)
139+
source = fileutils.as_posixpath(xev.source)
140+
if not isinstance(source, compat.unicode):
141+
source = toascii(source, translit=True).decode('utf-8', 'replace')
142+
source = get_relative_path(path=source, len_base_path=len_base_path, base_is_dir=base_is_dir)
143+
for e in xev.errors:
144+
echo_stderr('ERROR extracting: %(source)s: %(e)s' % locals(), fg='red')
145+
for warn in xev.warnings:
146+
echo_stderr('WARNING extracting: %(source)s: %(warn)s' % locals(), fg='yellow')
147+
148+
summary_color = 'green'
149+
if has_warnings:
150+
summary_color = 'yellow'
151+
if has_errors:
152+
summary_color = 'red'
153+
154+
echo_stderr('Extracting done.', fg=summary_color, reset=True)
155+
156+
# use for relative paths computation
157+
len_base_path = len(abs_location)
158+
base_is_dir = filetype.is_dir(abs_location)
159+
160+
extract_result_with_errors = []
161+
unique_extract_events_with_errors = set()
162+
has_extract_errors = False
163+
164+
extractibles = extract_archives(
165+
abs_location, recurse=not shallow, replace_originals=replace_originals, ignore_pattern=ignore)
166+
167+
if not quiet:
168+
echo_stderr('Extracting archives...', fg='green')
169+
with utils.progressmanager(extractibles,
170+
item_show_func=extract_event, verbose=verbose) as extraction_events:
171+
172+
for xev in extraction_events:
173+
if xev.done and (xev.warnings or xev.errors):
174+
has_extract_errors = has_extract_errors or xev.errors
175+
if repr(xev) not in unique_extract_events_with_errors:
176+
extract_result_with_errors.append(xev)
177+
unique_extract_events_with_errors.add(repr(xev))
178+
display_extract_summary()
179+
else:
180+
for xev in extractibles:
181+
if xev.done and (xev.warnings or xev.errors):
182+
has_extract_errors = has_extract_errors or xev.errors
183+
184+
rc = 1 if has_extract_errors else 0
185+
ctx.exit(rc)
186+
187+
188+
def get_relative_path(path, len_base_path, base_is_dir):
189+
"""
190+
Return a posix relative path from the posix 'path' relative to a
191+
base path of `len_base_path` length where the base is a directory if
192+
`base_is_dir` True or a file otherwise.
193+
"""
194+
path = fileutils.fsdecode(path)
195+
if base_is_dir:
196+
rel_path = path[len_base_path:]
197+
else:
198+
rel_path = fileutils.file_name(path)
199+
200+
return rel_path.lstrip('/')

0 commit comments

Comments
 (0)