Total Complexity | 40 |
Total Lines | 339 |
Duplicated Lines | 95.58 % |
Coverage | 83.94% |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like sdoc.SDoc often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | 1 | import configparser |
|
2 | 1 | import os |
|
3 | 1 | from typing import List, Optional |
|
4 | |||
5 | 1 | from cleo.styles import OutputStyle |
|
6 | |||
7 | 1 | from sdoc import sdoc2 |
|
8 | 1 | from sdoc.error import SDocError |
|
9 | 1 | from sdoc.format.Format import Format |
|
10 | 1 | from sdoc.sdoc1.SDoc1Interpreter import SDoc1Interpreter |
|
11 | 1 | from sdoc.sdoc2.NodeStore import NodeStore |
|
12 | 1 | from sdoc.sdoc2.SDoc2Interpreter import SDoc2Interpreter |
|
13 | |||
14 | |||
15 | 1 | View Code Duplication | class SDoc: |
|
|||
16 | """ |
||
17 | The SDoc program. |
||
18 | """ |
||
19 | |||
20 | # ------------------------------------------------------------------------------------------------------------------ |
||
21 | 1 | def __init__(self): |
|
22 | """ |
||
23 | Object contructor. |
||
24 | """ |
||
25 | 1 | self._io: Optional[OutputStyle] = None |
|
26 | """ |
||
27 | The IO object. |
||
28 | """ |
||
29 | |||
30 | 1 | self._format: Optional[Format] = None |
|
31 | """ |
||
32 | The class for generation the document in the target format. |
||
33 | """ |
||
34 | |||
35 | 1 | self._target_dir: str = '.' |
|
36 | """ |
||
37 | The directory where the document in the target format must be created. |
||
38 | """ |
||
39 | |||
40 | 1 | self._temp_dir: str = '.' |
|
41 | """ |
||
42 | The directory where temporary files are stored. |
||
43 | """ |
||
44 | |||
45 | 1 | self._config_path: str = '' |
|
46 | """ |
||
47 | The path of the config file. |
||
48 | """ |
||
49 | |||
50 | 1 | self._nodes_paths: List[str] = [] |
|
51 | """ |
||
52 | A list with path names from with node modules must be imported. |
||
53 | """ |
||
54 | |||
55 | 1 | self._formatter_paths: List[str] = [] |
|
56 | """ |
||
57 | A list with path names from with node modules must be imported. |
||
58 | """ |
||
59 | |||
60 | 1 | self._errors: int = 0 |
|
61 | 1 | """ |
|
62 | The total number of errors encountered at SDoc level 1 and level 2. |
||
63 | """ |
||
64 | |||
65 | # ------------------------------------------------------------------------------------------------------------------ |
||
66 | 1 | @property |
|
67 | 1 | def io(self) -> OutputStyle: |
|
68 | """ |
||
69 | Getter for io. |
||
70 | """ |
||
71 | return self._io |
||
72 | |||
73 | # ------------------------------------------------------------------------------------------------------------------ |
||
74 | 1 | @io.setter |
|
75 | 1 | def io(self, io: OutputStyle) -> None: |
|
76 | """ |
||
77 | Setter for io. |
||
78 | |||
79 | :param OutputStyle io: The IO object. |
||
80 | """ |
||
81 | 1 | self._io = io |
|
82 | |||
83 | # ------------------------------------------------------------------------------------------------------------------ |
||
84 | 1 | @property |
|
85 | 1 | def config_path(self) -> str: |
|
86 | """ |
||
87 | Getter for config_path. |
||
88 | """ |
||
89 | return self._config_path |
||
90 | |||
91 | # ------------------------------------------------------------------------------------------------------------------ |
||
92 | 1 | @config_path.setter |
|
93 | 1 | def config_path(self, config_path: str) -> None: |
|
94 | """ |
||
95 | Setter for config_path. |
||
96 | |||
97 | :param str config_path: The path of the config file. |
||
98 | """ |
||
99 | 1 | self._config_path = config_path |
|
100 | |||
101 | # ------------------------------------------------------------------------------------------------------------------ |
||
102 | 1 | @property |
|
103 | 1 | def target_dir(self) -> str: |
|
104 | """ |
||
105 | Getter for target_dir. |
||
106 | """ |
||
107 | return self.target_dir |
||
108 | |||
109 | # ------------------------------------------------------------------------------------------------------------------ |
||
110 | 1 | @property |
|
111 | 1 | def temp_dir(self) -> str: |
|
112 | """ |
||
113 | Getter for temp_dir. |
||
114 | """ |
||
115 | return self.temp_dir |
||
116 | |||
117 | # ------------------------------------------------------------------------------------------------------------------ |
||
118 | 1 | def _config_create_formatter(self, config: configparser.ConfigParser) -> None: |
|
119 | """ |
||
120 | Creates the formatter for generating the document in the target format. |
||
121 | |||
122 | :param configparser.ConfigParser config: The config parser. |
||
123 | """ |
||
124 | 1 | available_formats = ['html'] |
|
125 | |||
126 | # Read the target format of the document. |
||
127 | 1 | target_format = config.get('sdoc', 'format', fallback=None) |
|
128 | 1 | if target_format not in available_formats: |
|
129 | raise SDocError("The format '{0!s}' is not available in SDoc. Set another in config file '{1!s}'" |
||
130 | .format(target_format, self._config_path)) |
||
131 | |||
132 | 1 | if not target_format: |
|
133 | raise SDocError("Option 'format' in section 'sdoc' not set in config file '{0!s}'" |
||
134 | .format(self._config_path)) |
||
135 | |||
136 | # Read the class name for formatting the SDoc2 nodes into the target format. |
||
137 | 1 | section = 'format_' + target_format |
|
138 | 1 | class_name = config.get(section, 'class', fallback=None) |
|
139 | 1 | if not class_name: |
|
140 | raise SDocError("Option 'class' in section '{0!s}' not set in config file '{1!s}'". |
||
141 | format(section, self._config_path)) |
||
142 | |||
143 | # Import the class. |
||
144 | 1 | try: |
|
145 | 1 | parts = class_name.split('.') |
|
146 | 1 | module = ".".join(parts[:-1]) |
|
147 | 1 | __import__(module) |
|
148 | 1 | m = __import__(module) |
|
149 | 1 | for comp in parts[1:]: |
|
150 | 1 | m = getattr(m, comp) |
|
151 | except AttributeError: |
||
152 | raise SDocError("There is no module named '{0!s}'! Set name correctly in config file '{1!s}'" |
||
153 | .format(class_name, self._config_path)) |
||
154 | |||
155 | # Create the formatter. |
||
156 | 1 | self._format = m(self._io, target_format, config) |
|
157 | |||
158 | # ------------------------------------------------------------------------------------------------------------------ |
||
159 | 1 | def _config_set_temp_dir(self, config: configparser.ConfigParser) -> None: |
|
160 | """ |
||
161 | Reads the directory for storing temporary files. |
||
162 | |||
163 | :param configparser.ConfigParser config: The config parser. |
||
164 | """ |
||
165 | 1 | self._temp_dir = config.get('sdoc', 'temp_dir', fallback=self._temp_dir) |
|
166 | |||
167 | 1 | if not self._temp_dir: |
|
168 | raise SDocError("Option 'temp_dir' in section 'sdoc' not set correctly in config file '{0!s}'". |
||
169 | format(self._config_path)) |
||
170 | |||
171 | 1 | if not os.access(self._temp_dir, os.W_OK): |
|
172 | raise SDocError("Directory '{0!s}' is not writable".format(self._temp_dir)) |
||
173 | |||
174 | # ------------------------------------------------------------------------------------------------------------------ |
||
175 | 1 | def _config_set_target_dir(self, config: configparser.ConfigParser) -> None: |
|
176 | """ |
||
177 | Reads the directory where the document in the target format must be created. |
||
178 | |||
179 | :param configparser.ConfigParser config: The config parser. |
||
180 | """ |
||
181 | 1 | self._target_dir = config.get('sdoc', 'target_dir', fallback=self._target_dir) |
|
182 | |||
183 | 1 | if not self._target_dir: |
|
184 | raise SDocError("Option 'target_dir' in section 'sdoc' not set correctly in config file '{0!s}'". |
||
185 | format(self._config_path)) |
||
186 | |||
187 | 1 | if not os.access(self._target_dir, os.W_OK): |
|
188 | raise SDocError("Directory '{0!s}' is not writable".format(self._target_dir)) |
||
189 | |||
190 | # ------------------------------------------------------------------------------------------------------------------ |
||
191 | 1 | def _read_config_file(self) -> None: |
|
192 | """ |
||
193 | Reads the configuration file. |
||
194 | """ |
||
195 | 1 | config = configparser.ConfigParser() |
|
196 | 1 | config.read(self._config_path) |
|
197 | |||
198 | # Get the temp and target directory. |
||
199 | 1 | self._config_set_temp_dir(config) |
|
200 | 1 | self._config_set_target_dir(config) |
|
201 | |||
202 | # Create the formatter for generating the document in the target format. |
||
203 | 1 | self._config_create_formatter(config) |
|
204 | |||
205 | 1 | self._formatter_paths.append(os.path.dirname(__file__) + '/sdoc2/formatter') |
|
206 | 1 | self._nodes_paths.append(os.path.dirname(__file__) + '/sdoc2/node') |
|
207 | |||
208 | # ------------------------------------------------------------------------------------------------------------------ |
||
209 | 1 | def _create_node_store(self) -> None: |
|
210 | """ |
||
211 | Creates the node store (for storing nodes). |
||
212 | """ |
||
213 | 1 | sdoc2.node_store = NodeStore(self._io) |
|
214 | |||
215 | # ------------------------------------------------------------------------------------------------------------------ |
||
216 | 1 | @staticmethod |
|
217 | 1 | def importing(path: str) -> None: |
|
218 | """ |
||
219 | Imports modules from specific path. |
||
220 | |||
221 | :param str path: The specific path. |
||
222 | """ |
||
223 | 1 | modules = os.listdir(os.path.dirname(__file__) + path) |
|
224 | |||
225 | 1 | path = path.replace('/', '.') |
|
226 | |||
227 | 1 | for module in modules: |
|
228 | 1 | if module != '__init__.py' and module[-3:] == '.py': |
|
229 | 1 | __import__('sdoc' + path + module[:-3], locals(), globals()) |
|
230 | |||
231 | # ------------------------------------------------------------------------------------------------------------------ |
||
232 | 1 | def _import_nodes(self) -> None: |
|
233 | """ |
||
234 | Imports nodes from path which is declared below. |
||
235 | """ |
||
236 | # @todo improve |
||
237 | 1 | self.importing('/sdoc2/node/') |
|
238 | |||
239 | # ------------------------------------------------------------------------------------------------------------------ |
||
240 | 1 | def _import_formatters(self) -> None: |
|
241 | """ |
||
242 | Imports formatters from path which is declared below. |
||
243 | """ |
||
244 | # @todo improve |
||
245 | 1 | self.importing('/sdoc2/formatter/html/') |
|
246 | |||
247 | # ------------------------------------------------------------------------------------------------------------------ |
||
248 | 1 | def init(self) -> None: |
|
249 | """ |
||
250 | Executes initiations required before running SDoc. |
||
251 | """ |
||
252 | 1 | self._read_config_file() |
|
253 | 1 | self._create_node_store() |
|
254 | 1 | self._import_nodes() |
|
255 | 1 | self._import_formatters() |
|
256 | |||
257 | # ------------------------------------------------------------------------------------------------------------------ |
||
258 | 1 | def run_sdoc1(self, sdoc1_path: str, sdoc2_path: str, log_errors: bool = True) -> int: |
|
259 | """ |
||
260 | Run the SDoc1 parser and returns the error count. |
||
261 | |||
262 | :param str sdoc1_path: The path of the SDoc1 document. |
||
263 | :param str sdoc2_path: The path were the the SDoc2 document mut be stored. |
||
264 | :param bool log_errors: If true the number of errors will be logged. |
||
265 | """ |
||
266 | 1 | self._io.title('SDoc1') |
|
267 | |||
268 | 1 | interpreter1 = SDoc1Interpreter(self._io) |
|
269 | 1 | self._errors += interpreter1.process(sdoc1_path, sdoc2_path) |
|
270 | |||
271 | 1 | if log_errors and self._errors: |
|
272 | self._io.writeln(" ") |
||
273 | self._io.title('Errors') |
||
274 | self._io.error('There were {0} errors in total'.format(self._errors)) |
||
275 | |||
276 | 1 | return self._errors |
|
277 | |||
278 | # ------------------------------------------------------------------------------------------------------------------ |
||
279 | 1 | def run_sdoc2(self, sdoc2_path: str, log_errors: bool = True) -> int: |
|
280 | """ |
||
281 | Run the SDoc2 parser and returns the error count.. |
||
282 | |||
283 | :param str sdoc2_path: The path of the SDoc2 document. |
||
284 | :param bool log_errors: If true the number of errors will be logged. |
||
285 | """ |
||
286 | 1 | self._io.writeln('') |
|
287 | 1 | self._io.title('SDoc2') |
|
288 | |||
289 | 1 | interpreter2 = SDoc2Interpreter(self._io) |
|
290 | 1 | self._errors += interpreter2.process(sdoc2_path) |
|
291 | |||
292 | 1 | if log_errors and self._errors: |
|
293 | self._io.writeln(" ") |
||
294 | self._io.title('Errors') |
||
295 | self._io.error('There were {0} errors in total'.format(self._errors)) |
||
296 | |||
297 | 1 | return self._errors |
|
298 | |||
299 | # ------------------------------------------------------------------------------------------------------------------ |
||
300 | 1 | def run_format(self, log_errors: bool = True) -> int: |
|
301 | """ |
||
302 | Generates the target document in the specific format and returns the error count. |
||
303 | |||
304 | :param bool log_errors: If true the number of errors will be logged. |
||
305 | """ |
||
306 | 1 | self._io.writeln('') |
|
307 | 1 | self._io.title('Format') |
|
308 | |||
309 | 1 | self._errors += sdoc2.node_store.generate(self._format) |
|
310 | |||
311 | 1 | if log_errors and self._errors: |
|
312 | self._io.writeln(" ") |
||
313 | self._io.title('Errors') |
||
314 | self._io.error('There were {0} errors in total'.format(self._errors)) |
||
315 | |||
316 | 1 | return self._errors |
|
317 | |||
318 | # ------------------------------------------------------------------------------------------------------------------ |
||
319 | 1 | def run_sdoc(self, main_filename: str, log_errors: bool = True) -> int: |
|
320 | """ |
||
321 | Runs the SDoc1 and SDoc2 parser and returns the error count. |
||
322 | |||
323 | :param str main_filename: The path of the SDoc1 document. |
||
324 | :param bool log_errors: If true the number of errors will be logged. |
||
325 | """ |
||
326 | 1 | self.init() |
|
327 | |||
328 | 1 | temp_filename = self._temp_dir + '/' + os.path.basename(main_filename) + '.sdoc2' |
|
329 | 1 | self.run_sdoc1(main_filename, temp_filename, False) |
|
330 | 1 | self.run_sdoc2(temp_filename, False) |
|
331 | 1 | self.run_format(False) |
|
332 | |||
333 | 1 | if log_errors and self._errors: |
|
334 | 1 | self._io.writeln(" ") |
|
335 | 1 | self._io.title('Errors') |
|
336 | 1 | self._io.error('There were {0} errors in total'.format(self._errors)) |
|
337 | |||
338 | 1 | return self._errors |
|
339 | |||
341 |