Passed
Push — develop ( 13d0b0...48e910 )
by Christophe
01:22
created

pandoc_latex_tip._app   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 456
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
eloc 229
dl 0
loc 456
rs 8.96
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
B CollectionsDeleteCommand.handle() 0 44 7
A PandocBeamerFilterCommand.handle() 0 11 1
A CollectionsAddCommand.handle() 0 30 3
A PandocLaTeXFilterCommand.handle() 0 11 1
A CollectionsInfoCommand.handle() 0 29 3
A CollectionsListCommand.handle() 0 16 4
A IconsListCommand.handle() 0 22 3
D IconsAddCommand.handle() 0 75 13
B IconsDeleteCommand.handle() 0 40 7

1 Function

Rating   Name   Duplication   Size   Complexity  
A app() 0 19 1

How to fix   Complexity   

Complexity

Complex classes like pandoc_latex_tip._app 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
"""
2
App module.
3
"""
4
5
import pathlib
6
import shutil
7
import sys
8
from importlib.metadata import version
9
10
from cleo.application import Application
11
from cleo.commands.command import Command
12
from cleo.helpers import argument, option
13
14
import yaml
15
16
17
from ._main import get_core_icons, main
18
19
name_arg = argument(
20
    "name",
21
    description="Collection name",
22
)
23
file_arg = argument(
24
    "file",
25
    description="File name",
26
)
27
css_opt = option(
28
    "CSS",
29
    description="CSS filename from the collection",
30
    flag=False,
31
)
32
ttf_opt = option(
33
    "TTF",
34
    description="TTF filename from the collection",
35
    flag=False,
36
)
37
prefix_opt = option(
38
    "prefix",
39
    short_name="p",
40
    description="Icon prefix used to replace the common prefix found in the css file",
41
    flag=False,
42
)
43
44
45
class CollectionsAddCommand(Command):
46
    """
47
    CollectionsAddCommand.
48
    """
49
50
    name = "collections add"
51
    description = "Add a file to a collection"
52
    arguments = [name_arg, file_arg]
53
    help = (  # noqa: A003, VNE003
54
        "A collection is a space used to store all the CSS and TTF files "
55
        "related to one or more sets of icons."
56
    )
57
58
    def handle(self) -> int:
59
        """
60
        Handle collections add command.
61
62
        Returns
63
        -------
64
        int
65
            status code
66
67
        Raises
68
        ------
69
        ValueError
70
            If an error occurs.
71
        """
72
        if self.argument("name") == "fontawesome":
73
            raise ValueError("<error>You cannot modify core collection</error>")
74
        dir_path = pathlib.Path(
75
            sys.prefix, "share", "pandoc_latex_tip", self.argument("name")
76
        )
77
        if not dir_path.exists():
78
            dir_path.mkdir(parents=True)
79
        file_path = pathlib.Path(self.argument("file"))
80
        dest_path = pathlib.Path(dir_path, file_path.parts[-1])
81
        shutil.copy(file_path, dest_path)
82
83
        self.line(
84
            f"Add file '{self.argument('file')}' to "
85
            f"collection '{self.argument('name')}'"
86
        )
87
        return 0
88
89
90
class CollectionsDeleteCommand(Command):
91
    """
92
    CollectionDeleteCommand.
93
    """
94
95
    name = "collections delete"
96
    description = "Delete a collection"
97
    arguments = [name_arg]
98
    help = (  # noqa: A003,VNE003
99
        "Deleting a collection will erase the folder containing its files. "
100
        "The operation cannot proceed if the collection is used by a set of icons."
101
    )
102
103
    def handle(self) -> int:
104
        """
105
        Handle collections delete command.
106
107
        Returns
108
        -------
109
        int
110
            status code
111
112
        Raises
113
        ------
114
        ValueError
115
            If an error occurs.
116
        """
117
        if self.argument("name") == "fontawesome":
118
            raise ValueError("<error>You cannot modify core collection</error>")
119
        dir_path = pathlib.Path(
120
            sys.prefix, "share", "pandoc_latex_tip", self.argument("name")
121
        )
122
        config_path = pathlib.Path(
123
            sys.prefix,
124
            "share",
125
            "pandoc_latex_tip",
126
            "config.yml",
127
        )
128
        if config_path.exists():
129
            with config_path.open(encoding="utf-8") as stream:
130
                icons = yaml.safe_load(stream)
131
                for definition in icons:
132
                    if definition["collection"] == self.argument("name"):
133
                        raise ValueError(
134
                            f"<error>Collection '{self.argument('name')}' "
135
                            f"is in use</error>"
136
                        )
