API

Base Classes

class tctools.common.Tool(*args)

Tools base class.

argparse is done in the constructor, CLI arguments should be passed there.

Pass e.g. sys.args[1:] (skipping the script part of the arguments).

Parameters:

args – See set_arguments()

LOGGER_NAME: str | None = None
FILTER_DEFAULT: list[str]
CONFIG_KEY: str | None = None
PATH_VARIABLES: list[str] = []
classmethod get_argument_parser() ArgumentParser
classmethod set_arguments(parser)

Create application-specific arguments.

make_config() dict[str, Any]

Get configuration from possible files.

abstract run() int

Main tool execution.

get_logger()

Get logger for this class.

class tctools.common.TcTool(*args)

Base class for tools sharing TwinCAT functionality.

Pass e.g. sys.args[1:] (skipping the script part of the arguments).

Parameters:

args – See set_arguments()

PATH_VARIABLES: list[str] = ['target']
classmethod set_arguments(parser)

Create application-specific arguments.

classmethod set_main_argument(parser)

First argument that’s supplied.

Separate method to allow overriding it.

static get_xml_header(file: str) str | None

Get raw XML header as string.

get_xml_tree(path: str | Path) _ElementTree

Get parsed XML path.

classmethod find_files(targets: str | list[str], filters: None | list[str] = None, recursive: bool = True, skip_check: bool = False) dict[Path, list[Path]]

Find a set of files, based on one or more targets and an optional filter.

The entire set will never contain duplicate files

Returned dict looks like:

{
    "given pattern 1": [file_1, folder/file2, etc.],
    "given pattern 2": ... ,
}
find_target_files() Generator[Path, None, None]

Use argparse arguments to get a set of target files.

XML Sorter

class tctools.xml_sort.xml_sort_class.XmlSorter(*args)

Tool to sort XML files.

Use one instance for a sequence of files.

Pass e.g. sys.args[1:] (skipping the script part of the arguments).

Parameters:

args – See set_arguments()

LOGGER_NAME: str | None = 'xml_sorter'
FILTER_DEFAULT: list[str] = ['*.tsproj', '*.xti', '*.plcproj']
CONFIG_KEY: str | None = 'xml_sort'
classmethod set_arguments(parser)

Create application-specific arguments.

run() int

Main tool execution.

sort_file(path: str)

Sort a single path.

sort_node_recursively(node: _Element)

Sort a node and any sub-nodes, and their sub-nodes.

Sorting is done in-place, the object is passed in by reference.

static sort_attributes(node: _Element) bool

Sort the attributes of a node.

Returns:

True if any changes were really made

static get_node_sorting_key(node: _Element) str

Get the string by which sub-nodes will be sorted.

Sorting will be done on the literal node XML subtree string.

static get_tag(node: _Element) str

Get tag without URL prefix from node.

static get_attrib(node: _Element) dict[str, str]

Yield node attributes, with namespace stripped.

Formatter

class tctools.format.format_class.Formatter(*args)

Helper to check formatting in PLC files.

Instantiate once for a sequence of files.

Pass e.g. sys.args[1:] (skipping the script part of the arguments).

Parameters:

args – See set_arguments()

LOGGER_NAME: str | None = 'formatter'
FILTER_DEFAULT: list[str] = ['*.TcPOU', '*.TcGVL', '*.TcDUT']
CONFIG_KEY: str | None = 'format'
classmethod set_arguments(parser)

Create application-specific arguments.

classmethod register_rule(new_rule: type[FormattingRule])

Incorporate a new formatting rule (accounting for its priority).

run() int

Main tool execution.

format_file(path: str)

Format (or check) a specific path.

The path is read as text and code inside XML tags is detected manually. Other lines of XML remain untouched.

static split_code_segments(content: list[str])

Copy content, split into XML and code sections.

Function is a generator, each pair is yielded.

Note: line endings are not modified! I.e., segments should be appended together directly, without extra newlines.

Param:

File content as list

Returns:

list[Segment]

format_segment(content: list[str], kind: Kind)

Format a specific segment of code.

Parameters:
  • content – Text to reformat (changed in place!)

  • kind – type of the content

apply_rule(rule, content, kind: Kind | None = None)

Run a rule over some content and handle results.

class tctools.format.format_class.XmlMachine

Helper class to identify code bits inside an XML path.

parse(content: list[str])

Progress machine line by line.

class tctools.format.format_rules.FormattingRule(properties: OrderedDict)

TcFormatter rule base class.

Extend and implement this class to check for and correct a specific error/style/etc.

Variables:
  • PRIORITY – Lower priority means a rule gets applied earlier.

  • WHOLE_FILE – If True, rule is applied to the entire file instead of just code blocks.

PRIORITY = 100
WHOLE_FILE = False
property actual_indent_size: int

Tab width for style=tabs or indent size for style=spaces.

get_property(name: str, default: Any = None, value_type: type | None = None) Any

