Version 1.0
Copyright © 2023 Lowell D. Thomas
python-ini
 … powered by Python APG
ini_file.py
Go to the documentation of this file.
1 ''' @file python_ini/ini_file.py
2 @brief The IniFile class for parsing an INI file into section/key/values.
3 @dir docs @brief Supplemental documentation files.
4 @dir python_ini @brief The IniFile class.
5 @dir examples @brief Several examples of using the IniFile class.
6 '''
7 import sys
8 import os
9 import copy
10 # add the current working directory to the path
11 # DO NOT MOVE THE FOLLOWING STATEMENT
12 # if using autopep8 formatter, for example, set argument '--ignore=E402'
13 sys.path.append(os.getcwd())
14 from apg_py.lib import utilities as utils
15 from apg_py.lib.parser import Parser
16 from apg_py.lib.trace import Trace
17 from apg_py.lib.ast import Ast
18 import python_ini.grammar as grammar
19 import python_ini.parser_callbacks as pcb
20 import python_ini.ast_callbacks as acb
21 
22 # Note: The SABNF syntax for the ini file parser is in grammar.abnf.
23 # The grammar object, grammar.py was generated with (assuming PyPI installation of apg-py)
24 # python3 apg-py -i python_ini/grammar.abnf
25 
26 
27 class IniFile:
28 
29  def __init__(self, values='s', debug=False):
30  '''Ini file parser constructor.
31  @param values Determines whether the "getter" functions", get_values() and get_section_values(),
32  return a single value or a list of one or more values.
33  - 's' (default) Only a single value is returned for a given key.
34  If more than one value is defined for the key, only the
35  last value is returned.
36  - 'm' Keys are captured as a list of one or more values.
37  If the same key appears more than once within a given section,
38  the value(s) are appended to the previous list.
39  @param debug If True, a trace of the parse is printed to stdout.
40  '''
41  if(not (values == 'm' or values == 's')):
42  msg = 'values must be "s" (single-valued) or "m" (multi-valued)'
43  raise Exception(msg, values)
44  self.errorserrors = None
45  self.__parser__parser = Parser(grammar)
46  if(debug):
47  Trace(self.__parser__parser, mode='xc')
48  self.__parser__parser.add_callbacks({'line-end': pcb.line_end})
49  self.__parser__parser.add_callbacks({'bad-section-line': pcb.bad_section_line})
50  self.__parser__parser.add_callbacks({'bad-value-line': pcb.bad_value_line})
51  self.__parser__parser.add_callbacks({'bad-blank-line': pcb.bad_blank_line})
52  self.__parser__parser.add_callbacks({'u_unicode8': pcb.unicode8})
53  self.__parser__parser.add_callbacks({'u_unicode4': pcb.unicode4})
54  self.__parser__parser.add_callbacks({'u_hexadecimal': pcb.hexadecimal})
55  self.__parser__parser.add_callbacks({'u_escaped-error': pcb.escaped_error})
56  self.__ast__ast = Ast(self.__parser__parser)
57  self.__ast__ast.add_callback('section-name', acb.section_name)
58  self.__ast__ast.add_callback('key-name', acb.key_name)
59  self.__ast__ast.add_callback('value', acb.value)
60  self.__ast__ast.add_callback('d-quoted-value', acb.d_value)
61  self.__ast__ast.add_callback('s-quoted-value', acb.s_value)
62  self.__ast__ast.add_callback('string', acb.string_value)
63  self.__ast__ast.add_callback('true', acb.true_value)
64  self.__ast__ast.add_callback('false', acb.false_value)
65  self.__ast__ast.add_callback('null', acb.null_value)
66  self.__ast__ast.add_callback('int', acb.int_value)
67  self.__ast__ast.add_callback('float', acb.float_value)
68  self.__data__data = {}
69  self.__data__data['values'] = values
70  self.__data__data['current_section'] = None
71  self.__data__data['current_key'] = None
72  self.__data__data['global'] = {}
73  self.__data__data['sections'] = {}
74 
75  def parse(self, fname=None, fhandle=None, fstr=None):
76  '''Parse the input INI file.
77  Parses the ini file, input as a file name, file handle or string.
78  At least one of fname, fhandle or fstr must be supplied.
79  If more than one, the first non-None value of fname, fhandle or fstr in that
80  order is accepted. If none are supplied an Exception is raised.
81  @param fname The name of the ini file to parse.
82  @param fhandle A handle to an open ini file.
83  @param fstr The ini file as a string.
84  '''
85  self.errorserrors = None
86  if(fname):
87  with open(fname, 'r') as fd:
88  input = fd.read()
89  elif(fhandle):
90  input = fhandle.read()
91  elif(fstr):
92  input = fstr
93  else:
94  msg = 'no input supplied, must supply fname, fhandle or fstr'
95  self.errorserrors = msg
96  raise Exception(msg)
97  data = {}
98  data['line_no'] = 1
99  data['errors'] = []
100  data['skip_escaped_error'] = False
101  data['skip_bad_key'] = False
102  result = self.__parser__parser.parse(
103  utils.string_to_tuple(input), user_data=data)
104  if(not result.success):
105  # ABNF syntax is designed so that this should never happen
106  msg = 'internal error - parser failed'
107  msg += '\nuse IniFile(debug=True) for a trace of the parser'
108  raise Exception(msg)
109  if(len(data['errors'])):
110  # display errors
111  msg = ''
112  for error in data['errors']:
113  msg += '{'
114  count = 0
115  for key, value in error.items():
116  if(count > 0):
117  msg += ', '
118  msg += str(key) + ': ' + str(value)
119  count += 1
120  msg += '}\n'
121  self.errorserrors = msg
122  # raise Exception('ini file syntax errors found')
123  self.__data__data['current_section'] = None
124  self.__data__data['current_key'] = None
125  self.__data__data['global'] = {}
126  self.__data__data['sections'] = {}
127  self.__ast__ast.translate(self.__data__data)
128 
129  def display_errors(self):
130  '''Converts any errors found in the INI file syntax to
131  a human-readable ASCII string.
132  @returns Returns the errors display or None if none are found.
133  '''
134  display = None
135  if(self.errorserrors):
136  display = copy.copy(self.errorserrors)
137  return display
138 
139  def get_keys(self):
140  '''Get a list of the global key names.
141  @returns Returns a list, possibly empty, of global key names.
142  '''
143  keys = []
144  for key, value in self.__data__data['global'].items():
145  keys.append(key)
146  return keys
147 
148  def get_values(self, key, default=None):
149  '''Get the list of values for a global key name.
150  @param key The name of the key to get values for.
151  @param default The value to return if the key name is not found.
152  The default value ignores the single- or multi-valued mode
153  and is returned exactly as given to the user.
154  It is the user's responsibility to supply a default that
155  makes sense to his application.
156  @returns Returns
157  - default if the key name is not found
158  - True if the key name list is empty (a default "true" flag)
159  - the full list of values if the values switch, "m"(multiple), is set in the constructor
160  - the last name in the list if the values switch, "s"(single), is set in the constructor
161  '''
162  values = self.__data__data['global'].get(key)
163  if(values is None):
164  return default
165  if(self.__data__data['values'] == 'm'):
166  if(len(values) == 0):
167  return [True]
168  return values
169  if(len(values) == 0):
170  return True
171  return values[len(values) - 1]
172 
173  def get_sections(self):
174  '''Get a list of the section names.
175  @returns Returns a list, possibly empty, of section names.
176  '''
177  sections = []
178  for key, value in self.__data__data['sections'].items():
179  sections.append(key)
180  return sections
181 
182  def get_section_keys(self, section):
183  '''Get a list of key names in the named section.
184  @param section The section name to find the key names in .
185  @returns Returns a list, possibly empty, of key names.
186  '''
187  keys = []
188  if(self.__data__data['sections'].get(section)):
189  for key, value in self.__data__data['sections'][section].items():
190  keys.append(key)
191  return keys
192 
193  def get_section_values(self, section, key, default=None):
194  '''Get a list of values for the named key in the named section.
195  @param section The section name to find the key in.
196  @param key The key name to find the list of values for.
197  @param default The value to return if the section or key name is not found.
198  The default value ignores the single- or multi-valued mode
199  and is returned exactly as given to the user.
200  It is the user's responsibility to supply a default that
201  makes sense to his application.
202  @returns Returns
203  - default if the section or key name is not found
204  - True if the key name list is empty (a default "true" flag)
205  - the full list of values if the multiple values switch, "m", is set in the constructor
206  - the last name in the list if the single value switch, "s", is set in the constructor
207  '''
208  if(self.__data__data['sections'].get(section) is None):
209  return default
210  values = self.__data__data['sections'][section].get(key)
211  if(values is None):
212  return default
213  if(self.__data__data['values'] == 'm'):
214  if(len(values) == 0):
215  return [True]
216  return values
217  # to get here self.__data['values'] == 's'
218  if(len(values) == 0):
219  return True
220  return values[len(values) - 1]
def get_values(self, key, default=None)
Get the list of values for a global key name.
Definition: ini_file.py:148
def get_section_keys(self, section)
Get a list of key names in the named section.
Definition: ini_file.py:182
def parse(self, fname=None, fhandle=None, fstr=None)
Parse the input INI file.
Definition: ini_file.py:75
def get_section_values(self, section, key, default=None)
Get a list of values for the named key in the named section.
Definition: ini_file.py:193
def get_keys(self)
Get a list of the global key names.
Definition: ini_file.py:139
def get_sections(self)
Get a list of the section names.
Definition: ini_file.py:173
def __init__(self, values='s', debug=False)
Ini file parser constructor.
Definition: ini_file.py:29
def display_errors(self)
Converts any errors found in the INI file syntax to a human-readable ASCII string.
Definition: ini_file.py:129
Python APG, Version 1.0, is licensed under the 2-Clause BSD License,
an Open Source Initiative Approved License.