137
138
        if not dir_path.exists():
139
            raise ValueError(
140
                f"<error>Collection '{self.argument('name')}' "
141
                f"does not exist</error>"
142
            )
143
144
        shutil.rmtree(dir_path)
145
        self.line(f"Delete collection '{self.argument('name')}'")
146
        return 0
147
148
149
class CollectionsListCommand(Command):
150
    """
151
    CollectionsListCommand.
152
    """
153
154
    name = "collections"
155
    description = "List the collections"
156
157
    def handle(self) -> int:
158
        """
159
        Handle collections command.
160
161
        Returns
162
        -------
163
        int
164
            status code
165
        """
166
        dir_path = pathlib.Path(sys.prefix, "share", "pandoc_latex_tip")
167
        for folder in dir_path.iterdir():
168
            if folder.parts[-1] == "fontawesome":
169
                self.line("fontawesome *")
170
            elif folder.is_dir():
171
                self.line(folder.parts[-1])
172
        return 0
173
174
175
class CollectionsInfoCommand(Command):
176
    """
177
    CollectionsInfoCommand.
178
    """
179
180
    name = "collections info"
181
    description = "Display a collection"
182
    arguments = [name_arg]
183
    help = (  # noqa: A003, VNE003
184
        "Displaying a collection allows listing all the "
185
        "CSS and TTF files it contains."
186
    )
187
188
    def handle(self) -> int:
189
        """
190
        Handle collections info command.
191
192
        Returns
193
        -------
194
        int
195
            status code
196
197
        Raises
198
        ------
199
        ValueError
200
            If an error occurs.
201
        """
202
        dir_path = pathlib.Path(
203
            sys.prefix,
204
            "share",
205
            "pandoc_latex_tip",
206
            self.argument("name"),
207
        )
208
        if not dir_path.exists():
209
            raise ValueError(
210
                f"<error>Collection '{self.argument('name')}' "
211
                f"does not exist</error>"
212
            )
213
214
        for filename in dir_path.iterdir():
215
            self.line(filename.parts[-1])
216
        return 0
217
218
219
class IconsAddCommand(Command):
220
    """
221
    IconsAddCommand.
222
    """
223
224
    name = "icons add"
225
    description = "Add a set of icons from a collection"
226
    arguments = [name_arg]
227
    options = [css_opt, ttf_opt, prefix_opt]
228
    help = (  # noqa: A003, VNE003
229
        "A set of icons is created from a CSS file and a TTF file from a collection. "
230
        "The prefix ensures that the icons are unique. "
231
        "It replaces the common prefix detected in the CSS file."
232
    )
233
234
    # pylint: disable=too-many-return-statements, too-many-branches
235
    def handle(self) -> int:
236
        """
237
        Handle icons add command.
238
239
        Returns
240
        -------
241
        int
242
            status code
243
244
        Raises
245
        ------
246
        ValueError
247
            If an error occurs.
248
        """
249
        if self.argument("name") == "fontawesome":
250
            raise ValueError("<error>You cannot modify core collection</error>")
251
        dir_path = pathlib.Path(
252
            sys.prefix,
253
            "share",
254
            "pandoc_latex_tip",
255
            self.argument("name"),
256
        )
257
        if dir_path.exists():
258
            if not self.option("CSS"):
259
                raise ValueError("<error>CSS option is mandatory</error>")
260
            css_file = pathlib.Path(dir_path, self.option("CSS"))
261
            if not css_file.exists():
262
                raise ValueError(
263
                    f"<error>Collection '{self.argument('name')}' "
264
                    f"does not contain '{self.option('CSS')}'</error>"
265
                )
266
            if not self.option("TTF"):
267
                raise ValueError("<error>TTF option is mandatory</error>")
268
            ttf_file = pathlib.Path(dir_path, self.option("TTF"))
269
            if not ttf_file.exists():
270
                raise ValueError(
271
                    f"<error>Collection '{self.argument('name')}' "
272
                    f"does not contain '{self.option('TTF')}'</error>"
273
                )
274
            if not self.option("prefix"):
275
                raise ValueError("<error>prefix option is mandatory</error>")
276
            config_path = pathlib.Path(
277
                sys.prefix,
278
                "share",
279
                "pandoc_latex_tip",
280
                "config.yml",
281
            )
282
            if config_path.exists():
283
                with config_path.open(encoding="utf-8") as stream:
284
                    icons = yaml.safe_load(stream)
285
                    for definition in icons:
286
                        if definition["prefix"] == self.option("prefix"):
