mirror of
https://github.com/boostorg/json.git
synced 2026-01-19 04:12:14 +00:00
pretty printer module
This commit is contained in:
committed by
Dmitry Arkhipov
parent
824cc59b9f
commit
9faecfc7ce
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/bin
|
||||
/bin.v2
|
||||
/bin64
|
||||
/_build*
|
||||
temp
|
||||
@@ -19,3 +20,6 @@ temp
|
||||
# local copy of bench
|
||||
/bench/bench
|
||||
/bench/bench.exe
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
|
||||
118
pretty_printers/FindBoostPrettyPrinters.cmake
Normal file
118
pretty_printers/FindBoostPrettyPrinters.cmake
Normal file
@@ -0,0 +1,118 @@
|
||||
#
|
||||
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/json
|
||||
#
|
||||
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(BoostPrettyPrinters
|
||||
REQUIRED_VARS Python3_Interpreter_FOUND)
|
||||
|
||||
find_program(BoostPrettyPrinters_GDB gdb DOC "GDB executable tos use")
|
||||
set(BoostPrettyPrinters_HAS_GDB "${BoostPrettyPrinters_GDB}")
|
||||
|
||||
set(BoostPrettyPrinters_GDB_HEADER_SCRIPT
|
||||
"${CMAKE_CURRENT_LIST_DIR}/generate-gdb-header.py")
|
||||
set(BoostPrettyPrinters_GDB_TEST_SCRIPT
|
||||
"${CMAKE_CURRENT_LIST_DIR}/generate-gdb-test-runner.py")
|
||||
set(BoostPrettyPrinters_INCLUDES "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
|
||||
function(boost_pretty_printers_gdb_python_header)
|
||||
set(options EXCLUDE_FROM_ALL)
|
||||
set(oneValueArgs TARGET INPUT OUTPUT HEADER_GUARD DISABLE_MACRO)
|
||||
set(multiValueArgs)
|
||||
cmake_parse_arguments(BOOST_PPRINT_GDB_GEN
|
||||
"${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
foreach(kw TARGET INPUT OUTPUT)
|
||||
if(NOT DEFINED "BOOST_PPRINT_GDB_GEN_${kw}")
|
||||
message(FATAL_ERROR "Argument ${kw} is required for function \
|
||||
boost_pretty_printers_gdb_python_header.")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(DEFINED BOOST_PPRINT_GDB_GEN_HEADER_GUARD)
|
||||
set(BOOST_PPRINT_GDB_GEN_HEADER_GUARD
|
||||
"--header-guard=${BOOST_PPRINT_GDB_GEN_HEADER_GUARD}")
|
||||
endif()
|
||||
if(DEFINED BOOST_PPRINT_GDB_GEN_DISABLE_MACRO)
|
||||
set(BOOST_PPRINT_GDB_GEN_DISABLE_MACRO
|
||||
"--disable-macro=${BOOST_PPRINT_GDB_GEN_DISABLE_MACRO}")
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT "${BOOST_PPRINT_GDB_GEN_OUTPUT}"
|
||||
MAIN_DEPENDENCY "${BOOST_PPRINT_GDB_GEN_INPUT}"
|
||||
DEPENDS "${BoostPrettyPrinters_GDB_HEADER_SCRIPT}"
|
||||
COMMAND
|
||||
"${Python3_EXECUTABLE}"
|
||||
"${BoostPrettyPrinters_GDB_HEADER_SCRIPT}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_PPRINT_GDB_GEN_INPUT}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_PPRINT_GDB_GEN_OUTPUT}"
|
||||
${BOOST_PPRINT_GDB_GEN_HEADER_GUARD}
|
||||
${BOOST_PPRINT_GDB_GEN_DISABLE_MACRO}
|
||||
COMMENT "Regenerating ${BOOST_PPRINT_GDB_GEN_OUTPUT}")
|
||||
|
||||
if(NOT BOOST_PPRINT_GDB_GEN_EXCLUDE_FROM_ALL)
|
||||
set(isInAll ALL)
|
||||
endif()
|
||||
add_custom_target(${BOOST_PPRINT_GDB_GEN_TARGET}
|
||||
${isInAll}
|
||||
DEPENDS "${BOOST_PPRINT_GDB_GEN_OUTPUT}")
|
||||
endfunction()
|
||||
|
||||
|
||||
function(boost_pretty_printers_test_gdb_printers)
|
||||
set(options EXCLUDE_FROM_ALL)
|
||||
set(oneValueArgs TEST PROGRAM)
|
||||
set(multiValueArgs SOURCES)
|
||||
cmake_parse_arguments(BOOST_PPRINT_TEST_GDB
|
||||
"${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
foreach(kw TEST SOURCES)
|
||||
if(NOT DEFINED "BOOST_PPRINT_TEST_GDB_${kw}")
|
||||
message(FATAL_ERROR "Argument ${kw} is required for function \
|
||||
boost_pretty_printers_test_gdb_printers.")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT DEFINED BOOST_PPRINT_TEST_GDB_PROGRAM)
|
||||
set(BOOST_PPRINT_TEST_GDB_PROGRAM ${BOOST_PPRINT_TEST_GDB_TEST})
|
||||
endif()
|
||||
if(BOOST_PPRINT_TEST_GDB_EXCLUDE_FROM_ALL)
|
||||
set(excludeFromAll EXCLUDE_FROM_ALL)
|
||||
else()
|
||||
set(includeInAll ALL)
|
||||
endif()
|
||||
|
||||
LIST(GET BOOST_PPRINT_TEST_GDB_SOURCES 0 source0)
|
||||
add_custom_command(
|
||||
OUTPUT "${BOOST_PPRINT_TEST_GDB_TEST}.py"
|
||||
DEPENDS "${source0}"
|
||||
COMMAND
|
||||
"${Python3_EXECUTABLE}"
|
||||
"${BoostPrettyPrinters_GDB_TEST_SCRIPT}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${source0}"
|
||||
"${BOOST_PPRINT_TEST_GDB_TEST}.py"
|
||||
COMMENT "Generating ${source0}")
|
||||
|
||||
add_custom_target(${BOOST_PPRINT_TEST_GDB_TEST}_runner
|
||||
${includeInAll}
|
||||
DEPENDS "${BOOST_PPRINT_TEST_GDB_TEST}.py")
|
||||
|
||||
add_executable(${BOOST_PPRINT_TEST_GDB_PROGRAM}
|
||||
${excludeFromAll}
|
||||
${BOOST_PPRINT_TEST_GDB_SOURCES})
|
||||
add_dependencies(
|
||||
${BOOST_PPRINT_TEST_GDB_PROGRAM}
|
||||
${BOOST_PPRINT_TEST_GDB_TEST}_runner)
|
||||
|
||||
add_test(
|
||||
NAME ${BOOST_PPRINT_TEST_GDB_TEST}
|
||||
COMMAND "${BoostPrettyPrinters_GDB}"
|
||||
--batch-silent
|
||||
-x "${BOOST_PPRINT_TEST_GDB_TEST}.py"
|
||||
$<TARGET_FILE:${BOOST_PPRINT_TEST_GDB_PROGRAM}>)
|
||||
endfunction()
|
||||
159
pretty_printers/boost-pretty-printers.jam
Normal file
159
pretty_printers/boost-pretty-printers.jam
Normal file
@@ -0,0 +1,159 @@
|
||||
#
|
||||
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/json
|
||||
#
|
||||
|
||||
import common ;
|
||||
import make ;
|
||||
import modules ;
|
||||
import param ;
|
||||
import path ;
|
||||
import project ;
|
||||
import property ;
|
||||
import python ;
|
||||
import testing ;
|
||||
import toolset ;
|
||||
|
||||
|
||||
rule init ( command * )
|
||||
{
|
||||
if ! $(.initialized)
|
||||
{
|
||||
.initialized = true ;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ! $(command)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
}
|
||||
command ?= gdb ;
|
||||
|
||||
GDB_COMMAND = ;
|
||||
.has-gdb = ;
|
||||
for local part in $(command)
|
||||
{
|
||||
local found = [ common.find-tool $(part) ] ;
|
||||
if $(found)
|
||||
{
|
||||
.has-gdb = true ;
|
||||
}
|
||||
else
|
||||
{
|
||||
found = $(part) ;
|
||||
}
|
||||
GDB_COMMAND += $(found) ;
|
||||
}
|
||||
}
|
||||
|
||||
rule has-gdb
|
||||
{
|
||||
return $(.has-gdb) ;
|
||||
}
|
||||
|
||||
rule gdb-python-header ( target : sources + : requirements *
|
||||
: usage-requirements * )
|
||||
{
|
||||
param.handle-named-params sources requirements usage-requirements ;
|
||||
|
||||
make $(target)
|
||||
: $(sources)
|
||||
: @boost-pretty-printers.generate-gdb-header
|
||||
: $(requirements)
|
||||
<dependency>$(.gdb-header-script)
|
||||
: $(usage-requirements)
|
||||
;
|
||||
}
|
||||
|
||||
rule test-gdb-printers ( target : sources + : requirements * : default-build *
|
||||
: usage-requirements * )
|
||||
{
|
||||
param.handle-named-params
|
||||
sources requirements default-build usage-requirements ;
|
||||
|
||||
local project = [ project.current ] ;
|
||||
|
||||
local test-runner = _$(target:S=.py) ;
|
||||
make $(test-runner)
|
||||
: $(sources[1])
|
||||
: @boost-pretty-printers.generate-gdb-test-runner
|
||||
: <dependency>$(.gdb-test-generator-script)
|
||||
;
|
||||
$(project).mark-target-as-explicit $(test-runner) ;
|
||||
|
||||
local test-program = _$(target) ;
|
||||
run $(sources)
|
||||
: target-name $(test-program)
|
||||
: requirements
|
||||
<testing.launcher>$(GDB_COMMAND)
|
||||
<testing.arg>--batch-silent
|
||||
<testing.arg>-x
|
||||
<testing.input-file>$(test-runner)
|
||||
<debug-symbols>on
|
||||
<runtime-debugging>on
|
||||
<optimization>debug
|
||||
$(requirements)
|
||||
: default-build
|
||||
$(default-build)
|
||||
;
|
||||
$(project).mark-target-as-explicit $(test-program) ;
|
||||
|
||||
alias $(target)
|
||||
: $(test-program)
|
||||
: $(requirements)
|
||||
: $(default-build)
|
||||
: $(usage-requirements)
|
||||
;
|
||||
}
|
||||
|
||||
.here = [ path.make [ modules.binding $(__name__) ] ] ;
|
||||
.here = $(.here:D) ;
|
||||
.gdb-header-script = $(.here)/generate-gdb-header.py ;
|
||||
.gdb-test-generator-script = $(.here)/generate-gdb-test-runner.py ;
|
||||
|
||||
rule generate-gdb-header ( target : sources + : properties * )
|
||||
{
|
||||
warn-if-not-configuered ;
|
||||
RUNNER on $(target) = [ path.native $(.gdb-header-script) ] ;
|
||||
}
|
||||
actions generate-gdb-header
|
||||
{
|
||||
"$(PYTHON:E=python)" "$(RUNNER)" $(>[1]) $(<) $(FLAGS)
|
||||
}
|
||||
toolset.flags boost-pretty-printers.generate-gdb-header FLAGS <flags> ;
|
||||
toolset.flags boost-pretty-printers.generate-gdb-header PYTHON <python.interpreter> ;
|
||||
|
||||
rule generate-gdb-test-runner ( target : sources + : properties * )
|
||||
{
|
||||
warn-if-not-configuered ;
|
||||
RUNNER on $(target) = [ path.native $(.gdb-test-generator-script) ] ;
|
||||
}
|
||||
actions generate-gdb-test-runner
|
||||
{
|
||||
"$(PYTHON:E=python)" "$(RUNNER)" $(>[1]) $(<)
|
||||
}
|
||||
toolset.flags boost-pretty-printers.generate-gdb-test-runner PYTHON <python.interpreter> ;
|
||||
|
||||
rule warn-if-not-configuered ( )
|
||||
{
|
||||
if $(.checked) { return ; }
|
||||
|
||||
if ! $(.initialized)
|
||||
{
|
||||
echo "warning: module boost-pretty-printers was not initialized!" ;
|
||||
echo " add \"using boost-pretty-printers ;\" to your build scripts." ;
|
||||
}
|
||||
|
||||
if ! [ python.configured ]
|
||||
{
|
||||
echo "warning: module python was not initialized!" ;
|
||||
echo " add \"using python ;\" to your build scripts." ;
|
||||
}
|
||||
|
||||
.checked = true ;
|
||||
}
|
||||
150
pretty_printers/generate-gdb-header.py
Executable file
150
pretty_printers/generate-gdb-header.py
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/json
|
||||
#
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
_top = '''\
|
||||
#if defined(__ELF__)
|
||||
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Woverlength-strings"
|
||||
#elif defined(__GNUC__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Woverlength-strings"
|
||||
#endif
|
||||
|
||||
__asm__(
|
||||
".pushsection \\\".debug_gdb_scripts\\\", \\\"MS\\\",@progbits,1\\n"
|
||||
".ascii \\\"\\\\4gdb.inlined-script.{script_id}\\\\n\\\"\\n"
|
||||
'''
|
||||
|
||||
_bottom = '''\
|
||||
".byte 0\\n"
|
||||
".popsection\\n");
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // defined(__ELF__)
|
||||
'''
|
||||
|
||||
|
||||
class Nullcontext():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
pass
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=args[0],
|
||||
description=(
|
||||
'Converts a Python script into a C header '
|
||||
'that pushes that script into .debug_gdb_scripts ELF section'))
|
||||
parser.add_argument(
|
||||
'input',
|
||||
help='Input file')
|
||||
parser.add_argument(
|
||||
'output',
|
||||
nargs='?',
|
||||
help='Output file; STDOUT by default')
|
||||
parser.add_argument(
|
||||
'--header-guard',
|
||||
help=(
|
||||
'Header guard macro to use; '
|
||||
'by default deduced from the output file name'))
|
||||
parser.add_argument(
|
||||
'--disable-macro',
|
||||
default='BOOST_ALL_NO_EMBEDDED_GDB_SCRIPTS',
|
||||
help=(
|
||||
'Macro to disable pretty printer embedding; '
|
||||
'by default BOOST_ALL_NO_EMBEDDED_GDB_SCRIPTS'))
|
||||
return parser.parse_args(args[1:])
|
||||
|
||||
def write_front_matter(header, header_guard, disable_macro, source):
|
||||
header.write(
|
||||
'// Autogenerated from %s by boost-pretty-printers\n\n' % os.path.basename(source))
|
||||
if header_guard:
|
||||
header.write('#ifndef %s\n' % header_guard)
|
||||
header.write('#define %s\n\n' % header_guard)
|
||||
if disable_macro:
|
||||
header.write('#ifndef %s\n\n' % disable_macro)
|
||||
header.write(
|
||||
_top.format(script_id=header_guard or str(random.random()) ))
|
||||
|
||||
def write_back_matter(header, header_guard, disable_macro):
|
||||
header.write(_bottom)
|
||||
if disable_macro:
|
||||
header.write('\n#endif // %s\n' % disable_macro)
|
||||
if header_guard:
|
||||
header.write('\n#endif // %s\n' % header_guard)
|
||||
|
||||
def main(args, stdin, stdout):
|
||||
args = parse_args(args)
|
||||
|
||||
header_guard = args.header_guard
|
||||
if header_guard is None:
|
||||
if args.output:
|
||||
header_guard = os.path.basename(args.output).upper()
|
||||
|
||||
if args.output:
|
||||
header = open(args.output, 'w', encoding='utf-8')
|
||||
header_ctx = header
|
||||
else:
|
||||
header = stdout
|
||||
header_ctx = Nullcontext()
|
||||
|
||||
not_whitespace = re.compile('\\S', re.U)
|
||||
|
||||
with open(args.input, 'r', encoding='utf-8') as script:
|
||||
with header_ctx:
|
||||
check_for_attribution = True
|
||||
for line in script:
|
||||
if not not_whitespace.search(line):
|
||||
header.write('\n')
|
||||
continue
|
||||
|
||||
if check_for_attribution:
|
||||
if line.startswith('#!'): # shebang
|
||||
continue
|
||||
elif line.startswith('#'):
|
||||
header.write('// ')
|
||||
header.write(line[1:])
|
||||
continue
|
||||
else:
|
||||
write_front_matter(
|
||||
header,
|
||||
header_guard,
|
||||
args.disable_macro,
|
||||
args.input)
|
||||
check_for_attribution = False
|
||||
|
||||
header.write(' ".ascii \\"')
|
||||
header.write(line[:-1])
|
||||
header.write('\\\\n\\"\\n"\n')
|
||||
|
||||
write_back_matter(header, header_guard, args.disable_macro)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv, sys.stdin, sys.stdout)
|
||||
139
pretty_printers/generate-gdb-test-runner.py
Executable file
139
pretty_printers/generate-gdb-test-runner.py
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2024 Dmitry Arkhipov (grisumbras@yandex.ru)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/json
|
||||
#
|
||||
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
_top = '''\
|
||||
import gdb
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
def gdb_print(expr):
|
||||
output = gdb.execute('print %s' % expr, to_string=True)
|
||||
parts = output[:-1].split(' = ', 1)
|
||||
if len(parts) > 1:
|
||||
output = parts[1]
|
||||
else:
|
||||
output = parts[0]
|
||||
return output
|
||||
|
||||
def TEST_EXPR(expr, pattern, *args, **kwargs):
|
||||
def test():
|
||||
if args or kwargs:
|
||||
actual_args = [gdb_print(arg) for arg in args]
|
||||
actual_kwargs = dict([
|
||||
(k, gdb_print(v)) for (k,v) in kwargs.items() ])
|
||||
actual_pattern = pattern.format(*actual_args, **actual_kwargs)
|
||||
else:
|
||||
actual_pattern = pattern
|
||||
output = gdb_print(expr)
|
||||
try:
|
||||
if actual_pattern != output:
|
||||
print((
|
||||
'{0}: error: expression "{1}" evaluates to\\n'
|
||||
'{2}\\n'
|
||||
'expected\\n'
|
||||
'{3}\\n').format(
|
||||
bp.location, expr, output, actual_pattern),
|
||||
file=sys.stderr)
|
||||
gdb.execute('quit 1')
|
||||
except:
|
||||
raise
|
||||
return test
|
||||
|
||||
_return_code = 0
|
||||
_tests_to_run = []
|
||||
try:
|
||||
assert gdb.objfiles()
|
||||
|
||||
'''
|
||||
|
||||
_breakpoint = '''\
|
||||
_tests_to_run.append(
|
||||
(gdb.Breakpoint('{input}:{line}', internal=True), {text}))
|
||||
'''
|
||||
|
||||
_bottom = '''\
|
||||
gdb.execute('start', to_string=True)
|
||||
for bp, test in _tests_to_run:
|
||||
gdb.execute('continue', to_string=True)
|
||||
test()
|
||||
gdb.execute('continue', to_string=True)
|
||||
except BaseException:
|
||||
traceback.print_exc()
|
||||
gdb.execute('disable breakpoints')
|
||||
try:
|
||||
gdb.execute('continue')
|
||||
except:
|
||||
pass
|
||||
_return_code = 1
|
||||
|
||||
gdb.execute('quit %s' % _return_code)
|
||||
'''
|
||||
|
||||
|
||||
class Nullcontext():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
pass
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=args[0],
|
||||
description=(
|
||||
'Creates a Python script from C++ source file to control a GDB '
|
||||
'test of that source file'))
|
||||
parser.add_argument(
|
||||
'input',
|
||||
help='Input file')
|
||||
parser.add_argument(
|
||||
'output',
|
||||
nargs='?',
|
||||
help='Output file; STDOUT by default')
|
||||
return parser.parse_args(args[1:])
|
||||
|
||||
def main(args, stdin, stdout):
|
||||
args = parse_args(args)
|
||||
|
||||
if args.output:
|
||||
output = open(args.output, 'w', encoding='utf-8')
|
||||
output_ctx = output
|
||||
else:
|
||||
output = stdout
|
||||
output_ctx = Nullcontext()
|
||||
|
||||
test_line = re.compile(r'^\s*//\s*TEST_', re.U)
|
||||
|
||||
with open(args.input, 'r', encoding='utf-8') as input:
|
||||
with output_ctx:
|
||||
output.write(_top)
|
||||
for n, line in enumerate(input, start=1):
|
||||
match = test_line.search(line)
|
||||
if not match:
|
||||
continue
|
||||
line = line.strip()[2:].lstrip()
|
||||
output.write(
|
||||
_breakpoint.format(input=args.input, line=n, text=line))
|
||||
output.write(_bottom)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv, sys.stdin, sys.stdout)
|
||||
Reference in New Issue
Block a user