Quelle check_spidermonkey_style.py
Sprache: Python
# vim: set ts=8 sts=4 et sw=4 tw=99: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# ---------------------------------------------------------------------------- # This script checks various aspects of SpiderMonkey code style. The current checks are as # follows. # # We check the following things in headers. # # - No cyclic dependencies. # # - No normal header should #include a inlines.h/-inl.h file. # # - #ifndef wrappers should have the right form. (XXX: not yet implemented) # - Every header file should have one. # - The guard name used should be appropriate for the filename. # # We check the following things in all files. # # - #includes should have full paths, e.g. "jit/Ion.h", not "Ion.h". # # - #includes should use the appropriate form for system headers (<...>) and # local headers ("..."). # # - #includes should be ordered correctly. # - Each one should be in the correct section. # - Alphabetical order should be used within sections. # - Sections should be in the right order. # Note that the presence of #if/#endif blocks complicates things, to the # point that it's not always clear where a conditionally-compiled #include # statement should go, even to a human. Therefore, we check the #include # statements within each #if/#endif block (including nested ones) in # isolation, but don't try to do any order checking between such blocks. # ----------------------------------------------------------------------------
import difflib import os import re import sys
# We don't bother checking files in these directories, because they're (a) auxiliary or (b) # imported code that doesn't follow our coding style.
ignored_js_src_dirs = [ "js/src/config/", # auxiliary stuff "js/src/ctypes/libffi/", # imported code "js/src/devtools/", # auxiliary stuff "js/src/editline/", # imported code "js/src/gdb/", # auxiliary stuff "js/src/vtune/", # imported code "js/src/zydis/", # imported code "js/src/xsum/", # imported code
]
# We ignore #includes of these files, because they don't follow the usual rules.
included_inclnames_to_ignore = set(
[ "ffi.h", # generated in ctypes/libffi/ "devtools/Instruments.h", # we ignore devtools/ in general "double-conversion/double-conversion.h", # strange MFBT case "javascript-trace.h", # generated in $OBJDIR if HAVE_DTRACE is defined "frontend/ReservedWordsGenerated.h", # generated in $OBJDIR "frontend/smoosh_generated.h", # generated in $OBJDIR "gc/StatsPhasesGenerated.h", # generated in $OBJDIR "gc/StatsPhasesGenerated.inc", # generated in $OBJDIR "jit/ABIFunctionTypeGenerated.h", # generated in $OBJDIR" "jit/AtomicOperationsGenerated.h", # generated in $OBJDIR "jit/CacheIROpsGenerated.h", # generated in $OBJDIR "jit/CacheIRAOTGenerated.h", # generated in $OBJDIR "jit/LIROpsGenerated.h", # generated in $OBJDIR "jit/MIROpsGenerated.h", # generated in $OBJDIR "js/PrefsGenerated.h", # generated in $OBJDIR "js/ProfilingCategoryList.h", # comes from mozglue/baseprofiler "mozilla/glue/Debug.h", # comes from mozglue/misc, shadowed by <mozilla/Debug.h> "jscustomallocator.h", # provided by embedders; allowed to be missing "js-config.h", # generated in $OBJDIR "fdlibm.h", # fdlibm "FuzzerDefs.h", # included without a path "FuzzingInterface.h", # included without a path "diplomat_runtime.h", # ICU4X "ICU4XAnyCalendarKind.h", # ICU4X "ICU4XCalendar.h", # ICU4X "ICU4XDate.h", # ICU4X "ICU4XError.h", # ICU4X "ICU4XGraphemeClusterSegmenter.h", # ICU4X "ICU4XIsoDate.h", # ICU4X "ICU4XIsoWeekday.h", # ICU4X "ICU4XSentenceSegmenter.h", # ICU4X "ICU4XWeekCalculator.h", # ICU4X "ICU4XWeekOf.h", # ICU4X "ICU4XWeekRelativeUnit.h", # ICU4X "ICU4XWordSegmenter.h", # ICU4X "mozmemory.h", # included without a path "mozmemory_utils.h", # included without a path "pratom.h", # NSPR "prcvar.h", # NSPR "prerror.h", # NSPR "prinit.h", # NSPR "prio.h", # NSPR "private/pprio.h", # NSPR "prlink.h", # NSPR "prlock.h", # NSPR "prprf.h", # NSPR "prthread.h", # NSPR "prtypes.h", # NSPR "selfhosted.out.h", # generated in $OBJDIR "shellmoduleloader.out.h", # generated in $OBJDIR "unicode/locid.h", # ICU "unicode/uchar.h", # ICU "unicode/uniset.h", # ICU "unicode/unistr.h", # ICU "unicode/utypes.h", # ICU "vtune/VTuneWrapper.h", # VTune "wasm/WasmBuiltinModuleGenerated.h", # generated in $OBJDIR" "zydis/ZydisAPI.h", # Zydis "xsum/xsum.h", # xsum
]
)
# JSAPI functions should be included through headers from js/public instead of # using the old, catch-all jsapi.h file.
deprecated_inclnames_in_header = { "jsapi.h": "Prefer including headers from js/public.",
}
# Temporary exclusions for files which still need to include jsapi.h.
deprecated_inclnames_in_header_excludes = { "js/src/vm/Compartment-inl.h", # for JS::InformalValueTypeName "js/src/jsapi-tests/tests.h", # for JS_ValueToSource
}
# These files have additional constraints on where they are #included, so we # ignore #includes of them when checking #include ordering.
oddly_ordered_inclnames = set(
[ "ctypes/typedefs.h", # Included multiple times in the body of ctypes/CTypes.h # Included in the body of frontend/TokenStream.h "frontend/ReservedWordsGenerated.h", "gc/StatsPhasesGenerated.h", # Included in the body of gc/Statistics.h "gc/StatsPhasesGenerated.inc", # Included in the body of gc/Statistics.cpp "psapi.h", # Must be included after "util/WindowsWrapper.h" on Windows "machine/endian.h", # Must be included after <sys/types.h> on BSD "process.h", # Windows-specific "util/WindowsWrapper.h", # Must precede other system headers(?)
]
)
# System headers which shouldn't be included directly, but instead use the # designated wrapper.
wrapper_system_inclnames = { "windows.h": "util/WindowsWrapper.h", "windef.h": "util/WindowsWrapper.h", "winbase.h": "util/WindowsWrapper.h",
}
# The files in tests/style/ contain code that fails this checking in various # ways. Here is the output we expect. If the actual output differs from # this, one of the following must have happened. # - New SpiderMonkey code violates one of the checked rules. # - The tests/style/ files have changed without expected_output being changed # accordingly. # - This script has been broken somehow. #
expected_output = """\
js/src/tests/style/BadIncludes.h:3: error:
the file includes itself
js/src/tests/style/BadIncludes.h:6: error: "BadIncludes2.h"is included using the wrong path;
did you forget a prefix, oris the file not yet committed?
js/src/tests/style/BadIncludes.h:8: error:
<tests/style/BadIncludes2.h> should be included using
the #include "..." form
js/src/tests/style/BadIncludes.h:10: error: "stdio.h"is included using the wrong path;
did you forget a prefix, oris the file not yet committed?
js/src/tests/style/BadIncludes.h:12: error: "mozilla/Unused.h"is deprecated: Use [[nodiscard]] and (void)expr casts instead.
js/src/tests/style/BadIncludes2.h:1: error:
vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
js/src/tests/style/BadIncludesOrder-inl.h:5:6: error: "vm/JSScript-inl.h" should be included after "vm/Interpreter-inl.h"
js/src/tests/style/BadIncludesOrder-inl.h:6:7: error: "vm/Interpreter-inl.h" should be included after "js/Value.h"
js/src/tests/style/BadIncludesOrder-inl.h:7:8: error: "js/Value.h" should be included after "ds/LifoAlloc.h"
js/src/tests/style/BadIncludesOrder-inl.h:9: error: "jsapi.h"is deprecated: Prefer including headers from js/public.
js/src/tests/style/BadIncludesOrder-inl.h:8:9: error: "ds/LifoAlloc.h" should be included after "jsapi.h"
js/src/tests/style/BadIncludesOrder-inl.h:9:10: error: "jsapi.h" should be included after <stdio.h>
js/src/tests/style/BadIncludesOrder-inl.h:10:11: error:
<stdio.h> should be included after "mozilla/HashFunctions.h"
js/src/tests/style/BadIncludesOrder-inl.h:20: error: "jsapi.h"is deprecated: Prefer including headers from js/public.
js/src/tests/style/BadIncludesOrder-inl.h:28:29: error: "vm/JSScript.h" should be included after "vm/JSFunction.h"
(multiple files): error:
header files form one or more cycles
def out(*lines): for line in lines:
actual_output.append(line + "\n")
def error(filename, linenum, *lines):
location = filename if linenum isnotNone:
location += ":" + str(linenum)
out(location + ": error:") for line in lines:
out(" " + line)
out("")
class FileKind(object):
C = 1
CPP = 2
INL_H = 3
H = 4
TBL = 5
MSG = 6
@staticmethod def get(filename): if filename.endswith(".c"): return FileKind.C
if filename.endswith(".cpp"): return FileKind.CPP
if filename.endswith(("inlines.h", "-inl.h")): return FileKind.INL_H
if filename.endswith(".h"): return FileKind.H
if filename.endswith(".tbl"): return FileKind.TBL
if filename.endswith(".msg"): return FileKind.MSG
error(filename, None, "unknown file kind")
def check_style(enable_fixup): # We deal with two kinds of name. # - A "filename" is a full path to a file from the repository root. # - An "inclname" is how a file is referred to in a #include statement. # # Examples (filename -> inclname) # - "mfbt/Attributes.h" -> "mozilla/Attributes.h" # - "mozglue/misc/TimeStamp.h -> "mozilla/TimeStamp.h" # - "memory/mozalloc/mozalloc.h -> "mozilla/mozalloc.h" # - "js/public/Vector.h" -> "js/Vector.h" # - "js/src/vm/String.h" -> "vm/String.h"
# Process files in js/src.
js_src_root = os.path.join("js", "src") for dirpath, dirnames, filenames in os.walk(js_src_root): if dirpath == js_src_root: # Skip any subdirectories that contain a config.status file # (likely objdirs).
builddirs = [] for dirname in dirnames:
path = os.path.join(dirpath, dirname, "config.status") if os.path.isfile(path):
builddirs.append(dirname) for dirname in builddirs:
dirnames.remove(dirname) for filename in filenames:
filepath = os.path.join(dirpath, filename).replace("\\", "/") ifnot filepath.startswith(
tuple(ignored_js_src_dirs)
) and filepath.endswith((".c", ".cpp", ".h", ".tbl", ".msg")):
inclname = filepath[len("js/src/") :]
js_names[filepath] = inclname
# Look for header files in directories in non_js_dirnames. for non_js_dir in non_js_dirnames: for dirpath, dirnames, filenames in os.walk(non_js_dir): for filename in filenames: if filename.endswith(".h"):
inclname = "mozilla/" + filename if non_js_dir == "intl/components/":
inclname = "mozilla/intl/" + filename
non_js_inclnames.add(inclname)
# Look for header files in js/public.
js_public_root = os.path.join("js", "public") for dirpath, dirnames, filenames in os.walk(js_public_root): for filename in filenames: if filename.endswith((".h", ".msg")):
filepath = os.path.join(dirpath, filename).replace("\\", "/")
inclname = "js/" + filepath[len("js/public/") :]
js_names[filepath] = inclname
# We don't care what's inside the MFBT and MOZALLOC files, but because they # are #included from JS files we have to add them to the inclusion graph. for inclname in non_js_inclnames:
edges[inclname] = set()
# Process all the JS files. for filename in sorted(js_names.keys()):
inclname = js_names[filename]
file_kind = FileKind.get(filename) if (
file_kind == FileKind.C or file_kind == FileKind.CPP or file_kind == FileKind.H or file_kind == FileKind.INL_H
):
included_h_inclnames = set() # type: set(inclname)
with open(filename, encoding="utf-8") as f:
code = read_file(f)
if enable_fixup:
code = code.sorted(inclname) with open(filename, "w") as f:
f.write(code.to_source())
# Compare expected and actual output.
difflines = difflib.unified_diff(
expected_output,
actual_output,
fromfile="check_spidermonkey_style.py expected output",
tofile="check_spidermonkey_style.py actual output",
)
ok = True for diffline in difflines:
ok = False
print(diffline, end="")
return ok
def module_name(name): """Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename."""
def is_module_header(enclosing_inclname, header_inclname): """Determine if an included name is the "module header", i.e. should be
first in the file."""
module = module_name(enclosing_inclname)
# Normal case, for example: # module == "vm/Runtime", header_inclname == "vm/Runtime.h". if module == module_name(header_inclname): returnTrue
# A public header, for example: # # module == "vm/CharacterEncoding", # header_inclname == "js/CharacterEncoding.h" # # or (for implementation files for js/public/*/*.h headers) # # module == "vm/SourceHook", # header_inclname == "js/experimental/SourceHook.h"
m = re.match(r"js\/.*?([^\/]+)\.h", header_inclname) if m isnotNoneand module.endswith("/" + m.group(1)): returnTrue
returnFalse
class Include(object): """Important information for a single #include statement."""
def is_style_relevant(self): # Includes are style-relevant; that is, they're checked by the pairwise # style-checking algorithm in check_file. returnTrue
def section(self, enclosing_inclname): """Identify which section inclname belongs to.
The section numbers are as follows.
0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp)
1. mozilla/Foo.h
2. <foo.h> or <foo>
3. jsfoo.h, prmjtime.h, etc
4. foo/Bar.h
5. jsfooinlines.h
6. foo/Bar-inl.h
7. non-.h, e.g. *.tbl, *.msg (these can be scattered throughout files) """
if self.is_system: return 2
ifnot self.inclname.endswith(".h"): return 7
# A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need # special handling. if is_module_header(enclosing_inclname, self.inclname): return 0
if"/"in self.inclname: if self.inclname.startswith("mozilla/"): return 1
class CppBlock(object): """C preprocessor block: a whole file or a single #if/#elif/#else block.
A #if/#endif block is the contents of a #if/#endif (or similar) section.
The top-level block, which isnot within a #if/#endif pair, is also
considered a block.
Each kid is either an Include (representing a #include), OrdinaryCode, or
a nested CppBlock."""
def style_relevant_kids(self): """Return a list of kids in this block that are style-relevant.""" return [kid for kid in self.kids if kid.is_style_relevant()]
def sorted(self, enclosing_inclname): """Return a hopefully-sorted copy of this block. Implements --fixup.
When in doubt, this leaves the code unchanged. """
def pretty_sorted_includes(includes): """Return a new list containing the given includes, in order, with blank lines separating sections."""
keys = [inc.sort_key(enclosing_inclname) for inc in includes] if sorted(keys) == keys: return includes # if nothing is out of order, don't touch anything
output = []
current_section = None for (section, _), inc in sorted(zip(keys, includes)): if current_section isnotNoneand section != current_section:
output.append(OrdinaryCode(["\n"])) # blank line
output.append(inc)
current_section = section return output
def should_try_to_sort(includes): if"tests/style/BadIncludes"in enclosing_inclname: returnFalse# don't straighten the counterexample if any(inc.inclname in oddly_ordered_inclnames for inc in includes): returnFalse# don't sort batches containing odd includes if includes == sorted(
includes, key=lambda inc: inc.sort_key(enclosing_inclname)
): returnFalse# it's already sorted, avoid whitespace-only fixups returnTrue
# The content of the eventual output of this method.
output = []
# The current batch of includes to sort. This list only ever contains Include objects # and whitespace OrdinaryCode objects.
batch = []
def flush_batch(): """Sort the contents of `batch` and move it to `output`."""
assert all(
isinstance(item, Include) or (isinstance(item, OrdinaryCode) and"".join(item.lines).isspace()) for item in batch
)
# Here we throw away the blank lines. # `pretty_sorted_includes` puts them back.
includes = []
last_include_index = -1 for i, item in enumerate(batch): if isinstance(item, Include):
includes.append(item)
last_include_index = i
cutoff = last_include_index + 1
if should_try_to_sort(includes):
output.extend(pretty_sorted_includes(includes) + batch[cutoff:]) else:
output.extend(batch) del batch[:]
for kid in self.kids: if isinstance(kid, CppBlock):
flush_batch()
output.append(kid.sorted(enclosing_inclname)) elif isinstance(kid, Include):
batch.append(kid) else: assert isinstance(kid, OrdinaryCode) if kid.to_source().isspace():
batch.append(kid) else:
flush_batch()
output.append(kid)
flush_batch()
result = CppBlock()
result.start = self.start
result.end = self.end
result.kids = output return result
def to_source(self): return self.start + "".join(kid.to_source() for kid in self.kids) + self.end
class OrdinaryCode(object): """A list of lines of code that aren't #include/#if/#else/#endif lines."""
def __init__(self, lines=None):
self.lines = lines if lines isnotNoneelse []
def is_style_relevant(self): returnFalse
def to_source(self): return"".join(self.lines)
# A "snippet" is one of: # # * Include - representing an #include line # * CppBlock - a whole file or #if/#elif/#else block; contains a list of snippets # * OrdinaryCode - representing lines of non-#include-relevant code
def read_file(f):
block_stack = [CppBlock()]
# Extract the #include statements as a tree of snippets. for linenum, line in enumerate(f, start=1): if line.lstrip().startswith("#"): # Look for a |#include "..."| line.
m = re.match(r'(\s*#\s*include\s+)"([^"]*)"(.*)', line) if m isnotNone:
prefix, inclname, suffix = m.groups()
block_stack[-1].kids.append(
Include(prefix, inclname, suffix, linenum, is_system=False)
) continue
# Look for a |#include <...>| line.
m = re.match(r"(\s*#\s*include\s+)<([^>]*)>(.*)", line) if m isnotNone:
prefix, inclname, suffix = m.groups()
block_stack[-1].kids.append(
Include(prefix, inclname, suffix, linenum, is_system=True)
) continue
# Look for a |#{if,ifdef,ifndef}| line.
m = re.match(r"\s*#\s*(if|ifdef|ifndef)\b", line) if m isnotNone: # Open a new block.
new_block = CppBlock(line)
block_stack[-1].kids.append(new_block)
block_stack.append(new_block) continue
# Look for a |#{elif,else}| line.
m = re.match(r"\s*#\s*(elif|else)\b", line) if m isnotNone: # Close the current block, and open an adjacent one.
block_stack.pop()
new_block = CppBlock(line)
block_stack[-1].kids.append(new_block)
block_stack.append(new_block) continue
# Look for a |#endif| line.
m = re.match(r"\s*#\s*endif\b", line) if m isnotNone: # Close the current block.
block_stack.pop().end = line if len(block_stack) == 0: raise ValueError("#endif without #if at line " + str(linenum)) continue
# Otherwise, we have an ordinary line.
block_stack[-1].append_ordinary_line(line)
if len(block_stack) > 1: raise ValueError("unmatched #if") return block_stack[-1]
def check_file(
filename, inclname, file_kind, code, all_inclnames, included_h_inclnames
): def check_include_statement(include): """Check the style of a single #include statement."""
if include.is_system: # Check it is not a known local file (in which case it's probably a system header). if (
include.inclname in included_inclnames_to_ignore or include.inclname in all_inclnames
):
error(
filename,
include.linenum,
include.quote() + " should be included using", 'the #include "..." form',
)
# Check for system header which shouldn't be included directly. if (
include.inclname in wrapper_system_inclnames and wrapper_system_inclnames[include.inclname] != inclname
):
wrapper_inclname = wrapper_system_inclnames[include.inclname]
error(
filename,
include.linenum,
f"{include.quote()} should not be included directly, "
f'instead use "{wrapper_inclname}"',
) else:
msg = deprecated_inclnames.get(include.inclname) if msg:
error(
filename,
include.linenum,
include.quote() + " is deprecated: " + msg,
)
if file_kind == FileKind.H or file_kind == FileKind.INL_H:
msg = deprecated_inclnames_in_header.get(include.inclname) if msg and filename notin deprecated_inclnames_in_header_excludes:
error(
filename,
include.linenum,
include.quote() + " is deprecated: " + msg,
)
if include.inclname notin included_inclnames_to_ignore:
included_kind = FileKind.get(include.inclname)
# Check the #include path has the correct form. if include.inclname notin all_inclnames:
error(
filename,
include.linenum,
include.quote() + " is included using the wrong path;", "did you forget a prefix, or is the file not yet committed?",
)
# Record inclusions of .h files for cycle detection later. # (Exclude .tbl and .msg files.) elif included_kind == FileKind.H or included_kind == FileKind.INL_H:
included_h_inclnames.add(include.inclname)
# Check a H file doesn't #include an INL_H file. if file_kind == FileKind.H and included_kind == FileKind.INL_H:
error(
filename,
include.linenum, "vanilla header includes an inline-header file "
+ include.quote(),
)
# Check a file doesn't #include itself. (We do this here because the cycle # detection below doesn't detect this case.) if inclname == include.inclname:
error(filename, include.linenum, "the file includes itself")
def check_includes_order(include1, include2): """Check the ordering of two #include statements."""
if (
include1.inclname in oddly_ordered_inclnames or include2.inclname in oddly_ordered_inclnames
): return
section1 = include1.section(inclname)
section2 = include2.section(inclname) if (section1 > section2) or (
(section1 == section2) and (include1.inclname.lower() > include2.inclname.lower())
):
error(
filename,
str(include1.linenum) + ":" + str(include2.linenum),
include1.quote() + " should be included after " + include2.quote(),
)
# Check the extracted #include statements, both individually, and the ordering of # adjacent pairs that live in the same block. def pair_traverse(prev, this): if isinstance(this, Include):
check_include_statement(this) if isinstance(prev, Include):
check_includes_order(prev, this) else:
kids = this.style_relevant_kids() for prev2, this2 in zip([None] + kids[0:-1], kids):
pair_traverse(prev2, this2)
pair_traverse(None, code)
def find_cycles(all_inclnames, edges): """Find and draw any cycles."""
SCCs = tarjan(all_inclnames, edges)
# The various sorted() calls below ensure the output is deterministic.
def draw_SCC(c):
cset = set(c)
drawn = set()
def draw(v, indent):
out(" " * indent + ("-> "if indent else" ") + v) if v in drawn: return
drawn.add(v) for succ in sorted(edges[v]): if succ in cset:
draw(succ, indent + 1)
draw(sorted(c)[0], 0)
out("")
have_drawn_an_SCC = False for scc in sorted(SCCs): if len(scc) != 1: ifnot have_drawn_an_SCC:
error("(multiple files)", None, "header files form one or more cycles")
have_drawn_an_SCC = True
def strongconnect(v, index): # Set the depth index for v to the smallest unused index
vertex_index[v] = index
vertex_lowlink[v] = index
index += 1
S.append(v)
# Consider successors of v for w in E[v]: if w notin vertex_index: # Successor w has not yet been visited; recurse on it
index = strongconnect(w, index)
vertex_lowlink[v] = min(vertex_lowlink[v], vertex_lowlink[w]) elif w in S: # Successor w is in stack S and hence in the current SCC
vertex_lowlink[v] = min(vertex_lowlink[v], vertex_index[w])
# If v is a root node, pop the stack and generate an SCC if vertex_lowlink[v] == vertex_index[v]:
i = S.index(v)
scc = S[i:] del S[i:]
all_SCCs.append(scc)
return index
for v in V: if v notin vertex_index:
index = strongconnect(v, index)
return all_SCCs
def main(): if sys.argv[1:] == ["--fixup"]: # Sort #include directives in-place. Fixup mode doesn't solve # all possible silliness that the script checks for; it's just a # hack for the common case where renaming a header causes style # errors.
fixup = True elif sys.argv[1:] == []:
fixup = False else:
print( "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | unexpected command " "line options: " + repr(sys.argv[1:])
)
sys.exit(1)
ok = check_style(fixup)
if ok:
print("TEST-PASS | check_spidermonkey_style.py | ok") else:
print( "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
+ "actual output does not match expected output; diff is above."
)
print( "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
+ "Hint: If the problem is that you renamed a header, and many #includes "
+ "are no longer in alphabetical order, commit your work and then try "
+ "`check_spidermonkey_style.py --fixup`. "
+ "You need to commit first because --fixup modifies your files in place."
)
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.