Get item from _properties, parsing as needed.

Parameters:
  • name

  • default – Value to dfault if name doesn’t exist

  • value_type – Class of the returned value (e.g. bool)

abstract format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

add_correction(message: str, line_nr: int)

Register a formatting correction.

See Correction.

consume_corrections() list[tuple[int, str]]

Return listed corrections and reset list.

class tctools.format.format_rules.FormatTabs(*args)

Check usage of tab character.

format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

class tctools.format.format_rules.FormatTrailingWhitespace(*args)

Remove trailing whitespace.

PRIORITY = 90
format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

class tctools.format.format_rules.FormatInsertFinalNewline(*args)

Asserting a final empty newline in a file.

format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

class tctools.format.format_rules.FormatEndOfLine(*args)

Asserting line endings are as expected.

WHOLE_FILE = True
PRIORITY = 50
format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

class tctools.format.format_rules.FormatVariablesAlign(*args)

Assert whitespace align in variable declarations.

Target formatting will create columns on the “:” and the “//” of comments.

PRIORITY = 110
format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

format_argument_list(content: list[str])

Format entire declaration section

class tctools.format.format_rules.FormatConditionalParentheses(*args)

Formatter to make uses of parentheses inside IF, CASE and WHILE consistent.

First regex is used to find potential corrections, which are then investigated by a loop to make sure parentheses remain matching and no essential parentheses are removed.

format(content: list[str], kind: Kind | None = None)

Fun rule to format text.

Parameters:
  • content – Text to format (changed in place!)

  • kind – Kind of content

static find_and_match_braces(text: str, brace_left: str = '(', brace_right: str = ')') tuple[int, int]

Step through braces in a string.

Note that levels can step into negative.

Returns:

tuple of (strpos, level), where strpos is the zero-index position of the brace itself and level is the nested level it indicates

Git Info

class tctools.git_info.git_info_class.GitInfo(*args)

Class to insert Git version info into a template.

We use template keys in the source file like {{key}}. This is done to avoid conflicts with TwinCAT source files. e.g. <key> might collide with XML brackets in $key the dollar sign is a key for string constants.

Pass e.g. sys.args[1:] (skipping the script part of the arguments).

Parameters:

args – See set_arguments()

LOGGER_NAME: str | None = 'git_info'
CONFIG_KEY: str | None = 'git_info'
PATH_VARIABLES: list[str] = ['template', 'tolerate_dirty']
classmethod set_arguments(parser)

Create application-specific arguments.

run() int

Produce an info file based on template.

This is largely a copy of https://github.com/RobertoRoos/git-substitute. This DRY violation is accepted to prevent a code dependency.

Patch PLC

class tctools.patch_plc.patch_plc_class.PatchPlc(*args)

Function code for PLc project patching.

Pass e.g. sys.args[1:] (skipping the script part of the arguments).

Parameters:

args – See set_arguments()

LOGGER_NAME: str | None = 'patch_plc'
FILTER_DEFAULT: list[str] = ['*.TcPOU', '*.TcGVL', '*.TcDUT', '*.TcGTLO', '*.TcIO', '*.TcTLEO', '*.TcTTO']
CONFIG_KEY: str | None = 'patch_plc'
classmethod set_arguments(parser)

Create application-specific arguments.

classmethod set_main_argument(parser)

First argument that’s supplied.

Separate method to allow overriding it.

run() int

Perform actual patching.

operation_merge(current_sources: FileItems, new_sources: dict[Path, FileItems]) int | None

See help info for merge.

operation_remove(current_sources: FileItems, new_sources: dict[Path, FileItems])

See help info for remove.

operation_reset(current_sources: FileItems, new_sources: dict[Path, FileItems])

See help info for reset.

determine_source_folders(source_files: Iterable[Path]) FileItems

Collect all folders (incl. intermediate folders) of files.

Also turn files into a neat relative path, w.r.t. the project file. Relies on self._project_file.

skip_file_duplicates(new_sources: FileItems, current_sources: FileItems)

Avoid duplicate file names (regardless of full path).

current_sources is modified in-place.

get_project_sources(tree) FileItems

Get all files and folders currently in a PLC project.

sources_to_remove(current: FileItems, new_sources: dict[Path, FileItems]) FileItems

From a set of sources and CLI input, determine the sources to remove.

Important: only the keys of new_sources are considered! I.e., the files to remove need not exist on the filesystem.

listens to self.args.recursive.

xml_add_sources(sources: FileItems)

Modify the files and folders elements in-place.

xml_remove_source(to_remove: FileItems)

Modify the files and folders elements in-place.

log_sources(source: FileItems, add: bool)

Log a set of sources in its entirety.

Logged a INFO level when dry or check (otherwise the output is kind of meaningless), otherwise at DEBUG.

static path_to_str(path: PurePath) str

Turn any path into a windows-path string.

This is useful mostly for the paths inside the PLC project XML.