287
                            raise ValueError(
288
                                f"<error>Prefix '{self.option('prefix')}' "
289
                                f"is already used</error>"
290
                            )
291
            else:
292
                icons = []
293
            icons.append(
294
                {
295
                    "collection": self.argument("name"),
296
                    "CSS": self.option("CSS"),
297
                    "TTF": self.option("TTF"),
298
                    "prefix": self.option("prefix"),
299
                },
300
            )
301
            with config_path.open(mode="w", encoding="utf-8") as stream:
302
                stream.write(yaml.dump(icons, sort_keys=False))
303
304
        else:
305
            raise ValueError(
306
                f"<error>Collection '{self.argument('name')}' "
307
                f"does not exist</error>"
308
            )
309
        return 0
310
311
312
class IconsDeleteCommand(Command):
313
    """
314
    IconsDeleteCommand.
315
    """
316
317
    name = "icons delete"
318
    description = "Delete a set of icons"
319
    options = [prefix_opt]
320
321
    def handle(self) -> int:
322
        """
323
        Handle icons delete command.
324
325
        Returns
326
        -------
327
        int
328
            status code
329
330
        Raises
331
        ------
332
        ValueError
333
            If an error occurs.
334
        """
335
        if not self.option("prefix"):
336
            raise ValueError("<error>prefix option is mandatory</error>")
337
        if self.option("prefix") in ("fa-", "far-", "fab-"):
338
            raise ValueError("<error>You cannot modify core icons</error>")
339
        config_path = pathlib.Path(
340
            sys.prefix,
341
            "share",
342
            "pandoc_latex_tip",
343
            "config.yml",
344
        )
345
        if config_path.exists():
346
            with config_path.open(encoding="utf-8") as stream:
347
                icons = yaml.safe_load(stream)
348
            keep = [
349
                definition
350
                for definition in icons
351
                if definition["prefix"] != self.option("prefix")
352
            ]
353
            if keep != icons:
354
                with config_path.open(mode="w", encoding="utf-8") as stream:
355
                    stream.write(yaml.dump(keep, sort_keys=False))
356
            else:
357
                raise ValueError("<error>Unexisting prefix</error>")
358
        else:
359
            raise ValueError("<error>Unexisting config file</error>")
360
        return 0
361
362
363
class IconsListCommand(Command):
364
    """
365
    IconsListCommand.
366
    """
367
368
    name = "icons"
369
    description = "List the set of icons"
370
371
    def handle(self) -> int:
372
        """
373
        Handle icons command.
374
375
        Returns
376
        -------
377
        int
378
            status code
379
        """
380
        icons = get_core_icons()
381
        config_path = pathlib.Path(
382
            sys.prefix,
383
            "share",
384
            "pandoc_latex_tip",
385
            "config.yml",
386
        )
387
        if config_path.exists():
388
            with config_path.open(encoding="utf-8") as stream:
389
                icons.extend(yaml.safe_load(stream))
390
        self.line(yaml.dump(icons, sort_keys=False))
391
392
        return 0
393
394
395
class PandocLaTeXFilterCommand(Command):
396
    """
397
    PandocLaTeXFilterCommand.
398
    """
399
400
    name = "latex"
401
    description = "Run pandoc filter for LaTeX document"
402
403
    def handle(self) -> int:
404
        """
405
        Handle latex command.
406
407
        Returns
408
        -------
409
        int
410
            status code
411
        """
412
        main()
413
        return 0
414
415
416
class PandocBeamerFilterCommand(Command):
417
    """
418
    PandocBeamerFilterCommand.
419
    """
420
421
    name = "beamer"
422
    description = "Run pandoc filter for Beamer document"
423
424
    def handle(self) -> int:
425
        """
426
        Handle beamer command.
427
428
        Returns
429
        -------
430
        int
431
            status code
432
        """
433
        main()
434
        return 0
435
436
437
def app() -> None:
438
    """
439
    Create a cleo application.
440
    """
441
    application = Application(
442
        name="pandoc-latex-tip filter",
443
        version=version("pandoc-latex-tip"),
444
    )
445
    application.set_display_name("pandoc-latex-tip filter")
446
    application.add(CollectionsAddCommand())
447
    application.add(CollectionsDeleteCommand())
448
    application.add(CollectionsListCommand())
449
    application.add(CollectionsInfoCommand())
450
    application.add(IconsAddCommand())
451
    application.add(IconsDeleteCommand())
452
    application.add(IconsListCommand())
453
    application.add(PandocLaTeXFilterCommand())
454
    application.add(PandocBeamerFilterCommand())
455
    application.run()
456