reny 1.0.0__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.
reny/__init__.py ADDED
File without changes
reny/cli/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+ from importlib import metadata
16
+ import reny.cli.base.vchk
17
+ from reny.cli.base.bmp_options import BatchMPArgParser, BatchMPBaseCommands
18
+
19
+
20
+ class BatchMPDispatcher:
21
+ ''' Base BatchMP Commands Dispatcher
22
+ '''
23
+ def __init__(self):
24
+ self.option_parser = BatchMPArgParser()
25
+
26
+ # Dispatcher
27
+ def dispatch(self):
28
+ args = self.option_parser.parse_options()
29
+
30
+ if args['sub_cmd'] == BatchMPBaseCommands.VERSION:
31
+ self.print_version()
32
+
33
+ elif args['sub_cmd'] == BatchMPBaseCommands.INFO:
34
+ self.print_info()
35
+
36
+ else:
37
+ # nothing to dispatch
38
+ return False
39
+
40
+ return True
41
+
42
+ # Dispatched methods
43
+ def print_version(self):
44
+ ''' Prints BatchMP version info
45
+ '''
46
+ version = metadata.version("reny")
47
+ print('BatchMP tools version {}'.format(version))
48
+
49
+ def print_info(self):
50
+ print('\nBatch Media Processing Tools: {}'.format(self.option_parser.script_name))
51
+ print(self.option_parser.description)
52
+
53
+ def main():
54
+ ''' BatchMP entry point
55
+ '''
56
+ BatchMPDispatcher().dispatch()
57
+
58
+ if __name__ == '__main__':
59
+ main()
60
+
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+
16
+ """ Global options parsing:
17
+ [-r, --recursive] Recurse into nested folders
18
+ [-el, --end-level] End level for recursion into nested folders
19
+
20
+ [-in, --include] Include names pattern (Unix style)
21
+ [-ex, --exclude] Exclude names pattern (Unix style)
22
+ (excludes hidden files by default)
23
+ [-ad, --all-dirs] Prevent using Include/Exclude patterns on directories
24
+ [-af, --all-files] Prevent using Include/Exclude patterns on files
25
+ (shows hidden files excluded by default)
26
+
27
+ [-s, --sort]{na|nd|sa|sd} Sort order for files / folders (name | date, asc | desc)
28
+ [-ni, nested-indent] Indent for printing nested directories
29
+ [-q, --quiet] Do not visualise changes / show messages during processing
30
+ """
31
+
32
+ import os, sys, string
33
+ from argparse import ArgumentParser, HelpFormatter
34
+ from reny.commons.utils import strtobool
35
+ from urllib.parse import urlparse
36
+ from reny.commons.utils import MiscHelpers
37
+ from reny.fstools.fsutils import FSH
38
+ from reny.fstools.builders.fsentry import FSEntry, FSEntryDefaults
39
+
40
+
41
+
42
+ class BatchMPBaseCommands:
43
+ VERSION = 'version'
44
+ INFO = 'info'
45
+ PRINT = 'print'
46
+
47
+ @classmethod
48
+ def commands_meta(cls):
49
+ return ''.join(('{',
50
+ '{}, '.format(cls.INFO),
51
+ '{}'.format(cls.VERSION),
52
+ '}'))
53
+
54
+ class BatchMPArgParser:
55
+ def __init__(self):
56
+ self._script_name = 'Reny'
57
+ self._description = '''
58
+ Reny provides management of files, directories, etc...
59
+
60
+ Reny tools consist of three main command-line utilities.
61
+ For more information, run:
62
+ $ renamer -h
63
+ $ tagger -h
64
+ $ bmfp -h
65
+ '''
66
+
67
+ @property
68
+ def description(self):
69
+ return self._description
70
+
71
+ @property
72
+ def script_name(self):
73
+ return self._script_name
74
+
75
+ # Args parsing
76
+ def parse_options(self):
77
+ ''' Common workflow for parsing options
78
+ '''
79
+ parser = ArgumentParser(prog = self._script_name, description = self._description,
80
+ formatter_class=BatchMPHelpFormatter)
81
+
82
+ self.parse_global_options(parser)
83
+
84
+ self.parse_commands(parser)
85
+
86
+ args = vars(parser.parse_args())
87
+
88
+ self.check_args(args, parser)
89
+
90
+ return args
91
+
92
+ def parse_global_options(self, parser):
93
+ ''' Parses global options
94
+ '''
95
+ source_mode_group = parser.add_argument_group('Input source mode')
96
+ source_mode_group.add_argument("-d", "--dir", dest = "dir",
97
+ type = lambda d: self._is_valid_dir_path(parser, d),
98
+ help = "Source directory (default is current directory)",
99
+ default = os.curdir)
100
+ source_mode_group.add_argument("-f", "--file", dest = "file",
101
+ type = lambda f: self._is_valid_file_path(parser, f),
102
+ help = "File to process")
103
+
104
+ recursive_mode_group = parser.add_argument_group('Recursion mode')
105
+ recursive_mode_group.add_argument("-r", "--recursive", dest = "recursive",
106
+ help = "Recurse into nested folders",
107
+ action = 'store_true')
108
+ recursive_mode_group.add_argument("-el", "--end-level", dest = "end_level",
109
+ help = "End level for recursion into nested folders",
110
+ type = int,
111
+ default = 0)
112
+
113
+ include_mode_group = parser.add_argument_group('Filter files or folders')
114
+ include_mode_group.add_argument("-in", "--include", dest = "include",
115
+ help = "Include: Unix-style name patterns separated by ';'",
116
+ type = str,
117
+ default = FSEntryDefaults.DEFAULT_INCLUDE)
118
+ include_mode_group.add_argument("-ex", "--exclude", dest = "exclude",
119
+ help = "Exclude: Unix-style name patterns separated by ';' (excludes hidden files by default)",
120
+ type = str,
121
+ default = FSEntryDefaults.DEFAULT_EXCLUDE)
122
+ include_mode_group.add_argument("-ad", "--all-dirs", dest = "all_dirs",
123
+ help = "Disable Include/Exclude patterns on directories",
124
+ action = 'store_true')
125
+ include_mode_group.add_argument("-af", "--all-files", dest = "all_files",
126
+ help = "Disable Include/Exclude patterns on files (shows hidden files excluded by default)",
127
+ action = 'store_true')
128
+
129
+ media_types_group = parser.add_argument_group('File media types')
130
+ media_types_group.add_argument("-ft", "--file-type", dest = "file_type",
131
+ help = "File Media Type",
132
+ type = str,
133
+ choices = ['image', 'video', 'audio', 'media', 'nonmedia', 'playable', 'nonplayable', 'any'],
134
+ default = FSEntryDefaults.DEFAULT_FILE_TYPE)
135
+ media_types_group.add_argument("-ms", "--media-scan", dest = "media_scan",
136
+ help = "Scan for media types, instead of using file extensions (can take a long time)",
137
+ action = 'store_true')
138
+
139
+
140
+ # Add Default Miscellaneous Group
141
+ self._add_arg_misc_group(parser)
142
+
143
+ def parse_commands(self, parser):
144
+ ''' Specific commands parsing
145
+ '''
146
+ subparsers = parser.add_subparsers(dest = 'sub_cmd',
147
+ title = 'BatchMP commands',
148
+ metavar = BatchMPBaseCommands.commands_meta())
149
+ self._add_version(subparsers)
150
+ self._add_info(subparsers)
151
+
152
+ # Args checking
153
+ def check_cmd_args(self, args, parser,
154
+ show_help = False,
155
+ exit = False):
156
+ if not args.get('sub_cmd'):
157
+ if show_help:
158
+ parser.print_help()
159
+ if exit:
160
+ sys.exit(1)
161
+
162
+ # if not exiting, need to default
163
+ self.default_command(args, parser)
164
+
165
+ def default_command(self, args, parser):
166
+ args['sub_cmd'] = BatchMPBaseCommands.INFO
167
+
168
+ def check_args(self, args, parser):
169
+ ''' Validation of supplied CLI arguments
170
+ '''
171
+ # check if there is a cmd to execute
172
+ self.check_cmd_args(args, parser)
173
+
174
+ # if input source is a file, need to adjust
175
+ if args['file']:
176
+ args['dir'] = os.path.dirname(args['file'])
177
+ args['include'] = os.path.basename(args['file'])
178
+ args['exclude'] = ''
179
+ args['end_level'] = 0
180
+ args['all_files'] = False
181
+ args['all_dirs'] = False
182
+
183
+ # check recursion
184
+ if args['recursive'] and args['end_level'] == 0:
185
+ args['end_level'] = sys.maxsize
186
+
187
+
188
+ if args['media_scan']:
189
+ pass
190
+
191
+ if args['sub_cmd'] == BatchMPBaseCommands.PRINT:
192
+ if args['start_level'] != 0:
193
+ if args['file']:
194
+ print ('Start Level parameter requires a source directory\n Ignoring requested Start Level...')
195
+ args['start_level'] = 0
196
+ elif args['end_level'] < args['start_level']:
197
+ ''' print ('Start Level should be greater than or equal to the Recursion End Level Global Option\n'
198
+ '... Adjusting End Level to: {}'.format(args['start_level']))
199
+ '''
200
+ args['end_level'] = args['start_level']
201
+
202
+ # Internal Helpers
203
+ @staticmethod
204
+ def _is_valid_dir_path(parser, path_arg):
205
+ """ Checks if path_arg is a valid dir path
206
+ """
207
+ path_arg = FSH.full_path(path_arg)
208
+ if not (os.path.exists(path_arg) and os.path.isdir(path_arg)):
209
+ parser.error('"{}" does not seem to be an existing directory path'.format(path_arg))
210
+ else:
211
+ return path_arg
212
+
213
+ @staticmethod
214
+ def _is_valid_file_path(parser, path_arg):
215
+ """ Checks if path_arg is a valid file path
216
+ """
217
+ path_arg = FSH.full_path(path_arg)
218
+ if not (os.path.exists(path_arg) and os.path.isfile(path_arg)):
219
+ parser.error('"{}" does not seem to be an existing file path'.format(path_arg))
220
+ else:
221
+ return path_arg
222
+
223
+ @staticmethod
224
+ def _is_boolean(parser, bool_arg):
225
+ """ Checks if bool_arg can be interpreted as a boolean value
226
+ """
227
+ try:
228
+ bool_arg = True if strtobool(bool_arg) else False
229
+ except ValueError:
230
+ parser.error('"{}": Please enter a boolean value'.format(bool_arg))
231
+ return False
232
+
233
+ @staticmethod
234
+ def _is_valid_url(parser, url_arg):
235
+ url_parts = urlparse(url_arg)
236
+
237
+ def _parser_error():
238
+ parser.error('"{}": Please enter a valid URL'.format(url_arg))
239
+
240
+ if url_parts.scheme in (None, '') and url_parts.netloc in (None, ''):
241
+ _parser_error()
242
+
243
+ if url_parts.scheme == 'file':
244
+ if url_parts.netloc == '~':
245
+ fpath = '~{}'.format(url_parts.path)
246
+ else:
247
+ fpath = url_parts.path
248
+ return BatchMPArgParser._is_valid_file_path(parser, fpath)
249
+
250
+ if not set(url_parts.netloc).issubset(set(string.ascii_letters + string.digits + '-.')):
251
+ _parser_error()
252
+
253
+ if not url_parts.scheme in ['http', 'https', 'ftp', 'file']:
254
+ _parser_error()
255
+
256
+ return url_arg
257
+
258
+ @staticmethod
259
+ def _is_valid_url_or_file_path(parser, url_or_file_path_arg):
260
+ url_parts = urlparse(url_or_file_path_arg)
261
+ if url_parts.scheme in (None, '') and url_parts.netloc in (None, ''):
262
+ return BatchMPArgParser._is_valid_file_path(parser, url_or_file_path_arg)
263
+ else:
264
+ return BatchMPArgParser._is_valid_url(parser, url_or_file_path_arg)
265
+
266
+ @staticmethod
267
+ def _is_timedelta(parser, td_arg):
268
+ try:
269
+ td = MiscHelpers.time_delta(td_arg)
270
+ except ValueError:
271
+ parser.error('"{}": Please enter a valid value, ' \
272
+ 'in seconds or in the "hh:mm:ss[.xxx]" format'.format(td_arg))
273
+ return td
274
+
275
+ # Processing mode for relevant commands
276
+ @staticmethod
277
+ def _add_arg_display_curent_state_mode(parser):
278
+ parser.add_argument('-dc', '--display-current', dest = 'display_current',
279
+ help ='Unless in quiet mode, display current (pre-processing) state in the confirmation propmt',
280
+ action = 'store_true')
281
+
282
+ @staticmethod
283
+ def _add_arg_misc_group(parser):
284
+ misc_group = parser.add_argument_group('Miscellaneous')
285
+ misc_group.add_argument('-s', '--sort', dest = 'sort',
286
+ help = "Sorting for files ('na', i.e. by name ascending by default)",
287
+ type = str,
288
+ choices = ['na', 'nd', 'sa', 'sd'],
289
+ default = FSEntryDefaults.DEFAULT_SORT)
290
+ misc_group.add_argument('-ni', '--nested_indent', dest = 'nested_indent',
291
+ help = "Indent for printing nested directories",
292
+ type = str,
293
+ default = ' ')
294
+ misc_group.add_argument("-q", "--quiet", dest = 'quiet',
295
+ help = "Disable visualising changes & displaying info messages during processing",
296
+ action = 'store_true')
297
+
298
+ @staticmethod
299
+ def _add_version(parser):
300
+ ''' Adds the version command
301
+ '''
302
+ parser.add_parser(BatchMPBaseCommands.VERSION,
303
+ description = 'Displays BatchMP version info',
304
+ formatter_class=BatchMPHelpFormatter)
305
+
306
+ @staticmethod
307
+ def _add_info(parser):
308
+ ''' Adds the info command
309
+ '''
310
+ parser.add_parser(BatchMPBaseCommands.INFO,
311
+ description = 'Displays BatchMP info',
312
+ formatter_class=BatchMPHelpFormatter)
313
+
314
+
315
+
316
+ class BatchMPHelpFormatter(HelpFormatter):
317
+ ''' Custom ArgumentParser formatter
318
+ Disables double metavar display, showing it only for long-named options
319
+ '''
320
+ def _format_action_invocation(self, action):
321
+ if not action.option_strings:
322
+ metavar, = self._metavar_formatter(action, action.dest)(1)
323
+ return metavar
324
+ else:
325
+ parts = []
326
+ # if the Optional doesn't take a value, format is:
327
+ # -s, --long
328
+ if action.nargs == 0:
329
+ parts.extend(action.option_strings)
330
+
331
+ # if the Optional takes a value, format is:
332
+ # -s ARGS, --long ARGS
333
+ # change to
334
+ # -s, --long ARGS
335
+ else:
336
+ default = action.dest.upper()
337
+ args_string = self._format_args(action, default)
338
+ for option_string in action.option_strings:
339
+ #parts.append('%s %s' % (option_string, args_string))
340
+ parts.append('%s' % option_string)
341
+ parts[-1] += ' %s'%args_string
342
+ return ', '.join(parts)
reny/cli/base/vchk.py ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+ ''' Python version check
16
+ '''
17
+
18
+ from __future__ import print_function
19
+ import sys
20
+
21
+ def check_version():
22
+ if sys.version_info.major < 3:
23
+ print(\
24
+ '''
25
+ Batch Media Processing Tools require
26
+ Python version 3.6 or later.
27
+
28
+ You can create an isolated Python 3.6 environment
29
+ with the virtualenv tool:
30
+ http://docs.python-guide.org/en/latest/dev/virtualenvs
31
+
32
+ ''')
33
+ sys.exit(1)
34
+ elif sys.version_info.major == 3 and sys.version_info.minor < 6:
35
+ print(\
36
+ '''
37
+
38
+ Batch Media Processing Tools require
39
+ Python version 3.6 or later.
40
+
41
+ Please upgrade to the latest Python 3.x version.
42
+
43
+ ''')
44
+ sys.exit(1)
45
+
46
+ # check
47
+ check_version()
File without changes
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf8
3
+ ## Copyright (c) 2014 Arseniy Kuznetsov
4
+ ##
5
+ ## This program is free software; you can redistribute it and/or
6
+ ## modify it under the terms of the GNU General Public License
7
+ ## as published by the Free Software Foundation; either version 2
8
+ ## of the License, or (at your option) any later version.
9
+ ##
10
+ ## This program is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+
15
+ from reny.cli.base.bmp_dispatch import BatchMPDispatcher
16
+ from reny.cli.renamer.renamer_options import RenameArgParser, RenamerCommands
17
+ from reny.fstools.dirtools import DHandler
18
+ from reny.fstools.rename import Renamer
19
+ from reny.fstools.builders.fsprms import FSEntryParamsBase, FSEntryParamsExt, FSEntryParamsFlatten, FSEntryParamsOrganize
20
+ from reny.fstools.builders.fsb import FSEntryBuilderBase
21
+
22
+ class RenameDispatcher(BatchMPDispatcher):
23
+ ''' Renamer Commands Dispatcher
24
+ '''
25
+ def __init__(self):
26
+ self.option_parser = RenameArgParser()
27
+
28
+ # Dispatcher
29
+ def dispatch(self):
30
+ ''' Dispatches Renamer commands
31
+ '''
32
+ if not super().dispatch():
33
+ args = self.option_parser.parse_options()
34
+ if args['sub_cmd'] == RenamerCommands.PRINT:
35
+ self.print_dir(args)
36
+
37
+ elif args['sub_cmd'] == RenamerCommands.FLATTEN:
38
+ self.flatten(args)
39
+
40
+ elif args['sub_cmd'] == RenamerCommands.INDEX:
41
+ self.add_index(args)
42
+
43
+ elif args['sub_cmd'] == RenamerCommands.PAD:
44
+ self.pad(args)
45
+
46
+ elif args['sub_cmd'] == RenamerCommands.ADD_DATE:
47
+ self.add_date(args)
48
+
49
+ elif args['sub_cmd'] == RenamerCommands.ADD_TEXT:
50
+ self.add_text(args)
51
+
52
+ elif args['sub_cmd'] == RenamerCommands.REMOVE:
53
+ self.remove(args)
54
+
55
+ elif args['sub_cmd'] == RenamerCommands.REPLACE:
56
+ self.replace(args)
57
+
58
+ elif args['sub_cmd'] == RenamerCommands.CAPITALIZE:
59
+ self.capitalize(args)
60
+
61
+ elif args['sub_cmd'] == RenamerCommands.DELETE:
62
+ self.delete(args)
63
+
64
+ elif args['sub_cmd'] == RenamerCommands.STATS:
65
+ self.stats(args)
66
+
67
+ elif args['sub_cmd'] == RenamerCommands.ORGANIZE:
68
+ self.organize(args)
69
+
70
+ else:
71
+ print('Nothing to dispatch')
72
+ return False
73
+
74
+ return True
75
+
76
+ # Dispatched Methods
77
+ def print_dir(self, args):
78
+ # Check if organize view is requested
79
+ if args.get('by'):
80
+ fs_entry_params = FSEntryParamsOrganize(args)
81
+ DHandler.print_organized_view(fs_entry_params)
82
+ else:
83
+ fs_entry_params = FSEntryParamsBase(args)
84
+ DHandler.print_dir(fs_entry_params)
85
+
86
+ def stats(self, args):
87
+ fs_entry_params = FSEntryParamsBase(args)
88
+ DHandler.stats(fs_entry_params)
89
+
90
+ def flatten(self, args):
91
+ fs_entry_params = FSEntryParamsFlatten(args)
92
+ DHandler.flatten_folders(fs_entry_params)
93
+
94
+ def add_index(self, args):
95
+ fs_entry_params = FSEntryParamsExt(args)
96
+ Renamer.add_index(fs_entry_params,
97
+ as_prefix = not args['as_suffix'], join_str = args['join_str'],
98
+ start_from = args['start_from'], min_digits = args['min_digits'],
99
+ sequential = args['sequential'], by_directory = args['by_directory'])
100
+
101
+ def pad(self, args):
102
+ ''' Dispatch pad
103
+ '''
104
+ fs_entry_params = FSEntryParamsExt(args)
105
+ Renamer.pad(fs_entry_params, args['min_digits'])
106
+
107
+ def add_date(self, args):
108
+ fs_entry_params = FSEntryParamsExt(args)
109
+ Renamer.add_date(fs_entry_params,
110
+ as_prefix = args['as_prefix'], join_str = args['join_str'], format = args['format'])
111
+
112
+ def add_text(self, args):
113
+ fs_entry_params = FSEntryParamsExt(args)
114
+ Renamer.add_text(fs_entry_params,
115
+ text = args['text'], as_prefix = args['as_prefix'], join_str = args['join_str'])
116
+
117
+ def remove(self, args):
118
+ fs_entry_params = FSEntryParamsExt(args)
119
+ Renamer.remove_n_characters(fs_entry_params, num_chars = args['num_chars'], from_head = not args['from_tail'])
120
+
121
+ def replace(self, args):
122
+ fs_entry_params = FSEntryParamsExt(args)
123
+ Renamer.replace(fs_entry_params,
124
+ find_str = args['find_str'],
125
+ replace_str = args['replace_str'] if 'replace_str' in args else None,
126
+ case_insensitive = args['ignore_case'],
127
+ include_extension = args['include_extension'])
128
+
129
+ def capitalize(self, args):
130
+ fs_entry_params = FSEntryParamsExt(args)
131
+ Renamer.capitalize(fs_entry_params)
132
+
133
+ def delete(self, args):
134
+ fs_entry_params = FSEntryParamsExt(args)
135
+ Renamer.delete(fs_entry_params)
136
+
137
+ def organize(self, args):
138
+ fs_entry_params = FSEntryParamsOrganize(args)
139
+ DHandler.organize(fs_entry_params)
140
+
141
+ def main():
142
+ ''' Renamer entry point
143
+ '''
144
+ RenameDispatcher().dispatch()