Commit bd870b6cb9bce995cc99c71f39b80bcbacd59417

Authored by m-holger
1 parent 8120a444

Add detailed docstrings for main methods in `generate_auto_job`.

Enhance clarity and maintainability by introducing comprehensive (AI generated) docstrings for methods in the `Main` class, detailing functionality, parameters, and return types.
Showing 2 changed files with 277 additions and 11 deletions
generate_auto_job
@@ -157,6 +157,19 @@ def write_file(filename): @@ -157,6 +157,19 @@ def write_file(filename):
157 157
158 158
159 class Main: 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 # SOURCES is a list of source files whose contents are used by 173 # SOURCES is a list of source files whose contents are used by
161 # this program. If they change, we are out of date. 174 # this program. If they change, we are out of date.
162 SOURCES = [ 175 SOURCES = [
@@ -209,6 +222,20 @@ class Main: @@ -209,6 +222,20 @@ class Main:
209 return parser.parse_args(args) 222 return parser.parse_args(args)
210 223
211 def top(self, options): 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 with open('job.yml', 'r') as f: 239 with open('job.yml', 'r') as f:
213 data = yaml.safe_load(f.read()) 240 data = yaml.safe_load(f.read())
214 # config_decls maps a config key from an option in "options" 241 # config_decls maps a config key from an option in "options"
@@ -238,6 +265,19 @@ class Main: @@ -238,6 +265,19 @@ class Main:
238 exit(f'{whoami} unknown mode') 265 exit(f'{whoami} unknown mode')
239 266
240 def get_hashes(self): 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 hashes = {} 281 hashes = {}
242 for i in sorted([*self.SOURCES, *self.DESTS.values()]): 282 for i in sorted([*self.SOURCES, *self.DESTS.values()]):
243 m = hashlib.sha256() 283 m = hashlib.sha256()
@@ -250,6 +290,19 @@ class Main: @@ -250,6 +290,19 @@ class Main:
250 return hashes 290 return hashes
251 291
252 def check_hashes(self): 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 hashes = self.get_hashes() 306 hashes = self.get_hashes()
254 match = False 307 match = False
255 try: 308 try:
@@ -280,6 +333,18 @@ class Main: @@ -280,6 +333,18 @@ class Main:
280 return match 333 return match
281 334
282 def update_hashes(self): 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 hashes = self.get_hashes() 348 hashes = self.get_hashes()
284 with open(self.SUMS, 'w') as f: 349 with open(self.SUMS, 'w') as f:
285 print(f'# Generated by {whoami}', file=f) 350 print(f'# Generated by {whoami}', file=f)
@@ -287,6 +352,23 @@ class Main: @@ -287,6 +352,23 @@ class Main:
287 print(f'{k} {v}', file=f) 352 print(f'{k} {v}', file=f)
288 353
289 def generate_doc(self, df, f, f_man): 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 st_top = 0 372 st_top = 0
291 st_topic = 1 373 st_topic = 1
292 st_option = 2 374 st_option = 2
@@ -315,6 +397,18 @@ class Main: @@ -315,6 +397,18 @@ class Main:
315 indent = ' ' * len(x) 397 indent = ' ' * len(x)
316 398
317 def append_long_text(line, topic): 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 nonlocal indent, long_text 412 nonlocal indent, long_text
319 if line == '\n': 413 if line == '\n':
320 long_text += '\n' 414 long_text += '\n'
@@ -334,6 +428,20 @@ class Main: @@ -334,6 +428,20 @@ class Main:
334 return False 428 return False
335 429
336 def manify(text): 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 lines = text.split('\n') 445 lines = text.split('\n')
338 out = [] 446 out = []
339 last_was_item = False 447 last_was_item = False
@@ -447,6 +555,17 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -447,6 +555,17 @@ A complete manual can be found at https://qpdf.readthedocs.io.
447 ', '.join(self.options_without_help)) 555 ', '.join(self.options_without_help))
448 556
449 def generate(self, data): 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 warn(f'{whoami}: regenerating auto job files') 569 warn(f'{whoami}: regenerating auto job files')
451 self.validate(data) 570 self.validate(data)
452 571
@@ -521,9 +640,23 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -521,9 +640,23 @@ A complete manual can be found at https://qpdf.readthedocs.io.
521 # DON'T ADD CODE TO generate AFTER update_hashes 640 # DON'T ADD CODE TO generate AFTER update_hashes
522 641
523 def handle_trivial(self, i, identifier, cfg, prefix, kind, v): 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 decl_arg = 1 660 decl_arg = 1
528 decl_arg_optional = False 661 decl_arg_optional = False
529 if kind == 'bare': 662 if kind == 'bare':
@@ -576,10 +709,29 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -576,10 +709,29 @@ A complete manual can be found at https://qpdf.readthedocs.io.
576 f'QPDF_DLL {config_prefix}* {identifier}();') 709 f'QPDF_DLL {config_prefix}* {identifier}();')
577 710
578 def handle_flag(self, i, identifier, kind, v): 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 if kind == 'bare': 735 if kind == 'bare':
584 self.decls.append(f'void {identifier}();') 736 self.decls.append(f'void {identifier}();')
585 self.init.append(f'this->ap.addBare("{i}", ' 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,6 +757,20 @@ A complete manual can be found at https://qpdf.readthedocs.io.
605 f', false, {v}_choices);') 757 f', false, {v}_choices);')
606 758
607 def prepare(self, data): 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 self.decls = [] # argv handler declarations 774 self.decls = [] # argv handler declarations
609 self.init = [] # initialize arg parsing code 775 self.init = [] # initialize arg parsing code
610 self.json_decls = [] # json handler declarations 776 self.json_decls = [] # json handler declarations
@@ -613,9 +779,20 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -613,9 +779,20 @@ A complete manual can be found at https://qpdf.readthedocs.io.
613 self.by_table = {} # table information by name for easy lookup 779 self.by_table = {} # table information by name for easy lookup
614 780
615 def add_jdata(flag, table, details): 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 nonlocal self 796 nonlocal self
620 if table == 'help': 797 if table == 'help':
621 self.help_options.add(f'--{flag}') 798 self.help_options.add(f'--{flag}')
@@ -730,6 +907,16 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -730,6 +907,16 @@ A complete manual can be found at https://qpdf.readthedocs.io.
730 self.decls.append(f'void {identifier}();') 907 self.decls.append(f'void {identifier}();')
731 908
732 def handle_json_trivial(self, flag_key, fdata): 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 config = None 920 config = None
734 for t, [kind, v] in fdata['tables'].items(): 921 for t, [kind, v] in fdata['tables'].items():
735 # We have determined that all tables, if multiple, have 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,6 +945,14 @@ A complete manual can be found at https://qpdf.readthedocs.io.
758 f' {{ {config}->{flag_key}(p); }});') 945 f' {{ {config}->{flag_key}(p); }});')
759 946
760 def handle_json_manual(self, path): 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 method = re.sub(r'\.([a-zA-Z0-9])', 956 method = re.sub(r'\.([a-zA-Z0-9])',
762 lambda x: x.group(1).upper(), 957 lambda x: x.group(1).upper(),
763 f'setup{path}') 958 f'setup{path}')
@@ -874,6 +1069,27 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -874,6 +1069,27 @@ A complete manual can be found at https://qpdf.readthedocs.io.
874 return schema_value 1069 return schema_value
875 1070
876 def generate_schema(self, data): 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 # Check to make sure that every command-line option is 1093 # Check to make sure that every command-line option is
878 # represented in data['json']. Build a list of options that we 1094 # represented in data['json']. Build a list of options that we
879 # expect. If an option appears once, we just expect to see it 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,6 +1130,21 @@ A complete manual can be found at https://qpdf.readthedocs.io.
914 str(set(expected.keys()) - options_seen)) 1130 str(set(expected.keys()) - options_seen))
915 1131
916 def check_keys(self, what, d, exp): 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 if not isinstance(d, dict): 1148 if not isinstance(d, dict):
918 exit(f'{what} is not a dictionary') 1149 exit(f'{what} is not a dictionary')
919 actual = set(d.keys()) 1150 actual = set(d.keys())
@@ -922,6 +1153,22 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -922,6 +1153,22 @@ A complete manual can be found at https://qpdf.readthedocs.io.
922 exit(f'{what}: unknown keys = {extra}') 1153 exit(f'{what}: unknown keys = {extra}')
923 1154
924 def validate(self, data): 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 self.check_keys('top', data, set( 1172 self.check_keys('top', data, set(
926 ['choices', 'options', 'json'])) 1173 ['choices', 'options', 'json']))
927 for o in data['options']: 1174 for o in data['options']:
@@ -932,6 +1179,25 @@ A complete manual can be found at https://qpdf.readthedocs.io. @@ -932,6 +1179,25 @@ A complete manual can be found at https://qpdf.readthedocs.io.
932 'required_choices', 'optional_choices'])) 1179 'required_choices', 'optional_choices']))
933 1180
934 def to_identifier(self, label, prefix, const): 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 identifier = re.sub(r'[^a-zA-Z0-9]', '_', label) 1201 identifier = re.sub(r'[^a-zA-Z0-9]', '_', label)
936 if const: 1202 if const:
937 identifier = f'{prefix}_{identifier.upper()}' 1203 identifier = f'{prefix}_{identifier.upper()}'
job.sums
1 # Generated by generate_auto_job 1 # Generated by generate_auto_job
2 CMakeLists.txt 18214e276670dc8beb2ab83f789c6d94941bc92b199b353f3943024cfd41d3bc 2 CMakeLists.txt 18214e276670dc8beb2ab83f789c6d94941bc92b199b353f3943024cfd41d3bc
3 -generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 3 +generate_auto_job 280b75d5307c537385a75ec588493496cfb0bc754d48c34ca8c42bbc55dd717b
4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5