chname 3.0.5__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.
chname/__init__.py ADDED
File without changes
chname/__main__.py ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python -B
2
+ # vi: set syntax=python ts=4 sw=4 sts=4 et ff=unix ai si :
3
+ #
4
+ # (c) Steven Scholnick <scholnicks@gmail.com>
5
+ # The chname source code is published under a MIT license.
6
+
7
+ """
8
+ chname: renames files in powerful ways.
9
+
10
+ Usage:
11
+ chname append [--dry-run --quiet --verbose] <suffix> <files>...
12
+ chname lower [--dry-run --quiet --verbose] <files>...
13
+ chname merge [--dry-run --quiet --verbose] <directory> <files>...
14
+ chname order [--dry-run --quiet --verbose] <files>...
15
+ chname remove [--dry-run --quiet --verbose] <pattern> <files>...
16
+ chname prepend [--dry-run --quiet --verbose] <prefix> <files>...
17
+ chname substitute [--dry-run --quiet --verbose] <old> <new> <files>...
18
+ chname titles [--dry-run --quiet --verbose] <input> <files>...
19
+ chname usage
20
+ chname (-h | --help)
21
+ chname --version
22
+
23
+ Options:
24
+ --dry-run Show what would be done, but don't actually do it
25
+ -h, --help Show this help screen
26
+ -q, --quiet Quiet mode
27
+ -v, --verbose Verbose mode
28
+ --version Prints the version
29
+ """
30
+
31
+ import os
32
+ import re
33
+ import sys
34
+ from pathlib import Path
35
+
36
+ from docopt import DocoptExit, docopt
37
+
38
+ arguments = {}
39
+
40
+ OPERATIONS = {
41
+ "append": lambda: append(),
42
+ "lower": lambda: lower(),
43
+ "merge": lambda: merge(),
44
+ "order": lambda: order(),
45
+ "remove": lambda: remove(),
46
+ "prepend": lambda: prepend(),
47
+ "substitute": lambda: substitute(),
48
+ "titles": lambda: titles(),
49
+ "usage": lambda: usage(),
50
+ }
51
+
52
+
53
+ def main() -> None:
54
+ """Main entry point for chname utility"""
55
+ try:
56
+ global arguments
57
+ arguments = docopt(__doc__, version="chname 3.0.5")
58
+ for operation, func in OPERATIONS.items():
59
+ if arguments[operation]:
60
+ func()
61
+
62
+ sys.exit(0)
63
+ except (DocoptExit, KeyError):
64
+ print(__doc__, file=sys.stderr)
65
+ sys.exit(1)
66
+ except KeyboardInterrupt:
67
+ sys.exit(1)
68
+
69
+
70
+ def substitute() -> None:
71
+ """Substitutes a pattern in the specified files"""
72
+ for filePath in arguments["<files>"]:
73
+ rename_file(filePath, filePath.replace(arguments["<old>"], arguments["<new>"]))
74
+
75
+
76
+ def append() -> None:
77
+ """Appends a suffix to the specified files"""
78
+ for filePath in arguments["<files>"]:
79
+ rename_file(filePath, f"{filePath}{arguments["<suffix>"]}")
80
+
81
+
82
+ def lower() -> None:
83
+ """Translates filenames to lowercase"""
84
+ for filePath in arguments["<files>"]:
85
+ rename_file(filePath, filePath.lower())
86
+
87
+
88
+ def prepend() -> None:
89
+ """To prepends a prefix to the specified files"""
90
+ for filePath in arguments["<files>"]:
91
+ p = Path(filePath)
92
+ rename_file(filePath, str(p.parent / f"{arguments['<prefix>']}{p.name}"))
93
+
94
+
95
+ def remove() -> None:
96
+ """Removes a pattern from the specified files"""
97
+ for filePath in arguments["<files>"]:
98
+ rename_file(filePath, re.sub(arguments["<pattern>"], r"", filePath))
99
+
100
+
101
+ def order() -> None:
102
+ """Orders the files"""
103
+ filenameTemplate = r"{num:02d} - {filename}" if len(arguments["<files>"]) < 100 else r"{num:04d} - {filename}"
104
+
105
+ for index, currentFilePath in enumerate(sorted(arguments["<files>"]), 1):
106
+ newFilePath = os.path.join(
107
+ os.path.dirname(currentFilePath),
108
+ filenameTemplate.format(num=index, filename=os.path.basename(currentFilePath)),
109
+ )
110
+ rename_file(currentFilePath, newFilePath)
111
+
112
+
113
+ def merge() -> None:
114
+ """Merges files into a single directory with standardized names"""
115
+ # determine the extension
116
+ extension = calculateExtension(arguments["<files>"])
117
+
118
+ # rename the files in argument specified order
119
+ for index, filename in enumerate(arguments["<files>"], 1):
120
+ new_file_name = os.path.join(arguments["<directory>"], f"file_{index:04d}" + extension)
121
+ rename_file(filename, new_file_name)
122
+
123
+
124
+ def calculateExtension(files) -> str:
125
+ """determines a single extension"""
126
+ extensions = set((os.path.splitext(f)[1].lower() for f in files))
127
+ if len(extensions) > 1:
128
+ raise SystemExit(f"Only one extension allowed. Found: {", ".join(extensions)}")
129
+
130
+ return extensions.pop()
131
+
132
+
133
+ def titles() -> None:
134
+ """Names files by using an input text file"""
135
+ extension = calculateExtension(arguments["<files>"])
136
+
137
+ titlesFilePath = Path(arguments["<input>"])
138
+ if not titlesFilePath.exists():
139
+ raise SystemExit(f"Titles file {titlesFilePath} does not exist")
140
+
141
+ with titlesFilePath.open("r") as fp:
142
+ exportFileNames = [line.strip() for line in fp if line.strip()]
143
+
144
+ if len(arguments["<files>"]) != len(exportFileNames):
145
+ raise SystemExit(
146
+ f"{arguments["<input>"]} filenames ({len(exportFileNames)}) and files length ({len(arguments["<files>"])}) do not match"
147
+ )
148
+
149
+ filenameTemplate = (
150
+ r"{num:02d} - {filename}{extension}"
151
+ if len(arguments["<files>"]) < 100
152
+ else r"{num:04d} - {filename}{extension}"
153
+ )
154
+
155
+ index: int = 1
156
+ for currentFilePath, newFileName in zip(arguments["<files>"], exportFileNames):
157
+ newFilePath = os.path.join(
158
+ os.path.dirname(currentFilePath),
159
+ filenameTemplate.format(num=index, filename=newFileName, extension=extension),
160
+ )
161
+ rename_file(currentFilePath, newFilePath)
162
+ index += 1
163
+
164
+
165
+ def rename_file(oldName, newName) -> None:
166
+ """Performs the actual file rename"""
167
+ if arguments["--verbose"] or arguments["--dry-run"]:
168
+ print(f"Renaming {oldName} to {newName}")
169
+
170
+ if not Path(oldName).exists():
171
+ if not arguments["--quiet"]:
172
+ print(f"File {oldName} does not exist", file=sys.stderr)
173
+ return
174
+
175
+ if not arguments["--dry-run"]:
176
+ os.rename(oldName, newName)
177
+
178
+
179
+ def usage() -> None:
180
+ print(
181
+ __doc__
182
+ + """
183
+ Merge
184
+ -----
185
+ To merge files from two different directories into the current directory:
186
+
187
+ chname --merge d1/* d2/*
188
+
189
+ Input Files: d1/file1.txt d1/file2.txt d2/file1.txt
190
+ Results: ./file_0001.txt ./file_0002.txt ./file_0003.txt
191
+
192
+ where file_0003.txt is d2/file3.txt
193
+
194
+ All of the files must have the same extension. The filename format is file_NUMBER.extension.
195
+
196
+
197
+ Order
198
+ -----
199
+
200
+ Adds a numerical prefix to sorted input files. Example:
201
+
202
+ chname --order filea.mp3 fileb.mp3
203
+
204
+ becomes:
205
+
206
+ 01 - filea.mp3 02 - fileb.mp3
207
+
208
+ """
209
+ )
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: chname
3
+ Version: 3.0.5
4
+ Summary: Renames files in powerful ways
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: rename,file,batch,bulk,utility,command-line,cli
8
+ Author: Steve Scholnick
9
+ Author-email: scholnicks@gmail.com
10
+ Requires-Python: >=3.13
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Topic :: Utilities
17
+ Requires-Dist: docopt-ng (>=0.9.0,<0.10.0)
18
+ Project-URL: Homepage, https://pypi.org/project/chname/
19
+ Project-URL: Issues, https://github.com/scholnicks/chname/issues
20
+ Project-URL: Repository, https://github.com/scholnicks/chname/
21
+ Description-Content-Type: text/markdown
22
+
23
+ # chname
24
+
25
+ chname renames files in powerful ways.
26
+
27
+ ```
28
+ Usage:
29
+ chname append [--dry-run --quiet --verbose] <suffix> <files>...
30
+ chname lower [--dry-run --quiet --verbose] <files>...
31
+ chname merge [--dry-run --quiet --verbose] <directory> <files>...
32
+ chname order [--dry-run --quiet --verbose] <files>...
33
+ chname remove [--dry-run --quiet --verbose] <pattern> <files>...
34
+ chname prepend [--dry-run --quiet --verbose] <prefix> <files>...
35
+ chname substitute [--dry-run --quiet --verbose] <old> <new> <files>...
36
+ chname titles [--dry-run --quiet --verbose] <input> <files>...
37
+ chname usage
38
+ chname (-h | --help)
39
+ chname --version
40
+
41
+ Options:
42
+ --dry-run Show what would be done, but don't actually do it
43
+ -h, --help Show this help screen
44
+ -q, --quiet Quiet mode
45
+ -v, --verbose Verbose mode
46
+ --version Prints the version
47
+ ```
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install chname
53
+ ```
54
+
55
+ ## License
56
+
57
+ chname is freeware released under the [MIT License](https://github.com/scholnicks/chname/blob/main/LICENSE).
58
+
@@ -0,0 +1,7 @@
1
+ chname/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ chname/__main__.py,sha256=YYZMHkAekUBn469kdKERzvtjx4T6bRaIcDa-vF2EH6A,6370
3
+ chname-3.0.5.dist-info/METADATA,sha256=iJ6tig2Pv-Z5JLv9svF2febiqxX_gFz_rempCQVp8DY,1943
4
+ chname-3.0.5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
5
+ chname-3.0.5.dist-info/entry_points.txt,sha256=l8ek6yn-OaGAak7ye2y-kPeGb6YMnYcwwHfQ1ygX6sk,47
6
+ chname-3.0.5.dist-info/licenses/LICENSE,sha256=wT_mfbxynx42y5DZ0-Kuf_pie3PaQPeK5nXUL8_V1WQ,1073
7
+ chname-3.0.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ chname=chname.__main__:main
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021- Steve Scholnick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.