From bd870b6cb9bce995cc99c71f39b80bcbacd59417 Mon Sep 17 00:00:00 2001 From: m-holger Date: Tue, 18 Nov 2025 12:45:06 +0000 Subject: [PATCH] Add detailed docstrings for main methods in `generate_auto_job`. --- generate_auto_job | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- job.sums | 2 +- 2 files changed, 277 insertions(+), 11 deletions(-) diff --git a/generate_auto_job b/generate_auto_job index d4143d5..394eac7 100755 --- a/generate_auto_job +++ b/generate_auto_job @@ -157,6 +157,19 @@ def write_file(filename): class Main: + """ + Main class to manage generation of files for QPDFJob. + + The class provides logic to determine changes in input or generated files, + update checksums, and facilitate file generation based on specified options. + It utilizes checksums to avoid unnecessary file regenerations and manages + source files, output destinations, and their checks in a build process. + + :ivar SOURCES: List of source files used as inputs. + :ivar DESTS: Dictionary mapping file identifiers to their output destinations. + :ivar SUMS: Filename of the checksum file for source and destination file + checksums. + """ # SOURCES is a list of source files whose contents are used by # this program. If they change, we are out of date. SOURCES = [ @@ -209,6 +222,20 @@ class Main: return parser.parse_args(args) def top(self, options): + """ + Processes a configuration job file and generates an appropriate output + or performs checks based on the provided options. + + This function reads a 'job.yml' file to process configurations, generates + declarations for option tables, and updates configuration destinations + based on data from the job file. Depending on the mode specified in the + options, it checks for modified input hashes, generates outputs, or exits + with an appropriate message. + + :param options: The configuration options specifying the mode of operation + (e.g., 'check', 'generate') and other relevant settings. + :return: None + """ with open('job.yml', 'r') as f: data = yaml.safe_load(f.read()) # config_decls maps a config key from an option in "options" @@ -238,6 +265,19 @@ class Main: exit(f'{whoami} unknown mode') def get_hashes(self): + """ + Calculates and retrieves the SHA-256 hashes of files from source and destination paths. + + Summary: + This method iterates over a collection of file paths from both source and + destination attributes, calculates the SHA-256 hash for each existing file, + and returns a dictionary containing the file paths and their corresponding + hashes. If a file is not found, it is skipped. + + :return: A dictionary where keys are file paths (as str) and values are their + SHA-256 hashes (as str). + :rtype: dict + """ hashes = {} for i in sorted([*self.SOURCES, *self.DESTS.values()]): m = hashlib.sha256() @@ -250,6 +290,19 @@ class Main: return hashes def check_hashes(self): + """ + Compares the current hashes with previously stored hashes in a file and determines if they match. + + This method retrieves the current hashes using the `get_hashes` method, attempts to read + the stored hashes from a file, and compares the two. If there are mismatches or missing + entries in any direction, relevant messages are printed. The purpose is to validate + whether the current environment or configuration remains consistent with previous runs. + + :raises Exception: If an error occurs during file reading or processing. + :return: A boolean value indicating whether the current hashes match the previously + stored hashes. + :rtype: bool + """ hashes = self.get_hashes() match = False try: @@ -280,6 +333,18 @@ class Main: return match def update_hashes(self): + """ + Updates the hash values and writes them to a specified file. + + This method retrieves a collection of hash values by calling the `get_hashes` + method. It then writes these hash values to a predefined file specified by + the `SUMS` attribute. The file will include a header line indicating the + source of the generated hashes. + + :raises IOError: If the file specified by `SUMS` cannot be opened + or written to. + :return: None + """ hashes = self.get_hashes() with open(self.SUMS, 'w') as f: print(f'# Generated by {whoami}', file=f) @@ -287,6 +352,23 @@ class Main: print(f'{k} {v}', file=f) def generate_doc(self, df, f, f_man): + """ + Generates documentation and help-related functionalities for a given parser. + + This function processes input data to generate structured help content, associating + it with topics or options. It splits the large function operation into smaller, manageable + static sub-components, ensuring maintainability while dealing with large content. In addition + to generating help texts for topics and options, it formats and outputs content into + various formats including string outputs and man page style documentation. + + :param df: A file-like object from which content is read to generate topics + and option-based help content. + :param f: A writable file-like object where the generated static functions + and help configuration for the parser are written. + :param f_man: A writable file-like object where formatted manual page text + is generated. + :return: None + """ st_top = 0 st_topic = 1 st_option = 2 @@ -315,6 +397,18 @@ class Main: indent = ' ' * len(x) def append_long_text(line, topic): + """ + Appends a line of text to a growing long text description for a specific topic. + The function processes lines, either appending them to the existing long text + or finalizing the long text for a topic if the line doesn't match the expected + indentation. Raises an error if a finalized long text is missing for a given + topic. Additionally, updates the collection of referenced topics if applicable. + + :param line: A string representing the current line of text being processed. + :param topic: A string representing the topic associated with the long text. + :return: A boolean indicating whether the long text for the topic has been + finalized. + """ nonlocal indent, long_text if line == '\n': long_text += '\n' @@ -334,6 +428,20 @@ class Main: return False def manify(text): + """ + Transforms a given text into a format suitable for a manual page. + + This function processes the input text and modifies its formatting + to match the conventions typically used in manual pages. It converts + list items that start with '- ' into equivalent `.IP \\[bu]` formatted + entries and handles indented lines associated with such list items. + + :param text: The input plain text to be transformed for manual page + formatting. + :type text: str + :return: The modified text formatted for manual pages. + :rtype: str + """ lines = text.split('\n') out = [] last_was_item = False @@ -447,6 +555,17 @@ A complete manual can be found at https://qpdf.readthedocs.io. ', '.join(self.options_without_help)) def generate(self, data): + """ + Generates and writes various files associated with job configuration, initialization, schema, + documentation, and other related tasks. The method performs necessary validations, extracts + version information, processes job configurations, and prepares structured outputs for different + file types. It ensures completeness of help options and updates necessary data hashes. + + :param data: Input data required for generating and preparing files. + :type data: any + + :return: None + """ warn(f'{whoami}: regenerating auto job files') self.validate(data) @@ -521,9 +640,23 @@ A complete manual can be found at https://qpdf.readthedocs.io. # DON'T ADD CODE TO generate AFTER update_hashes def handle_trivial(self, i, identifier, cfg, prefix, kind, v): - # A "trivial" option is one whose handler does nothing other - # than to call the config method with the same name (switched - # to camelCase). + """ + Handle a "trivial" option by generating initialization and declaration statements for configuration methods. + A trivial option is one where the handler does nothing other than calling the + configuration method with the same name (switched to camelCase). + + The function processes different option types (`bare`, `required_parameter`, `optional_parameter`, + `required_choices`, `optional_choices`) and generates corresponding initialization code for adding + these options. It also generates or updates configuration method declarations as needed. + + :param i: Identifier of the option. + :param identifier: Name of the configuration method to be invoked. + :param cfg: Object representing the configuration context. + :param prefix: Prefix used for generating configuration method names. + :param kind: Type of the option (e.g., "bare", "required_parameter", etc.). + :param v: Additional value or information associated with specific types of options. + :return: None + """ decl_arg = 1 decl_arg_optional = False if kind == 'bare': @@ -576,10 +709,29 @@ A complete manual can be found at https://qpdf.readthedocs.io. f'QPDF_DLL {config_prefix}* {identifier}();') def handle_flag(self, i, identifier, kind, v): - # For flags that require manual handlers, declare the handler - # and register it. They have to be implemented manually in - # QPDFJob_argv.cc. You get compiler/linker errors for any - # missing methods. + """ + Handles flag processing and declaration for commands that require custom + manual handlers. Depending on the type of the flag, it declares the + appropriate handler method and registers it. They have to be implemented + manually in QPDFJob_argv.cc. You get compiler/linker errors for any + missing methods.This function associates the flag identifier with specific + handlers for various flag types such as bare, parameter-based, or + choice-based flags. + + :param i: The command-line flag or parameter. + :type i: str + :param identifier: Name used to identify the flag handler method. + :type identifier: str + :param kind: The type of flag. Supported types are 'bare', + 'required_parameter', 'optional_parameter', + 'required_choices', or 'optional_choices'. + :type kind: str + :param v: Additional value or information required for choices or + parameter flags; unused in the case of 'bare' flags. + :type v: str + :return: None + :rtype: None + """ if kind == 'bare': self.decls.append(f'void {identifier}();') self.init.append(f'this->ap.addBare("{i}", ' @@ -605,6 +757,20 @@ A complete manual can be found at https://qpdf.readthedocs.io. f', false, {v}_choices);') def prepare(self, data): + """ + Prepare the internal configuration of options and handlers for argument parsing. + + This function sets up various internal data structures essential for managing + argv handlers, option table declarations, initialization procedures, and other + required data for parsing command-line arguments. It also assists in registering + handlers, generating constants, and organizing choices for easier use in the + argument parsing process. + + :param data: The input dictionary containing configuration for options, choices, + and other relevant details to initialize argument parsing. + :type data: dict + :return: None + """ self.decls = [] # argv handler declarations self.init = [] # initialize arg parsing code self.json_decls = [] # json handler declarations @@ -613,9 +779,20 @@ A complete manual can be found at https://qpdf.readthedocs.io. self.by_table = {} # table information by name for easy lookup def add_jdata(flag, table, details): - # Keep track of each flag and where it appears so we can - # check consistency between the json information and the - # options section. + """ + Add JSON data to track flags and their respective details and table associations. + + This function manages the relationship between a given flag and the + tables it references. It also ensures that appropriate options are + added if the table specified is "help". For other tables, it maintains + the corresponding details against the flag in the JSON structure. + + :param flag: A string identifying a specific flag for tracking. + :param table: A string specifying the table the flag is associated with. + :param details: A dictionary containing details associated with the given table + for the specified flag. + :return: None + """ nonlocal self if table == 'help': self.help_options.add(f'--{flag}') @@ -730,6 +907,16 @@ A complete manual can be found at https://qpdf.readthedocs.io. self.decls.append(f'void {identifier}();') def handle_json_trivial(self, flag_key, fdata): + """ + Handles JSON configuration based on the specified flag, data, and the associated + table configuration. Determines the type of operation based on the kind of entry + and appends the appropriate initialization string to the `json_init`. + + :param flag_key: A string representing the key used to modify the configuration. + :param fdata: A dictionary containing table information and other associated + data necessary for configuration handling. + :return: None + """ config = None for t, [kind, v] in fdata['tables'].items(): # We have determined that all tables, if multiple, have @@ -758,6 +945,14 @@ A complete manual can be found at https://qpdf.readthedocs.io. f' {{ {config}->{flag_key}(p); }});') def handle_json_manual(self, path): + """ + Processes a given file path to create a method name in camelCase format + and appends corresponding declarations and invocation to internal lists. + + :param path: The file path to process as a string + :type path: str + :return: None + """ method = re.sub(r'\.([a-zA-Z0-9])', lambda x: x.group(1).upper(), f'setup{path}') @@ -874,6 +1069,27 @@ A complete manual can be found at https://qpdf.readthedocs.io. return schema_value def generate_schema(self, data): + """ + Generate and validate a JSON schema based on the given data. + + This method ensures that every command-line option is represented + in the JSON schema described in the `data` parameter. It checks + for consistency between the defined command-line options and the + JSON section of the input data. If any option is missing or + inconsistent, an exception is raised. The method builds a schema + by incorporating help information provided in the data, and it + registers JSON handlers that correspond with the created schema. + + :param data: A dictionary containing the JSON section and option + information necessary for schema generation and + validation. + - `data['json']`: Dictionary describing the JSON + schema structure. + :return: None + :raises Exception: If there is a mismatch between expected + options and options specified in the JSON + schema. + """ # Check to make sure that every command-line option is # represented in data['json']. Build a list of options that we # expect. If an option appears once, we just expect to see it @@ -914,6 +1130,21 @@ A complete manual can be found at https://qpdf.readthedocs.io. str(set(expected.keys()) - options_seen)) def check_keys(self, what, d, exp): + """ + Validates that the provided dictionary has the expected set of keys. If the + `d` parameter is not a dictionary or contains unknown keys that are not + in the `exp` set, the program will terminate with an error message. + + :param what: A descriptive string indicating the purpose of the dictionary. + Used in error messages to provide context. + :type what: str + :param d: The dictionary to be inspected for its keys. + :type d: dict + :param exp: A set of expected keys that `d` should adhere to. + :type exp: set + :return: None. Terminates the program with an error message if the + validation fails. + """ if not isinstance(d, dict): exit(f'{what} is not a dictionary') actual = set(d.keys()) @@ -922,6 +1153,22 @@ A complete manual can be found at https://qpdf.readthedocs.io. exit(f'{what}: unknown keys = {extra}') def validate(self, data): + """ + Validates the given data against a set of required keys for proper structure. Checks are + performed for both the top-level keys and the keys within the 'options' list in the data. + This ensures that the data has the required configuration necessary for processing. + + :param data: The input data to be validated. It is expected to be a dictionary containing + the keys 'choices', 'options', and 'json'. The 'options' key must contain a list + whose elements are dictionaries with specific required keys. + :type data: dict + :return: None. The function does not return any value but may raise exceptions if the + validation fails. + :rtype: None + :raises ValueError: If any required keys are missing in the provided data for either the + top-level or within the 'options' list. + :raises TypeError: If the structure or type of the input 'data' is incorrect. + """ self.check_keys('top', data, set( ['choices', 'options', 'json'])) for o in data['options']: @@ -932,6 +1179,25 @@ A complete manual can be found at https://qpdf.readthedocs.io. 'required_choices', 'optional_choices'])) def to_identifier(self, label, prefix, const): + """ + Converts a given label into a valid identifier by replacing invalid characters + and applying formatting rules. The method ensures that the resulting identifier + conforms to naming conventions, optionally prepending a prefix and enforcing + uppercase for constants. + + :param label: The input label string that needs to be converted into an + identifier. + :type label: str + :param prefix: An optional prefix to prepend to the identifier. If not + provided, no prefix is added. + :type prefix: str + :param const: Indicates whether the output identifier should be treated as + a constant. If True, the identifier is converted to uppercase and prefixed. + :type const: bool + :return: A valid identifier string generated from the input label based on the + provided parameters. + :rtype: str + """ identifier = re.sub(r'[^a-zA-Z0-9]', '_', label) if const: identifier = f'{prefix}_{identifier.upper()}' diff --git a/job.sums b/job.sums index 85931ae..03c5f64 100644 --- a/job.sums +++ b/job.sums @@ -1,6 +1,6 @@ # Generated by generate_auto_job CMakeLists.txt 18214e276670dc8beb2ab83f789c6d94941bc92b199b353f3943024cfd41d3bc -generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 +generate_auto_job 280b75d5307c537385a75ec588493496cfb0bc754d48c34ca8c42bbc55dd717b include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 -- libgit2 0.21.4