Commit 91ad44eb05e46c5c8686dda5536bbc75532f9979
Committed by
GitHub
Merge pull request #1592 from m-holger/auto_job
Add detailed docstrings for main methods in `generate_auto_job`.
Showing
2 changed files
with
277 additions
and
11 deletions
generate_auto_job
| ... | ... | @@ -157,6 +157,19 @@ def write_file(filename): |
| 157 | 157 | |
| 158 | 158 | |
| 159 | 159 | class Main: |
| 160 | + """ | |
| 161 | + Main class to manage generation of files for QPDFJob. | |
| 162 | + | |
| 163 | + The class provides logic to determine changes in input or generated files, | |
| 164 | + update checksums, and facilitate file generation based on specified options. | |
| 165 | + It utilizes checksums to avoid unnecessary file regenerations and manages | |
| 166 | + source files, output destinations, and their checks in a build process. | |
| 167 | + | |
| 168 | + :ivar SOURCES: List of source files used as inputs. | |
| 169 | + :ivar DESTS: Dictionary mapping file identifiers to their output destinations. | |
| 170 | + :ivar SUMS: Filename of the checksum file for source and destination file | |
| 171 | + checksums. | |
| 172 | + """ | |
| 160 | 173 | # SOURCES is a list of source files whose contents are used by |
| 161 | 174 | # this program. If they change, we are out of date. |
| 162 | 175 | SOURCES = [ |
| ... | ... | @@ -209,6 +222,20 @@ class Main: |
| 209 | 222 | return parser.parse_args(args) |
| 210 | 223 | |
| 211 | 224 | def top(self, options): |
| 225 | + """ | |
| 226 | + Processes a configuration job file and generates an appropriate output | |
| 227 | + or performs checks based on the provided options. | |
| 228 | + | |
| 229 | + This function reads a 'job.yml' file to process configurations, generates | |
| 230 | + declarations for option tables, and updates configuration destinations | |
| 231 | + based on data from the job file. Depending on the mode specified in the | |
| 232 | + options, it checks for modified input hashes, generates outputs, or exits | |
| 233 | + with an appropriate message. | |
| 234 | + | |
| 235 | + :param options: The configuration options specifying the mode of operation | |
| 236 | + (e.g., 'check', 'generate') and other relevant settings. | |
| 237 | + :return: None | |
| 238 | + """ | |
| 212 | 239 | with open('job.yml', 'r') as f: |
| 213 | 240 | data = yaml.safe_load(f.read()) |
| 214 | 241 | # config_decls maps a config key from an option in "options" |
| ... | ... | @@ -238,6 +265,19 @@ class Main: |
| 238 | 265 | exit(f'{whoami} unknown mode') |
| 239 | 266 | |
| 240 | 267 | def get_hashes(self): |
| 268 | + """ | |
| 269 | + Calculates and retrieves the SHA-256 hashes of files from source and destination paths. | |
| 270 | + | |
| 271 | + Summary: | |
| 272 | + This method iterates over a collection of file paths from both source and | |
| 273 | + destination attributes, calculates the SHA-256 hash for each existing file, | |
| 274 | + and returns a dictionary containing the file paths and their corresponding | |
| 275 | + hashes. If a file is not found, it is skipped. | |
| 276 | + | |
| 277 | + :return: A dictionary where keys are file paths (as str) and values are their | |
| 278 | + SHA-256 hashes (as str). | |
| 279 | + :rtype: dict | |
| 280 | + """ | |
| 241 | 281 | hashes = {} |
| 242 | 282 | for i in sorted([*self.SOURCES, *self.DESTS.values()]): |
| 243 | 283 | m = hashlib.sha256() |
| ... | ... | @@ -250,6 +290,19 @@ class Main: |
| 250 | 290 | return hashes |
| 251 | 291 | |
| 252 | 292 | def check_hashes(self): |
| 293 | + """ | |
| 294 | + Compares the current hashes with previously stored hashes in a file and determines if they match. | |
| 295 | + | |
| 296 | + This method retrieves the current hashes using the `get_hashes` method, attempts to read | |
| 297 | + the stored hashes from a file, and compares the two. If there are mismatches or missing | |
| 298 | + entries in any direction, relevant messages are printed. The purpose is to validate | |
| 299 | + whether the current environment or configuration remains consistent with previous runs. | |
| 300 | + | |
| 301 | + :raises Exception: If an error occurs during file reading or processing. | |
| 302 | + :return: A boolean value indicating whether the current hashes match the previously | |
| 303 | + stored hashes. | |
| 304 | + :rtype: bool | |
| 305 | + """ | |
| 253 | 306 | hashes = self.get_hashes() |
| 254 | 307 | match = False |
| 255 | 308 | try: |
| ... | ... | @@ -280,6 +333,18 @@ class Main: |
| 280 | 333 | return match |
| 281 | 334 | |
| 282 | 335 | def update_hashes(self): |
| 336 | + """ | |
| 337 | + Updates the hash values and writes them to a specified file. | |
| 338 | + | |
| 339 | + This method retrieves a collection of hash values by calling the `get_hashes` | |
| 340 | + method. It then writes these hash values to a predefined file specified by | |
| 341 | + the `SUMS` attribute. The file will include a header line indicating the | |
| 342 | + source of the generated hashes. | |
| 343 | + | |
| 344 | + :raises IOError: If the file specified by `SUMS` cannot be opened | |
| 345 | + or written to. | |
| 346 | + :return: None | |
| 347 | + """ | |
| 283 | 348 | hashes = self.get_hashes() |
| 284 | 349 | with open(self.SUMS, 'w') as f: |
| 285 | 350 | print(f'# Generated by {whoami}', file=f) |
| ... | ... | @@ -287,6 +352,23 @@ class Main: |
| 287 | 352 | print(f'{k} {v}', file=f) |
| 288 | 353 | |
| 289 | 354 | def generate_doc(self, df, f, f_man): |
| 355 | + """ | |
| 356 | + Generates documentation and help-related functionalities for a given parser. | |
| 357 | + | |
| 358 | + This function processes input data to generate structured help content, associating | |
| 359 | + it with topics or options. It splits the large function operation into smaller, manageable | |
| 360 | + static sub-components, ensuring maintainability while dealing with large content. In addition | |
| 361 | + to generating help texts for topics and options, it formats and outputs content into | |
| 362 | + various formats including string outputs and man page style documentation. | |
| 363 | + | |
| 364 | + :param df: A file-like object from which content is read to generate topics | |
| 365 | + and option-based help content. | |
| 366 | + :param f: A writable file-like object where the generated static functions | |
| 367 | + and help configuration for the parser are written. | |
| 368 | + :param f_man: A writable file-like object where formatted manual page text | |
| 369 | + is generated. | |
| 370 | + :return: None | |
| 371 | + """ | |
| 290 | 372 | st_top = 0 |
| 291 | 373 | st_topic = 1 |
| 292 | 374 | st_option = 2 |
| ... | ... | @@ -315,6 +397,18 @@ class Main: |
| 315 | 397 | indent = ' ' * len(x) |
| 316 | 398 | |
| 317 | 399 | def append_long_text(line, topic): |
| 400 | + """ | |
| 401 | + Appends a line of text to a growing long text description for a specific topic. | |
| 402 | + The function processes lines, either appending them to the existing long text | |
| 403 | + or finalizing the long text for a topic if the line doesn't match the expected | |
| 404 | + indentation. Raises an error if a finalized long text is missing for a given | |
| 405 | + topic. Additionally, updates the collection of referenced topics if applicable. | |
| 406 | + | |
| 407 | + :param line: A string representing the current line of text being processed. | |
| 408 | + :param topic: A string representing the topic associated with the long text. | |
| 409 | + :return: A boolean indicating whether the long text for the topic has been | |
| 410 | + finalized. | |
| 411 | + """ | |
| 318 | 412 | nonlocal indent, long_text |
| 319 | 413 | if line == '\n': |
| 320 | 414 | long_text += '\n' |
| ... | ... | @@ -334,6 +428,20 @@ class Main: |
| 334 | 428 | return False |
| 335 | 429 | |
| 336 | 430 | def manify(text): |
| 431 | + """ | |
| 432 | + Transforms a given text into a format suitable for a manual page. | |
| 433 | + | |
| 434 | + This function processes the input text and modifies its formatting | |
| 435 | + to match the conventions typically used in manual pages. It converts | |
| 436 | + list items that start with '- ' into equivalent `.IP \\[bu]` formatted | |
| 437 | + entries and handles indented lines associated with such list items. | |
| 438 | + | |
| 439 | + :param text: The input plain text to be transformed for manual page | |
| 440 | + formatting. | |
| 441 | + :type text: str | |
| 442 | + :return: The modified text formatted for manual pages. | |
| 443 | + :rtype: str | |
| 444 | + """ | |
| 337 | 445 | lines = text.split('\n') |
| 338 | 446 | out = [] |
| 339 | 447 | last_was_item = False |
| ... | ... | @@ -447,6 +555,17 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 447 | 555 | ', '.join(self.options_without_help)) |
| 448 | 556 | |
| 449 | 557 | def generate(self, data): |
| 558 | + """ | |
| 559 | + Generates and writes various files associated with job configuration, initialization, schema, | |
| 560 | + documentation, and other related tasks. The method performs necessary validations, extracts | |
| 561 | + version information, processes job configurations, and prepares structured outputs for different | |
| 562 | + file types. It ensures completeness of help options and updates necessary data hashes. | |
| 563 | + | |
| 564 | + :param data: Input data required for generating and preparing files. | |
| 565 | + :type data: any | |
| 566 | + | |
| 567 | + :return: None | |
| 568 | + """ | |
| 450 | 569 | warn(f'{whoami}: regenerating auto job files') |
| 451 | 570 | self.validate(data) |
| 452 | 571 | |
| ... | ... | @@ -521,9 +640,23 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 521 | 640 | # DON'T ADD CODE TO generate AFTER update_hashes |
| 522 | 641 | |
| 523 | 642 | def handle_trivial(self, i, identifier, cfg, prefix, kind, v): |
| 524 | - # A "trivial" option is one whose handler does nothing other | |
| 525 | - # than to call the config method with the same name (switched | |
| 526 | - # to camelCase). | |
| 643 | + """ | |
| 644 | + Handle a "trivial" option by generating initialization and declaration statements for configuration methods. | |
| 645 | + A trivial option is one where the handler does nothing other than calling the | |
| 646 | + configuration method with the same name (switched to camelCase). | |
| 647 | + | |
| 648 | + The function processes different option types (`bare`, `required_parameter`, `optional_parameter`, | |
| 649 | + `required_choices`, `optional_choices`) and generates corresponding initialization code for adding | |
| 650 | + these options. It also generates or updates configuration method declarations as needed. | |
| 651 | + | |
| 652 | + :param i: Identifier of the option. | |
| 653 | + :param identifier: Name of the configuration method to be invoked. | |
| 654 | + :param cfg: Object representing the configuration context. | |
| 655 | + :param prefix: Prefix used for generating configuration method names. | |
| 656 | + :param kind: Type of the option (e.g., "bare", "required_parameter", etc.). | |
| 657 | + :param v: Additional value or information associated with specific types of options. | |
| 658 | + :return: None | |
| 659 | + """ | |
| 527 | 660 | decl_arg = 1 |
| 528 | 661 | decl_arg_optional = False |
| 529 | 662 | if kind == 'bare': |
| ... | ... | @@ -576,10 +709,29 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 576 | 709 | f'QPDF_DLL {config_prefix}* {identifier}();') |
| 577 | 710 | |
| 578 | 711 | def handle_flag(self, i, identifier, kind, v): |
| 579 | - # For flags that require manual handlers, declare the handler | |
| 580 | - # and register it. They have to be implemented manually in | |
| 581 | - # QPDFJob_argv.cc. You get compiler/linker errors for any | |
| 582 | - # missing methods. | |
| 712 | + """ | |
| 713 | + Handles flag processing and declaration for commands that require custom | |
| 714 | + manual handlers. Depending on the type of the flag, it declares the | |
| 715 | + appropriate handler method and registers it. They have to be implemented | |
| 716 | + manually in QPDFJob_argv.cc. You get compiler/linker errors for any | |
| 717 | + missing methods.This function associates the flag identifier with specific | |
| 718 | + handlers for various flag types such as bare, parameter-based, or | |
| 719 | + choice-based flags. | |
| 720 | + | |
| 721 | + :param i: The command-line flag or parameter. | |
| 722 | + :type i: str | |
| 723 | + :param identifier: Name used to identify the flag handler method. | |
| 724 | + :type identifier: str | |
| 725 | + :param kind: The type of flag. Supported types are 'bare', | |
| 726 | + 'required_parameter', 'optional_parameter', | |
| 727 | + 'required_choices', or 'optional_choices'. | |
| 728 | + :type kind: str | |
| 729 | + :param v: Additional value or information required for choices or | |
| 730 | + parameter flags; unused in the case of 'bare' flags. | |
| 731 | + :type v: str | |
| 732 | + :return: None | |
| 733 | + :rtype: None | |
| 734 | + """ | |
| 583 | 735 | if kind == 'bare': |
| 584 | 736 | self.decls.append(f'void {identifier}();') |
| 585 | 737 | self.init.append(f'this->ap.addBare("{i}", ' |
| ... | ... | @@ -605,6 +757,20 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 605 | 757 | f', false, {v}_choices);') |
| 606 | 758 | |
| 607 | 759 | def prepare(self, data): |
| 760 | + """ | |
| 761 | + Prepare the internal configuration of options and handlers for argument parsing. | |
| 762 | + | |
| 763 | + This function sets up various internal data structures essential for managing | |
| 764 | + argv handlers, option table declarations, initialization procedures, and other | |
| 765 | + required data for parsing command-line arguments. It also assists in registering | |
| 766 | + handlers, generating constants, and organizing choices for easier use in the | |
| 767 | + argument parsing process. | |
| 768 | + | |
| 769 | + :param data: The input dictionary containing configuration for options, choices, | |
| 770 | + and other relevant details to initialize argument parsing. | |
| 771 | + :type data: dict | |
| 772 | + :return: None | |
| 773 | + """ | |
| 608 | 774 | self.decls = [] # argv handler declarations |
| 609 | 775 | self.init = [] # initialize arg parsing code |
| 610 | 776 | self.json_decls = [] # json handler declarations |
| ... | ... | @@ -613,9 +779,20 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 613 | 779 | self.by_table = {} # table information by name for easy lookup |
| 614 | 780 | |
| 615 | 781 | def add_jdata(flag, table, details): |
| 616 | - # Keep track of each flag and where it appears so we can | |
| 617 | - # check consistency between the json information and the | |
| 618 | - # options section. | |
| 782 | + """ | |
| 783 | + Add JSON data to track flags and their respective details and table associations. | |
| 784 | + | |
| 785 | + This function manages the relationship between a given flag and the | |
| 786 | + tables it references. It also ensures that appropriate options are | |
| 787 | + added if the table specified is "help". For other tables, it maintains | |
| 788 | + the corresponding details against the flag in the JSON structure. | |
| 789 | + | |
| 790 | + :param flag: A string identifying a specific flag for tracking. | |
| 791 | + :param table: A string specifying the table the flag is associated with. | |
| 792 | + :param details: A dictionary containing details associated with the given table | |
| 793 | + for the specified flag. | |
| 794 | + :return: None | |
| 795 | + """ | |
| 619 | 796 | nonlocal self |
| 620 | 797 | if table == 'help': |
| 621 | 798 | self.help_options.add(f'--{flag}') |
| ... | ... | @@ -730,6 +907,16 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 730 | 907 | self.decls.append(f'void {identifier}();') |
| 731 | 908 | |
| 732 | 909 | def handle_json_trivial(self, flag_key, fdata): |
| 910 | + """ | |
| 911 | + Handles JSON configuration based on the specified flag, data, and the associated | |
| 912 | + table configuration. Determines the type of operation based on the kind of entry | |
| 913 | + and appends the appropriate initialization string to the `json_init`. | |
| 914 | + | |
| 915 | + :param flag_key: A string representing the key used to modify the configuration. | |
| 916 | + :param fdata: A dictionary containing table information and other associated | |
| 917 | + data necessary for configuration handling. | |
| 918 | + :return: None | |
| 919 | + """ | |
| 733 | 920 | config = None |
| 734 | 921 | for t, [kind, v] in fdata['tables'].items(): |
| 735 | 922 | # We have determined that all tables, if multiple, have |
| ... | ... | @@ -758,6 +945,14 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 758 | 945 | f' {{ {config}->{flag_key}(p); }});') |
| 759 | 946 | |
| 760 | 947 | def handle_json_manual(self, path): |
| 948 | + """ | |
| 949 | + Processes a given file path to create a method name in camelCase format | |
| 950 | + and appends corresponding declarations and invocation to internal lists. | |
| 951 | + | |
| 952 | + :param path: The file path to process as a string | |
| 953 | + :type path: str | |
| 954 | + :return: None | |
| 955 | + """ | |
| 761 | 956 | method = re.sub(r'\.([a-zA-Z0-9])', |
| 762 | 957 | lambda x: x.group(1).upper(), |
| 763 | 958 | f'setup{path}') |
| ... | ... | @@ -874,6 +1069,27 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 874 | 1069 | return schema_value |
| 875 | 1070 | |
| 876 | 1071 | def generate_schema(self, data): |
| 1072 | + """ | |
| 1073 | + Generate and validate a JSON schema based on the given data. | |
| 1074 | + | |
| 1075 | + This method ensures that every command-line option is represented | |
| 1076 | + in the JSON schema described in the `data` parameter. It checks | |
| 1077 | + for consistency between the defined command-line options and the | |
| 1078 | + JSON section of the input data. If any option is missing or | |
| 1079 | + inconsistent, an exception is raised. The method builds a schema | |
| 1080 | + by incorporating help information provided in the data, and it | |
| 1081 | + registers JSON handlers that correspond with the created schema. | |
| 1082 | + | |
| 1083 | + :param data: A dictionary containing the JSON section and option | |
| 1084 | + information necessary for schema generation and | |
| 1085 | + validation. | |
| 1086 | + - `data['json']`: Dictionary describing the JSON | |
| 1087 | + schema structure. | |
| 1088 | + :return: None | |
| 1089 | + :raises Exception: If there is a mismatch between expected | |
| 1090 | + options and options specified in the JSON | |
| 1091 | + schema. | |
| 1092 | + """ | |
| 877 | 1093 | # Check to make sure that every command-line option is |
| 878 | 1094 | # represented in data['json']. Build a list of options that we |
| 879 | 1095 | # 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. |
| 914 | 1130 | str(set(expected.keys()) - options_seen)) |
| 915 | 1131 | |
| 916 | 1132 | def check_keys(self, what, d, exp): |
| 1133 | + """ | |
| 1134 | + Validates that the provided dictionary has the expected set of keys. If the | |
| 1135 | + `d` parameter is not a dictionary or contains unknown keys that are not | |
| 1136 | + in the `exp` set, the program will terminate with an error message. | |
| 1137 | + | |
| 1138 | + :param what: A descriptive string indicating the purpose of the dictionary. | |
| 1139 | + Used in error messages to provide context. | |
| 1140 | + :type what: str | |
| 1141 | + :param d: The dictionary to be inspected for its keys. | |
| 1142 | + :type d: dict | |
| 1143 | + :param exp: A set of expected keys that `d` should adhere to. | |
| 1144 | + :type exp: set | |
| 1145 | + :return: None. Terminates the program with an error message if the | |
| 1146 | + validation fails. | |
| 1147 | + """ | |
| 917 | 1148 | if not isinstance(d, dict): |
| 918 | 1149 | exit(f'{what} is not a dictionary') |
| 919 | 1150 | actual = set(d.keys()) |
| ... | ... | @@ -922,6 +1153,22 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 922 | 1153 | exit(f'{what}: unknown keys = {extra}') |
| 923 | 1154 | |
| 924 | 1155 | def validate(self, data): |
| 1156 | + """ | |
| 1157 | + Validates the given data against a set of required keys for proper structure. Checks are | |
| 1158 | + performed for both the top-level keys and the keys within the 'options' list in the data. | |
| 1159 | + This ensures that the data has the required configuration necessary for processing. | |
| 1160 | + | |
| 1161 | + :param data: The input data to be validated. It is expected to be a dictionary containing | |
| 1162 | + the keys 'choices', 'options', and 'json'. The 'options' key must contain a list | |
| 1163 | + whose elements are dictionaries with specific required keys. | |
| 1164 | + :type data: dict | |
| 1165 | + :return: None. The function does not return any value but may raise exceptions if the | |
| 1166 | + validation fails. | |
| 1167 | + :rtype: None | |
| 1168 | + :raises ValueError: If any required keys are missing in the provided data for either the | |
| 1169 | + top-level or within the 'options' list. | |
| 1170 | + :raises TypeError: If the structure or type of the input 'data' is incorrect. | |
| 1171 | + """ | |
| 925 | 1172 | self.check_keys('top', data, set( |
| 926 | 1173 | ['choices', 'options', 'json'])) |
| 927 | 1174 | for o in data['options']: |
| ... | ... | @@ -932,6 +1179,25 @@ A complete manual can be found at https://qpdf.readthedocs.io. |
| 932 | 1179 | 'required_choices', 'optional_choices'])) |
| 933 | 1180 | |
| 934 | 1181 | def to_identifier(self, label, prefix, const): |
| 1182 | + """ | |
| 1183 | + Converts a given label into a valid identifier by replacing invalid characters | |
| 1184 | + and applying formatting rules. The method ensures that the resulting identifier | |
| 1185 | + conforms to naming conventions, optionally prepending a prefix and enforcing | |
| 1186 | + uppercase for constants. | |
| 1187 | + | |
| 1188 | + :param label: The input label string that needs to be converted into an | |
| 1189 | + identifier. | |
| 1190 | + :type label: str | |
| 1191 | + :param prefix: An optional prefix to prepend to the identifier. If not | |
| 1192 | + provided, no prefix is added. | |
| 1193 | + :type prefix: str | |
| 1194 | + :param const: Indicates whether the output identifier should be treated as | |
| 1195 | + a constant. If True, the identifier is converted to uppercase and prefixed. | |
| 1196 | + :type const: bool | |
| 1197 | + :return: A valid identifier string generated from the input label based on the | |
| 1198 | + provided parameters. | |
| 1199 | + :rtype: str | |
| 1200 | + """ | |
| 935 | 1201 | identifier = re.sub(r'[^a-zA-Z0-9]', '_', label) |
| 936 | 1202 | if const: |
| 937 | 1203 | identifier = f'{prefix}_{identifier.upper()}' | ... | ... |
job.sums
| 1 | 1 | # Generated by generate_auto_job |
| 2 | 2 | CMakeLists.txt 18214e276670dc8beb2ab83f789c6d94941bc92b199b353f3943024cfd41d3bc |
| 3 | -generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 | |
| 3 | +generate_auto_job 280b75d5307c537385a75ec588493496cfb0bc754d48c34ca8c42bbc55dd717b | |
| 4 | 4 | include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 |
| 5 | 5 | include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 |
| 6 | 6 | include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 | ... | ... |