nwdocstringchecking 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: nwdocstringchecking
3
+ Version: 2.0.1
4
+ Summary: A library designed to identify which methods in a Python file are missing docstrings.
5
+ Home-page: https://github.com/numbworks/nwdocstringchecking
6
+ Author: numbworks
7
+ License: MIT
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ Dynamic: author
11
+ Dynamic: description
12
+ Dynamic: description-content-type
13
+ Dynamic: home-page
14
+ Dynamic: license
15
+ Dynamic: requires-python
16
+ Dynamic: summary
17
+
18
+ # nwdocstringchecking
19
+
20
+ ## Introduction
21
+
22
+ This packages consists of the following modules:
23
+
24
+ - `nwdocstringchecking` is a library designed to identify which methods in a Python file are missing docstrings.
25
+ - `nwdocstringcheckingcli` is a command-line application built on the top of `nwdocstringchecking`.
26
+
27
+ ## Getting Started
28
+
29
+ I'll use Debian as operating system, but the concept is similar for Windows or Mac.
30
+
31
+ Procedure:
32
+
33
+ 1. Install `pip` and `venv`:
34
+
35
+ ```sh
36
+ sudo apt install python3-pip python3.12-venv
37
+ ```
38
+
39
+ 2. Create a virtual environment:
40
+
41
+ ```sh
42
+ python3 -m venv /tmp/nwdocstringchecking
43
+ source /tmp/nwdocstringchecking/bin/activate
44
+ ```
45
+
46
+ 3. Download and install the package:
47
+
48
+ ```sh
49
+ pip install nwdocstringchecking
50
+ ```
51
+
52
+ 4. Enter in the interactive Python environment:
53
+
54
+ ```sh
55
+ python3
56
+ ```
57
+ ```
58
+ Python 3.12.3 (main, Aug 31 2026, 10:18:26) [GCC 13.3.0] on linux
59
+ Type "help", "copyright", "credits" or "license" for more information.
60
+ >>>
61
+ ```
62
+
63
+ 5. Try out if the library works:
64
+
65
+ ```python
66
+ from nwdocstringchecking import DocStringChecker
67
+
68
+ docstring_checker : DocStringChecker = DocStringChecker()
69
+ ```
70
+
71
+ 6. Exit from the interactive Python environment:
72
+
73
+ ```python
74
+ exit()
75
+ ```
76
+
77
+ 7. Try out if the CLI application works:
78
+
79
+ ```sh
80
+ nwdocstringcheckingcli
81
+ ```
82
+
83
+ 8. Deactivate and delete the virtual environment:
84
+
85
+ ```sh
86
+ deactivate
87
+ rm -rf /tmp/nwdocstringchecking
88
+ ```
89
+
90
+ 9. Done!
91
+
92
+ ## Examples
93
+
94
+ Run it against a `file_path`:
95
+
96
+ ```sh
97
+ nwdocstringcheckingcli --file_path src/nwdocstringchecking.py
98
+ ```
99
+
100
+ ```
101
+ *******************************************
102
+ '##::: ##:'##:::::'##:'########:::'######::
103
+ ###:: ##: ##:'##: ##: ##.... ##:'##... ##:
104
+ ####: ##: ##: ##: ##: ##:::: ##: ##:::..::
105
+ ## ## ##: ##: ##: ##: ##:::: ##:. ######::
106
+ ##. ####: ##: ##: ##: ##:::: ##::..... ##:
107
+ ##:. ###: ##: ##: ##: ##:::: ##:'##::: ##:
108
+ ##::. ##:. ###. ###:: ########::. ######::
109
+ ..::::..:::...::...:::........::::......:::
110
+ ************************Version: 2.0.1*****
111
+
112
+ file_path: 'src/nwdocstringchecking.py'
113
+ exclude: '[]'
114
+
115
+ _MessageCollectionValidator.provided_file_path_doesnt_exist
116
+ ```
117
+
118
+ Run it against a `file_path` with `exclude`:
119
+
120
+ ```sh
121
+ nwdocstringcheckingcli --file_path src/nwdocstringchecking.py --exclude Message --exclude Something
122
+ ```
123
+
124
+ ```
125
+ *******************************************
126
+ '##::: ##:'##:::::'##:'########:::'######::
127
+ ###:: ##: ##:'##: ##: ##.... ##:'##... ##:
128
+ ####: ##: ##: ##: ##: ##:::: ##: ##:::..::
129
+ ## ## ##: ##: ##: ##: ##:::: ##:. ######::
130
+ ##. ####: ##: ##: ##: ##:::: ##::..... ##:
131
+ ##:. ###: ##: ##: ##: ##:::: ##:'##::: ##:
132
+ ##::. ##:. ###. ###:: ########::. ######::
133
+ ..::::..:::...::...:::........::::......:::
134
+ ************************Version: 2.0.1*****
135
+
136
+ file_path: 'src/nwdocstringchecking.py'
137
+ exclude: '['Message', 'Something']'
138
+
139
+ All methods have docstrings.
140
+ ```
141
+
142
+ ## Links
143
+
144
+ - Source: https://github.com/numbworks/nwdocstringchecking
145
+ - License: https://github.com/numbworks/nwdocstringchecking/blob/master/LICENSE
146
+
147
+ ## Total Downloads (PyPi)
148
+
149
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/nwdocstringchecking?period=total&units=NONE&left_color=BLACK&right_color=BLUE&left_text=TOTAL+DOWNLOADS)](https://pepy.tech/projects/nwdocstringchecking)
@@ -0,0 +1,8 @@
1
+ nwdocstringchecking.py,sha256=en2BhIbWK4newXig-kPjVFePGjd0n_4gZweoaxlfFq8,2613
2
+ nwdocstringcheckingcli.py,sha256=6Xxr9Fb8ArsbWy9MztGw1NQdeWDwDOxUNR7wQWST06s,11857
3
+ setupinfo.py,sha256=bxSRJLmhxTbDpQ5NSPPbudbV__EoxGw8yTsvyOJ9kBg,557
4
+ nwdocstringchecking-2.0.1.dist-info/METADATA,sha256=TnfCZ6b3-v4wrGcgVkrwXYuxwUJ0Ng5GWrA0NGy3tF0,3611
5
+ nwdocstringchecking-2.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ nwdocstringchecking-2.0.1.dist-info/entry_points.txt,sha256=zVRHVw0fuAi4ScBzKUt82iMFltcpWJCDNcP8E5Q58Co,71
7
+ nwdocstringchecking-2.0.1.dist-info/top_level.txt,sha256=CMJgYLAyf5OdUH_PfZuM5ywuBhEdv6N6DhVyg_hMVJs,53
8
+ nwdocstringchecking-2.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nwdocstringcheckingcli = nwdocstringcheckingcli:main
@@ -0,0 +1,3 @@
1
+ nwdocstringchecking
2
+ nwdocstringcheckingcli
3
+ setupinfo
nwdocstringchecking.py ADDED
@@ -0,0 +1,86 @@
1
+ '''
2
+ A library designed to identify which methods in a Python file are missing docstrings.
3
+
4
+ Alias: nwds
5
+ '''
6
+
7
+ # GLOBAL MODULES
8
+ import ast
9
+ import os
10
+ from ast import Module
11
+ from typing import cast
12
+
13
+ # LOCAL MODULES
14
+ # CONSTANTS
15
+ # STATIC CLASSES
16
+ class _MessageCollectionValidator():
17
+
18
+ '''Collects all the messages used for logging and for the exceptions used by Validator.'''
19
+
20
+ @staticmethod
21
+ def provided_file_path_doesnt_exist(file_path : str) -> str:
22
+ return f"The provided 'file_path' doesn't exist: '{file_path}'."
23
+ class _MessageCollection(
24
+ _MessageCollectionValidator):
25
+
26
+ '''Collects all the messages used for logging and for the exceptions.'''
27
+ class Validator():
28
+
29
+ '''Collects all validation methods.'''
30
+
31
+ @staticmethod
32
+ def validate_file_path(file_path : str) -> None:
33
+
34
+ '''Returns file_path or raises Exception.'''
35
+
36
+ if not os.path.isfile(file_path):
37
+ raise Exception(_MessageCollection.provided_file_path_doesnt_exist(file_path))
38
+
39
+ # CLASSES
40
+ class DocStringChecker():
41
+
42
+ '''Collects all the logic related to docstrings management.'''
43
+
44
+ def __load_source(self, file_path : str) -> str:
45
+
46
+ '''Loads source from file_path.'''
47
+
48
+ source : str = ""
49
+
50
+ with open(file_path, "r", encoding='utf-8') as file:
51
+ source = file.read()
52
+
53
+ return source
54
+ def __get_missing_docstrings(self, source : str, exclude : list[str]) -> list[str]:
55
+
56
+ '''Returns all the method names missing docstrings by excluding specified substrings.'''
57
+
58
+ tree : Module = ast.parse(source = source)
59
+
60
+ method_names : list[str] = []
61
+
62
+ for node in ast.walk(tree):
63
+ if isinstance(node, ast.ClassDef):
64
+ for item in node.body:
65
+ if isinstance(item, ast.FunctionDef):
66
+ if ast.get_docstring(item) is None:
67
+ method_name = f"{node.name}.{item.name}"
68
+ if not any(substring in method_name for substring in exclude):
69
+ method_names.append(method_name)
70
+
71
+ return method_names
72
+
73
+ def run(self, file_path : str, exclude : list[str] = []) -> list[str]:
74
+
75
+ '''Runs the docstring check.'''
76
+
77
+ Validator.validate_file_path(file_path)
78
+
79
+ source : str = self.__load_source(file_path = cast(str, file_path))
80
+ missing : list[str] = self.__get_missing_docstrings(source = source, exclude = exclude)
81
+
82
+ return missing
83
+
84
+ # MAIN
85
+ if __name__ == "__main__":
86
+ pass
@@ -0,0 +1,358 @@
1
+ '''
2
+ A CLI application built on the top of nwdocstringchecking.
3
+ '''
4
+
5
+ # GLOBAL MODULES
6
+ import os
7
+ import subprocess
8
+ from argparse import ArgumentParser, Namespace
9
+ from shutil import get_terminal_size
10
+ from subprocess import CompletedProcess
11
+ from typing import Any, Callable, Final, Optional
12
+
13
+ # LOCAL MODULES
14
+ from nwdocstringchecking import DocStringChecker, Validator
15
+ from setupinfo import CLI_DESCRIPTION, PROJECT_VERSION
16
+
17
+ # CONSTANTS
18
+ class CLISTRING:
19
+
20
+ '''Collects all the CLI-related strings.'''
21
+
22
+ OPTION_FILEPATH_FLAGS : Final[list[str]] = ["--file_path"]
23
+ OPTION_FILEPATH_DEST : Final[str] = "file_path"
24
+ OPTION_FILEPATH_REQUIRED : Final[bool] = True
25
+ OPTION_FILEPATH_HELP : Final[str] = "The path to the Python file to check docstrings for."
26
+
27
+ OPTION_EXCLUDE_FLAGS : Final[list[str]] = ["--exclude"]
28
+ OPTION_EXCLUDE_DEST : Final[str] = "exclude"
29
+ OPTION_EXCLUDE_REQUIRED : Final[bool] = False
30
+ OPTION_EXCLUDE_HELP : Final[str] = "One or multiple substrings to exclude from the output."
31
+ OPTION_EXCLUDE_DEFAULT : Final[list[str]] = []
32
+ OPTION_EXCLUDE_ACTION : Final[str] = "append"
33
+
34
+ # STATIC CLASSES
35
+ class _MessageCollectionAsciiBannerManager():
36
+
37
+ '''Collects all the messages used for logging and for the exceptions.'''
38
+
39
+ @staticmethod
40
+ def provided_version_empty_whitespace() -> str:
41
+ return "The provided 'version' is empty or whitespace."
42
+ class _MessageCollectionCLIManager():
43
+
44
+ '''Collects all the messages used for logging and for the exceptions.'''
45
+
46
+ @staticmethod
47
+ def all_methods_have_docstrings() -> str:
48
+ return "All methods have docstrings."
49
+ class _MessageCollection(
50
+ _MessageCollectionAsciiBannerManager,
51
+ _MessageCollectionCLIManager):
52
+
53
+ '''Collects all the messages used for logging and for the exceptions.'''
54
+
55
+ # CLASSES
56
+ class AsciiBannerManager:
57
+
58
+ """
59
+ Creates the ASCII banner for the provided library's version.
60
+
61
+ The figlet can be generated using
62
+ - 'http://www.network-science.de/ascii/' (font: "banner3-D", width: 120)
63
+ - 'https://www.askapache.com/online-tools/figlet-ascii/'.
64
+ """
65
+
66
+ def __validate(self, version: str) -> None:
67
+
68
+ """Validates the provided 'version'."""
69
+
70
+ if not version or not version.strip():
71
+ raise ValueError(_MessageCollection.provided_version_empty_whitespace())
72
+ def __create_figlet(self) -> tuple:
73
+
74
+ """Returns a tuple containing the figlet and its width."""
75
+
76
+ lines : list[str] = [
77
+ "'##::: ##:'##:::::'##:'########:::'######::",
78
+ " ###:: ##: ##:'##: ##: ##.... ##:'##... ##:",
79
+ " ####: ##: ##: ##: ##: ##:::: ##: ##:::..::",
80
+ " ## ## ##: ##: ##: ##: ##:::: ##:. ######::",
81
+ " ##. ####: ##: ##: ##: ##:::: ##::..... ##:",
82
+ " ##:. ###: ##: ##: ##: ##:::: ##:'##::: ##:",
83
+ " ##::. ##:. ###. ###:: ########::. ######::",
84
+ "..::::..:::...::...:::........::::......:::"
85
+ ]
86
+
87
+ return (os.linesep.join(lines), len(lines[0]))
88
+ def __create_frame(self, version: str, max_length: int) -> tuple:
89
+
90
+ """Returns a tuple containing the frame of the figlet."""
91
+
92
+ version_token : str = f"Version: {version}"
93
+
94
+ margin_length : int = 5
95
+ total_length : int = max_length - len(version_token) - margin_length
96
+
97
+ top_line : str = "*" * max_length
98
+ bottom_line : str = f"{top_line[:total_length]}{version_token}{'*' * margin_length}"
99
+
100
+ return (top_line, bottom_line)
101
+
102
+ def create_standard(self, version : str) -> str:
103
+
104
+ """Creates the standard ASCII banner."""
105
+
106
+ self.__validate(version)
107
+
108
+ figlet, max_length = self.__create_figlet()
109
+ top_line, bottom_line = self.__create_frame(version, max_length)
110
+
111
+ ascii_banner : str = os.linesep.join([
112
+ top_line,
113
+ figlet,
114
+ bottom_line,
115
+ ""
116
+ ])
117
+
118
+ return ascii_banner
119
+ def create_mini(self, version : str) -> str:
120
+
121
+ """
122
+ Creates the mini ASCII banner:
123
+
124
+ ***************
125
+ * NWDS v1.0.0 *
126
+ ***************
127
+ """
128
+
129
+ self.__validate(version)
130
+
131
+ assembly_name : str = "NWDS"
132
+ middle_line : str = f"* {assembly_name} v{version} *"
133
+
134
+ top_line : str = "*" * len(middle_line)
135
+ bottom_line : str = top_line
136
+
137
+ ascii_banner : str = os.linesep.join([
138
+ top_line,
139
+ middle_line,
140
+ bottom_line,
141
+ ""
142
+ ])
143
+
144
+ return ascii_banner
145
+ def create(self, version : str, terminal_width : int) -> str:
146
+
147
+ """Creates either a standard or mini ASCII banner depending on the terminal width."""
148
+
149
+ _, max_length = self.__create_figlet()
150
+
151
+ if max_length <= terminal_width:
152
+ return self.create_standard(version)
153
+ else:
154
+ return self.create_mini(version)
155
+ class TerminalWindowManager:
156
+
157
+ '''Handles terminal window size.'''
158
+
159
+ __shutil_width_function : Callable[[], Optional[int]]
160
+ __stty_width_function : Callable[[], Optional[int]]
161
+
162
+ cutoff_width : Final[int] = 70
163
+
164
+ @staticmethod
165
+ def default_shutil_width_function() -> Optional[int]:
166
+
167
+ """Get terminal width using shutil (multi-platform)."""
168
+
169
+ try:
170
+
171
+ terminal_width : int = get_terminal_size().columns
172
+
173
+ return terminal_width
174
+
175
+ except:
176
+ return None
177
+
178
+ @staticmethod
179
+ def default_stty_width_function() -> Optional[int]:
180
+
181
+ """Get terminal width using stty command (Linux)."""
182
+
183
+ try:
184
+
185
+ process : CompletedProcess[str] = subprocess.run(
186
+ ["/bin/sh", "-c", "stty size | cut -d' ' -f2"],
187
+ capture_output = True,
188
+ text = True,
189
+ check = False,
190
+ )
191
+
192
+ stty_output : str = process.stdout.strip()
193
+ terminal_width : int = int(stty_output)
194
+
195
+ if terminal_width >= 0:
196
+ return terminal_width
197
+
198
+ return None
199
+ except:
200
+ return None
201
+
202
+ def __init__(
203
+ self,
204
+ shutil_width_function : Optional[Callable[[], Optional[int]]] = None,
205
+ stty_width_function : Optional[Callable[[], Optional[int]]] = None,
206
+ ) -> None:
207
+
208
+ if shutil_width_function is None:
209
+ shutil_width_function = self.default_shutil_width_function
210
+
211
+ if stty_width_function is None:
212
+ stty_width_function = self.default_stty_width_function
213
+
214
+ self.__shutil_width_function = shutil_width_function
215
+ self.__stty_width_function = stty_width_function
216
+
217
+ def get_or_cutoff(self) -> int:
218
+
219
+ terminal_width : Optional[int] = self.__shutil_width_function()
220
+
221
+ if terminal_width is None:
222
+ terminal_width = self.__stty_width_function()
223
+
224
+ if terminal_width is None:
225
+ terminal_width = self.cutoff_width
226
+
227
+ return terminal_width
228
+ class CLIValidator:
229
+
230
+ '''Handles CLI argument validation.'''
231
+
232
+ def validate_file_path(self, file_path: str) -> str:
233
+
234
+ '''Returns file_path or raises Exception.'''
235
+
236
+ Validator().validate_file_path(file_path)
237
+
238
+ return file_path
239
+ class APFactory():
240
+
241
+ '''Encapsulates all the logic related to the creation of a custom instance of argparse.ArgumentParser.'''
242
+
243
+ __cli_validator : CLIValidator
244
+
245
+ def __init__(self, cli_validator : CLIValidator = CLIValidator()) -> None:
246
+ self.__cli_validator = cli_validator
247
+
248
+ def create(self) -> ArgumentParser:
249
+
250
+ '''
251
+ Creates a custom instance of argparse.ArgumentParser.
252
+
253
+ The "prog" argument is not provided in order to make the "usage" statement dynamic:
254
+
255
+ usage: nwdocstringcheckingcli.py [-h] --file_path FILE_PATH [--exclude EXCLUDE]
256
+ '''
257
+
258
+ argument_parser : ArgumentParser = ArgumentParser(description = CLI_DESCRIPTION)
259
+
260
+ argument_parser.add_argument(
261
+ *CLISTRING.OPTION_FILEPATH_FLAGS,
262
+ dest = CLISTRING.OPTION_FILEPATH_DEST,
263
+ required = CLISTRING.OPTION_FILEPATH_REQUIRED,
264
+ help = CLISTRING.OPTION_FILEPATH_HELP,
265
+ type = self.__cli_validator.validate_file_path)
266
+
267
+ argument_parser.add_argument(
268
+ *CLISTRING.OPTION_EXCLUDE_FLAGS,
269
+ dest = CLISTRING.OPTION_EXCLUDE_DEST,
270
+ required = CLISTRING.OPTION_EXCLUDE_REQUIRED,
271
+ help = CLISTRING.OPTION_EXCLUDE_HELP,
272
+ default = CLISTRING.OPTION_EXCLUDE_DEFAULT,
273
+ action = CLISTRING.OPTION_EXCLUDE_ACTION)
274
+
275
+ return argument_parser
276
+ class CLIManager():
277
+
278
+ '''Collects all the logic related to the CLI management.'''
279
+
280
+ __ap_factory : APFactory
281
+ __ascii_banner_manager : AsciiBannerManager
282
+ __docstring_checker : DocStringChecker
283
+ __tw_manager : TerminalWindowManager
284
+ __logging_function : Callable[[str], None]
285
+
286
+ def __init__(
287
+ self,
288
+ ap_factory : APFactory = APFactory(),
289
+ ascii_banner_manager : AsciiBannerManager = AsciiBannerManager(),
290
+ docstring_checker : DocStringChecker = DocStringChecker(),
291
+ tw_manager : TerminalWindowManager = TerminalWindowManager(),
292
+ logging_function : Callable[[str], None] = lambda msg : print(msg)) -> None:
293
+
294
+ self.__ap_factory = ap_factory
295
+ self.__ascii_banner_manager = ascii_banner_manager
296
+ self.__docstring_checker = docstring_checker
297
+ self.__tw_manager = tw_manager
298
+ self.__logging_function = logging_function
299
+
300
+ def __log_ascii_banner(self) -> None:
301
+
302
+ """Logs the ascii banner."""
303
+
304
+ terminal_width : int = self.__tw_manager.get_or_cutoff()
305
+ ascii_banner : str = self.__ascii_banner_manager.create(PROJECT_VERSION, terminal_width)
306
+
307
+ self.__logging_function("")
308
+ self.__logging_function(ascii_banner)
309
+ def __log_namespace(self, args : Namespace):
310
+
311
+ '''Logs the provided args.'''
312
+
313
+ for key, value in vars(args).items():
314
+ self.__logging_function(f"{key}: '{value}'")
315
+
316
+ self.__logging_function("")
317
+ def __log_docstrings(self, missing: list[str]) -> None:
318
+
319
+ '''Logs missing docstrings.'''
320
+
321
+ if missing:
322
+ for method in missing:
323
+ self.__logging_function(method)
324
+ else:
325
+ self.__logging_function(_MessageCollection.all_methods_have_docstrings())
326
+
327
+ def run_and_log(self) -> None:
328
+
329
+ '''
330
+ Extract the missing docstrings and log them.
331
+
332
+ The SystemExit exception occurs when a required option is not provided.
333
+ SystemExit doesn't inherit from Exception and has no message, therefore we need to handle it accordingly.
334
+ '''
335
+
336
+ try:
337
+
338
+ self.__log_ascii_banner()
339
+
340
+ argument_parser : ArgumentParser = self.__ap_factory.create()
341
+ args : Namespace = argument_parser.parse_args()
342
+
343
+ self.__log_namespace(args)
344
+
345
+ missing : list[str] = self.__docstring_checker.run(file_path = args.file_path, exclude = args.exclude)
346
+
347
+ self.__log_docstrings(missing)
348
+
349
+ except (Exception, SystemExit) as e:
350
+
351
+ if not isinstance(e, SystemExit):
352
+ self.__logging_function(str(e))
353
+
354
+ # MAIN
355
+ def main(): CLIManager().run_and_log()
356
+
357
+ if __name__ == "__main__":
358
+ main()
setupinfo.py ADDED
@@ -0,0 +1,14 @@
1
+ '''Contains project information.'''
2
+
3
+ # INFORMATION
4
+ PROJECT_VERSION : str = "2.0.1"
5
+ PROJECT_AUTHOR : str = "numbworks"
6
+ PROJECT_ALIAS : str = "nwds"
7
+
8
+ LIBRARY_NAME : str = "nwdocstringchecking"
9
+ LIBRARY_DESCRIPTION : str = "A library designed to identify which methods in a Python file are missing docstrings."
10
+
11
+ CLI_NAME : str = "nwdocstringcheckingcli"
12
+ CLI_DESCRIPTION : str = "A CLI application designed to identify which methods in a Python file are missing docstrings."
13
+
14
+ PROJECT_URL : str = f"https://github.com/{PROJECT_AUTHOR}/{LIBRARY_NAME}"