Convert to f-strings where it makes sense

This commit is contained in:
James R. Barlow
2018-12-31 15:01:19 -08:00
parent c2a947acf4
commit c771938907
17 changed files with 93 additions and 120 deletions
+3 -5
View File
@@ -135,8 +135,7 @@ def check_external_program(
version_scrape_regex=re.compile(r'(\d+\.\d+(?:\.\d+)?)'),
optional=False):
print('Checking for {program} >= {need_version}...'.format(
program=program, need_version=need_version))
print(f'Checking for {program} >= {need_version}...')
try:
result = check_output(
[program] + version_check_args,
@@ -145,7 +144,7 @@ def check_external_program(
error_missing_program(program, package, optional)
if not optional:
sys.exit(1)
print('Continuing install without {program}'.format(program=program))
print(f'Continuing install without {program}')
return
try:
@@ -158,8 +157,7 @@ def check_external_program(
error_old_version(program, package, optional, need_version,
found_version)
print('Found {program} {found_version}'.format(
program=program, found_version=found_version))
print(f'Found {program} {found_version}')
command = next((arg for arg in sys.argv[1:] if not arg.startswith('-')), '')
+26 -32
View File
@@ -69,8 +69,8 @@ verify_python3_env()
if not tesseract.v4:
complain(
"Please install tesseract 4.0.0 or newer "
"(currently installed version is {1})".format(tesseract.version())
f"Please install tesseract 4.0.0 or newer "
f"(currently installed version is {tesseract.version()})"
)
sys.exit(ExitCode.missing_dependency)
@@ -550,7 +550,7 @@ def check_options_output(options, log):
"with the OCR languages you specified. Use --output-type pdf or "
"upgrade to Ghostscript 9.20 or later to avoid this issue."
)
msg += "Found Ghostscript {}".format(ghostscript.version())
msg += f"Found Ghostscript {ghostscript.version()}"
log.warning(msg)
# Decide on what renderer to use
@@ -599,12 +599,12 @@ def _optional_program_required(name, version_fn, min_version, for_argument):
try:
if version_fn() < min_version:
raise MissingDependencyError(
"The installed '{}' is not supported. "
"Install version {} or newer.".format(name, min_version)
f"The installed '{name}' is not supported. "
f"Install version {min_version} or newer."
)
except (FileNotFoundError, MissingDependencyError):
raise MissingDependencyError(
"Install the '{}' program to use {}.".format(name, for_argument)
f"Install the '{name}' program to use {for_argument}."
)
@@ -612,13 +612,13 @@ def _optional_program_recommended(name, version_fn, min_version, for_argument):
try:
if version_fn() < min_version:
raise MissingDependencyError(
"The installed '{}' is not supported. "
"Install version {} or newer.".format(name, min_version)
f"The installed '{name}' is not supported. "
f"Install version {min_version} or newer."
)
except (FileNotFoundError, MissingDependencyError):
complain(
"For best results, install the optional program '{}' to use the "
"argument {}.".format(name, for_argument)
f"For best results, install the optional program '{name}' to use the "
f"argument {for_argument}."
)
@@ -926,11 +926,9 @@ def check_environ(options, _log):
if k in os.environ:
_log.warning(
textwrap.dedent(
"""\
OCRmyPDF no longer uses the environment variable {}.
Change PATH to select alternate programs.""".format(
k
)
f"""\
OCRmyPDF no longer uses the environment variable {k}.
Change PATH to select alternate programs."""
)
)
@@ -996,9 +994,7 @@ def report_output_file_size(options, _log, input_file, output_file):
if not attr:
continue
reasons.append(
"The argument --{} was issued, causing transcoding.".format(
arg.replace('_', '-')
)
f"The argument --{arg.replace('_', '-')} was issued, causing transcoding."
)
if reasons:
@@ -1008,12 +1004,10 @@ def report_output_file_size(options, _log, input_file, output_file):
_log.warning(
textwrap.dedent(
"""\
The output file size is {:.2f}× larger than the input file.
{}
""".format(
ratio, explanation
)
f"""\
The output file size is {ratio:.2f}× larger than the input file.
{explanation}
"""
)
)
@@ -1040,9 +1034,9 @@ def run_pipeline(args=None):
# for qpdf 7.0.0 for Ubuntu trusty (i.e. Travis)
if qpdf.version() < '7.0.0' and not os.environ.get('PYTEST_CURRENT_TEST'):
complain(
"You are using qpdf version {0} which has known issues including "
"security vulnerabilities with certain malformed PDFs. Consider "
"upgrading to version 7.0.0 or newer.".format(qpdf.version())
f"You are using qpdf version {qpdf.version()} which has known issues including "
f"security vulnerabilities with certain malformed PDFs. Consider "
f"upgrading to version 7.0.0 or newer."
)
if ghostscript.version() == '9.24':
@@ -1105,7 +1099,7 @@ def run_pipeline(args=None):
return ExitCode.other_error
if options.flowchart:
_log.info("Flowchart saved to {}".format(options.flowchart))
_log.info(f"Flowchart saved to {options.flowchart}")
return ExitCode.ok
elif options.output_file == '-':
_log.info("Output sent to stdout")
@@ -1115,11 +1109,11 @@ def run_pipeline(args=None):
if options.output_type.startswith('pdfa'):
pdfa_info = file_claims_pdfa(options.output_file)
if pdfa_info['pass']:
msg = 'Output file is a {} (as expected)'
_log.info(msg.format(pdfa_info['conformance']))
msg = f"Output file is a {pdfa_info['conformance']} (as expected)"
_log.info(msg)
else:
msg = 'Output file is okay but is not PDF/A (seems to be {})'
_log.warning(msg.format(pdfa_info['conformance']))
msg = f"Output file is okay but is not PDF/A (seems to be {pdfa_info['conformance']})"
_log.warning(msg)
return ExitCode.pdfa_conversion_failed
if not qpdf.check(options.output_file, _log):
_log.warning('Output file: The generated PDF is INVALID')
+1 -4
View File
@@ -77,10 +77,7 @@ class JobContextManager(SyncManager):
def cleanup_working_files(work_folder, options):
if options.keep_temporary_files:
print(
"Temporary working files saved at:\n{0}".format(work_folder),
file=sys.stderr,
)
print(f"Temporary working files saved at:\n{work_folder}", file=sys.stderr)
else:
with suppress(FileNotFoundError):
shutil.rmtree(work_folder)
+29 -37
View File
@@ -270,29 +270,29 @@ def is_ocr_required(pageinfo, log, options):
ocr_required = True
if pageinfo.has_text:
msg = "{0:4d}: page already has text! {1}"
prefix = f"{page:4d}: page already has text! - "
if not options.force_ocr and not (options.skip_text or options.redo_ocr):
log.error(msg.format(page, "aborting (use --force-ocr to force OCR)"))
log.error(prefix + "aborting (use --force-ocr to force OCR)")
raise PriorOcrFoundError()
elif options.force_ocr:
log.info(msg.format(page, "rasterizing text and running OCR anyway"))
log.info(prefix + "rasterizing text and running OCR anyway")
ocr_required = True
elif options.redo_ocr:
if pageinfo.has_corrupt_text:
log.warning(
msg.format(
page,
prefix
+ (
"some text on this page cannot be mapped to characters: "
"consider using --force-ocr instead",
)
)
raise PriorOcrFoundError() # Wrong error but will do for now
else:
log.info(msg.format(page, "redoing OCR"))
log.info(prefix + "redoing OCR")
ocr_required = True
elif options.skip_text:
log.info(msg.format(page, "skipping all processing on this page"))
log.info(prefix + "skipping all processing on this page")
ocr_required = False
elif not pageinfo.images and not options.lossless_reconstruction:
# We found a page with no images and no text. That means it may
@@ -305,27 +305,25 @@ def is_ocr_required(pageinfo, log, options):
if options.force_ocr and options.oversample:
# The user really wants to reprocess this file
log.info(
"{0:4d}: page has no images - "
"rasterizing at {1} DPI because "
"--force-ocr --oversample was specified".format(
page, options.oversample
)
f"{page:4d}: page has no images - "
f"rasterizing at {options.oversample} DPI because "
"--force-ocr --oversample was specified"
)
elif options.force_ocr:
# Warn the user they might not want to do this
log.warning(
"{0:4d}: page has no images - "
f"{page:4d}: page has no images - "
"all vector content will be "
"rasterized at {1} DPI, losing some resolution and likely "
f"rasterized at {VECTOR_PAGE_DPI} DPI, losing some resolution and likely "
"increasing file size. Use --oversample to adjust the "
"DPI.".format(page, VECTOR_PAGE_DPI)
"DPI."
)
else:
log.info(
"{0:4d}: page has no images - "
f"{page:4d}: page has no images - "
"skipping all processing on this page to avoid losing detail. "
"Use --force-ocr if you wish to perform OCR on pages that "
"have vector content.".format(page)
"have vector content."
)
ocr_required = False
@@ -334,10 +332,8 @@ def is_ocr_required(pageinfo, log, options):
if pixel_count > (options.skip_big * 1_000_000):
ocr_required = False
log.warning(
"{0:4d}: page too big, skipping OCR "
"({1:.1f} MPixels > {2:.1f} MPixels --skip-big)".format(
page, pixel_count / 1_000_000, options.skip_big
)
f"{page:4d}: page too big, skipping OCR "
f"({(pixel_count / 1_000_000):.1f} MPixels > {options.skip_big:.1f} MPixels --skip-big)"
)
return ocr_required
@@ -358,7 +354,7 @@ def marker_pages(input_files, output_files, log, context):
# If no files were repaired the input will be empty
if not input_file:
log.error("{0}: file not found or invalid argument".format(options.input_file))
log.error(f"{options.input_file}: file not found or invalid argument")
raise InputFileError()
pdfinfo = context.get_pdfinfo()
@@ -367,7 +363,7 @@ def marker_pages(input_files, output_files, log, context):
# Ruffus needs to see a file for any task it generates, so make very
# file a symlink back to the source.
for n in range(npages):
page = Path(work_folder) / '{0:06d}.marker.pdf'.format(n + 1)
page = Path(work_folder) / f'{(n + 1):06d}.marker.pdf'
page.symlink_to(input_file) # pylint: disable=E1101
@@ -509,7 +505,7 @@ def rasterize_with_ghostscript(input_file, output_file, log, context):
device = colorspaces[device_idx]
log.debug("Rasterize {0} with {1}".format(os.path.basename(input_file), device))
log.debug(f"Rasterize {os.path.basename(input_file)} with {device}")
# Produce the page image with square resolution or else deskew and OCR
# will not work properly.
@@ -543,9 +539,7 @@ def preprocess_remove_background(input_file, output_file, log, context):
if any(image.bpc > 1 for image in pageinfo.images):
leptonica.remove_background(input_file, output_file)
else:
log.info(
"{0:4d}: background removal skipped on mono page".format(pageinfo.pageno)
)
log.info(f"{pageinfo.pageno:4d}: background removal skipped on mono page")
re_symlink(input_file, output_file, log)
@@ -670,7 +664,7 @@ def select_visible_page_image(infiles, output_file, log, context):
pageinfo = get_pageinfo(image, context)
if pageinfo.images and all(im.enc == 'jpeg' for im in pageinfo.images):
log.debug('{:4d}: JPEG input -> JPEG output'.format(page_number(image)))
log.debug(f'{page_number(image):4d}: JPEG input -> JPEG output')
# If all images were JPEGs originally, produce a JPEG as output
with Image.open(image) as im:
# At this point the image should be a .png, but deskew, unpaper
@@ -699,9 +693,7 @@ def select_image_layer(infiles, output_file, log, context):
if options.lossless_reconstruction:
log.debug(
"{:4d}: page eligible for lossless reconstruction".format(
page_number(page_pdf)
)
f"{page_number(page_pdf):4d}: page eligible for lossless reconstruction"
)
re_symlink(page_pdf, output_file, log) # Still points to multipage
return
@@ -719,11 +711,11 @@ def select_image_layer(infiles, output_file, log, context):
# This create a single page PDF
with open(image, 'rb') as imfile, open(output_file, 'wb') as pdf:
log.debug('{:4d}: convert'.format(page_number(page_pdf)))
log.debug(f'{page_number(page_pdf):4d}: convert')
img2pdf.convert(
imfile, with_pdfrw=False, layout_fun=layout_fun, outputstream=pdf
)
log.debug('{:4d}: convert done'.format(page_number(page_pdf)))
log.debug(f'{page_number(page_pdf):4d}: convert done')
def render_hocr_page(infiles, output_file, log, context):
@@ -794,10 +786,10 @@ def get_docinfo(base_pdf, options):
else:
renderer_tag = 'OCR'
pdfmark['/Creator'] = '{0} {1} / Tesseract {2} {3}'.format(
PROGRAM_NAME, VERSION, renderer_tag, tesseract.version()
pdfmark['/Creator'] = (
f'{PROGRAM_NAME} {VERSION} / ' f'Tesseract {renderer_tag} {tesseract.version()}'
)
pdfmark['/Producer'] = 'pikepdf ' + pikepdf.__version__
pdfmark['/Producer'] = f'pikepdf {pikepdf.__version__}'
if 'OCRMYPDF_CREATOR' in os.environ:
pdfmark['/Creator'] = os.environ['OCRMYPDF_CREATOR']
if 'OCRMYPDF_PRODUCER' in os.environ:
@@ -907,7 +899,7 @@ def merge_sidecars(input_files_groups, output_file, log, context):
else:
stream.write(txt)
else:
stream.write('[OCR skipped on page {}]'.format(page_num + 1))
stream.write(f'[OCR skipped on page {(page_num + 1)}]')
if output_file == '-':
write_pages(sys.stdout)
+1 -1
View File
@@ -400,7 +400,7 @@ def weave_layers(infiles, output_file, log, context):
_update_page_resources(
page=page0, font=font, font_key=font_key, procset=procset
)
interim = output_file + '_working{}.pdf'.format(page_num)
interim = output_file + f'_working{page_num}.pdf'
pdf_base.save(interim)
del pdf_base
keep_open = []
+5 -8
View File
@@ -39,25 +39,22 @@ def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)'):
output = proc.stdout
except FileNotFoundError as e:
raise MissingDependencyError(
"Could not find program '{}' on the PATH".format(program)
f"Could not find program '{program}' on the PATH"
) from e
except CalledProcessError as e:
if e.returncode < 0:
raise MissingDependencyError(
"Ran program '{}' but it exited with an error:\n{}".format(
program, e.output
)
f"Ran program '{program}' but it exited with an error:\n{e.output}"
) from e
raise MissingDependencyError(
"Could not find program '{}' on the PATH".format(program)
f"Could not find program '{program}' on the PATH"
) from e
try:
version = re.match(regex, output.strip()).group(1)
except AttributeError as e:
raise MissingDependencyError(
("The program '{}' did not report its version. " "Message was:\n{}").format(
program, output
)
f"The program '{program}' did not report its version. "
f"Message was:\n{output}"
)
return version
+5 -7
View File
@@ -142,10 +142,10 @@ def rasterize_pdf(
'-dSAFER',
'-dBATCH',
'-dNOPAUSE',
'-sDEVICE=%s' % raster_device,
'-dFirstPage=%i' % pageno,
'-dLastPage=%i' % pageno,
'-r{0}x{1}'.format(str(int_res[0]), str(int_res[1])),
f'-sDEVICE={raster_device}',
f'-dFirstPage={pageno}',
f'-dLastPage={pageno}',
f'-r{str(int_res[0])}x{str(int_res[1])}',
]
+ (['-dFILTERVECTOR'] if filter_vector else [])
+ [
@@ -181,9 +181,7 @@ def rasterize_pdf(
)
if expected_size != im.size or page_dpi != (xres, yres):
log.debug(
"Ghostscript: resize output image {} -> {}".format(
im.size, expected_size
)
f"Ghostscript: resize output image {im.size} -> {expected_size}"
)
im = im.resize(expected_size)
+1 -1
View File
@@ -43,7 +43,7 @@ def quantize(input_file, output_file, quality_min, quality_max):
'--output',
output_file,
'--quality',
'{}-{}'.format(quality_min, quality_max),
f'{quality_min}-{quality_max}',
'--',
input_file,
]
+2 -2
View File
@@ -159,7 +159,7 @@ def get_orientation(input_file, engine_mode, timeout: float, log):
def tesseract_log_output(log, stdout, input_file):
prefix = "{0:4d}: [tesseract] ".format(page_number(input_file))
prefix = f"{(page_number(input_file)):4d}: [tesseract] "
try:
text = stdout.decode()
@@ -201,7 +201,7 @@ def tesseract_log_output(log, stdout, input_file):
def page_timedout(log, input_file):
prefix = "{0:4d}: [tesseract] ".format(page_number(input_file))
prefix = f"{(page_number(input_file)):4d}: [tesseract] "
log.warning(prefix + " took too long to OCR - skipping")
+9 -9
View File
@@ -36,7 +36,7 @@ DEFAULT_PNG_QUALITY = 70
def img_name(root, xref, ext):
return fspath(root / '{:08d}{}'.format(xref, ext))
return fspath(root / f'{xref:08d}{ext}')
def png_name(root, xref):
@@ -86,7 +86,7 @@ def extract_image_jbig2(*, pike, root, log, image, xref, options):
and jbig2enc.available()
):
try:
imgname = Path(root / '{:08d}'.format(xref))
imgname = Path(root / f'{xref:08d}')
with imgname.open('wb') as f:
ext = pim.extract_to(stream=f)
imgname.rename(imgname.with_suffix(ext))
@@ -120,7 +120,7 @@ def extract_image_generic(*, pike, root, log, image, xref, options):
# with Image.open(stream) as im:
# im.save(jpg_name(root, xref), icc_profile=iccbytes)
try:
imgname = Path(root / '{:08d}'.format(xref))
imgname = Path(root / f'{xref:08d}')
with imgname.open('wb') as f:
ext = pim.extract_to(stream=f)
imgname.rename(imgname.with_suffix(ext))
@@ -224,7 +224,7 @@ def _produce_jbig2_images(jbig2_groups, root, log, options):
def jbig2_group_futures(executor, root, groups):
for group, xref_exts in groups.items():
prefix = 'group{:08d}'.format(group)
prefix = f'group{group:08d}'
future = executor.submit(
jbig2enc.convert_group,
cwd=fspath(root),
@@ -235,7 +235,7 @@ def _produce_jbig2_images(jbig2_groups, root, log, options):
def jbig2_single_futures(executor, root, groups):
for group, xref_exts in groups.items():
prefix = 'group{:08d}'.format(group)
prefix = f'group{group:08d}'
# Second loop is to ensure multiple images per page are unpacked
for n, xref_ext in enumerate(xref_exts):
xref, ext = xref_ext
@@ -243,7 +243,7 @@ def _produce_jbig2_images(jbig2_groups, root, log, options):
jbig2enc.convert_single,
cwd=fspath(root),
infile=img_name(root, xref, ext),
outfile=root / ('{}.{:04d}'.format(prefix, n)),
outfile=root / f'{prefix}.{n:04d}',
)
yield future
@@ -276,7 +276,7 @@ def convert_to_jbig2(pike, jbig2_groups, root, log, options):
_produce_jbig2_images(jbig2_groups, root, log, options)
for group, xref_exts in jbig2_groups.items():
prefix = 'group{:08d}'.format(group)
prefix = f'group{group:08d}'
jbig2_symfile = root / (prefix + '.sym')
if jbig2_symfile.exists():
jbig2_globals_data = jbig2_symfile.read_bytes()
@@ -289,7 +289,7 @@ def convert_to_jbig2(pike, jbig2_groups, root, log, options):
for n, xref_ext in enumerate(xref_exts):
xref, _ = xref_ext
jbig2_im_file = root / (prefix + '.{:04d}'.format(n))
jbig2_im_file = root / (prefix + f'.{n:04d}')
jbig2_im_data = jbig2_im_file.read_bytes()
im_obj = pike.get_object(xref, 0)
im_obj.write(
@@ -428,7 +428,7 @@ def optimize(input_file, output_file, log, context):
output_size = Path(target_file).stat().st_size
ratio = input_size / output_size
savings = 1 - output_size / input_size
log.info("Optimize ratio: {:.2f} savings: {:.1f}%".format(ratio, 100 * savings))
log.info(f"Optimize ratio: {ratio:.2f} savings: {(100 * savings):.1f}%")
if savings < 0:
log.info("Optimize did not improve the file - discarded")
+1 -1
View File
@@ -140,7 +140,7 @@ def file_claims_pdfa(filename):
'conformance': 'No PDF/A metadata in XMP',
}
valid_part_conforms = {'1A', '1B', '2A', '2B', '2U', '3A', '3B', '3U'}
conformance = 'PDF/A-{}'.format(pdfmeta.pdfa_status)
conformance = f'PDF/A-{pdfmeta.pdfa_status}'
pdfa_dict = {}
if pdfmeta.pdfa_status in valid_part_conforms:
pdfa_dict['pass'] = True
+1 -1
View File
@@ -789,7 +789,7 @@ class PdfInfo:
return len(self._pages)
def __repr__(self):
return "<PdfInfo('...'), page count={}>".format(len(self))
return f"<PdfInfo('...'), page count={len(self)}>"
def main():
+1 -1
View File
@@ -126,7 +126,7 @@ def main():
cache_folder = Path(CACHE_ROOT) / Path(source).stem / argv_slug
cache_folder.mkdir(parents=True, exist_ok=True)
print("Tesseract cache folder {} - ".format(cache_folder), end='', file=sys.stderr)
print(f"Tesseract cache folder {cache_folder} - ", end='', file=sys.stderr)
if (cache_folder / 'stderr.bin').exists() and not cache_disabled:
# Cache hit
+2 -2
View File
@@ -160,7 +160,7 @@ def test_remove_background(spoof_tesseract_noop, resources, outdir):
def test_exotic_image(
spoof_tesseract_cache, pdf, renderer, output_type, resources, outdir
):
outfile = outdir / 'test_{0}_{1}.pdf'.format(pdf, renderer)
outfile = outdir / f'test_{pdf}_{renderer}.pdf'
check_ocrmypdf(
resources / pdf,
outfile,
@@ -994,7 +994,7 @@ def test_pdfa_n(spoof_tesseract_cache, pdfa_level, resources, outpdf):
)
pdfa_info = file_claims_pdfa(outpdf)
assert pdfa_info['conformance'] == 'PDF/A-{}B'.format(pdfa_level)
assert pdfa_info['conformance'] == f'PDF/A-{pdfa_level}B'
@pytest.mark.skipif(sys.version_info >= (3, 7, 0), reason='better utf-8')
+2 -2
View File
@@ -247,12 +247,12 @@ def test_xml_metadata_preserved(spoof_tesseract_noop, output_type, resources, ou
for prop in equal_properties:
if prop in before:
assert prop in after, '{} dropped from xmp'.format(prop)
assert prop in after, f'{prop} dropped from xmp'
assert before[prop] == after[prop]
# Certain entries like title appear as dc:title[1], with the possibility
# of several
propidx = '{}[1]'.format(prop)
propidx = f'{prop}[1]'
if propidx in before:
assert (
after.get(propidx) == before[propidx]
-1
View File
@@ -182,7 +182,6 @@ def test_ocr_detection(resources):
assert pdf[0].has_text
@pytest.mark.parametrize(
'testfile', ('truetype_font_nomapping.pdf', 'type3_font_nomapping.pdf')
)
+4 -6
View File
@@ -50,10 +50,8 @@ def check_monochrome_correlation(
):
gslog = logging.getLogger()
reference_png = outdir / '{}.ref{:04d}.png'.format(
reference_pdf.name, reference_pageno
)
test_png = outdir / '{}.test{:04d}.png'.format(test_pdf.name, test_pageno)
reference_png = outdir / f'{reference_pdf.name}.ref{reference_pageno:04d}.png'
test_png = outdir / f'{test_pdf.name}.test{test_pageno:04d}.png'
def rasterize(pdf, pageno, png):
if png.exists():
@@ -229,7 +227,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
im = Image.open(fspath(resources / 'typewriter.png'))
if image_angle != 0:
ccw_angle = -image_angle % 360
im = im.transpose(getattr(Image, 'ROTATE_{}'.format(ccw_angle)))
im = im.transpose(getattr(Image, f'ROTATE_{ccw_angle}'))
memimg = BytesIO()
im.save(memimg, format='PNG')
memimg.seek(0)
@@ -242,7 +240,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
mempdf.seek(0)
pike = pikepdf.open(mempdf)
pike.pages[0].Rotate = page_angle
target = outdir / '{}_{}_{}.pdf'.format(prefix, image_angle, page_angle)
target = outdir / f'{prefix}_{image_angle}_{page_angle}.pdf'
pike.save(target)
return target