twd-m4sc0 1.5.2__tar.gz → 1.5.4__tar.gz

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,155 @@
1
+ Metadata-Version: 2.1
2
+ Name: twd_m4sc0
3
+ Version: 1.5.4
4
+ Summary: A tool to temporarily save and go to a working directory
5
+ Home-page: https://github.com/m4sc0/twd
6
+ Author: m4sc0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+
14
+
15
+ # twd-m4sc0
16
+
17
+ `twd-m4sc0` is a command-line tool that allows you to temporarily save a working directory and easily navigate back to it. It's designed for developers and users who frequently need to switch between directories in the terminal.
18
+
19
+ ## Features
20
+
21
+ - Save the current or specified working directory.
22
+ - Go back to a saved directory using an optional alias.
23
+ - List all saved directories with metadata.
24
+ - Unset and delete saved directories.
25
+ - Integrates with your shell for seamless directory management.
26
+
27
+ ## Installation
28
+
29
+ ### Installation using `pip`:
30
+
31
+ 1. Install the package from the `pypi` repository:
32
+
33
+ ```bash
34
+ pip install twd-m4sc0
35
+ ```
36
+
37
+ 2. Add the following line to your `.bashrc` or `.zshrc` to set up the shell function:
38
+
39
+ > Since 1.5.4 you can also set a different command for the `twd` program by replacing `[alias]` in the following code by your custom alias
40
+
41
+ ```bash
42
+ eval $(python3 -m twd --shell [alias])
43
+ ```
44
+
45
+ 3. Exit and reopen the terminal or reload using:
46
+
47
+ ```bash
48
+ source ~/.bashrc
49
+ # or
50
+ source ~/.zshrc
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ### Save a directory
56
+
57
+ - Save the current directory or a specified directory:
58
+
59
+ ```bash
60
+ twd -s [path] [alias]
61
+ ```
62
+
63
+ If no path is specified, the current directory is saved. The alias is optional, and if not provided, an auto-generated ID will be used.
64
+
65
+ ### Go to a saved directory
66
+
67
+ - Navigate to a saved directory using an optional alias:
68
+
69
+ ```bash
70
+ twd -g [alias]
71
+ ```
72
+
73
+ If no alias is provided, the most recently saved directory will be used. If an alias is provided, it will navigate to the directory associated with that alias.
74
+
75
+ ### List saved directories
76
+
77
+ - Display a list of all saved directories:
78
+
79
+ ```bash
80
+ twd -l
81
+ ```
82
+
83
+ ### Unset the TWD and delete the data file
84
+
85
+ - Unset and delete the saved directories:
86
+
87
+ ```bash
88
+ twd -u
89
+ ```
90
+
91
+ You can force this action using the `--force` flag to avoid accidental execution.
92
+
93
+ ```bash
94
+ twd -u --force
95
+ ```
96
+
97
+ ### Optional Parameters
98
+
99
+ #### Simple Output
100
+
101
+ For cleaner, minimal output intended for scripting or piping.
102
+
103
+ - Example with `--simple-output`:
104
+
105
+ ```bash
106
+ twd -s --simple-output
107
+ /home/user/.config
108
+ ```
109
+
110
+ - Example without `--simple-output`:
111
+
112
+ ```bash
113
+ Saved TWD to /home/user/.config
114
+ ```
115
+
116
+ #### No Output
117
+
118
+ Suppresses all output (including confirmation messages).
119
+
120
+ - Example with `--no-output`:
121
+
122
+ ```bash
123
+ twd -s --no-output
124
+ # No output
125
+ ```
126
+
127
+ #### Force
128
+
129
+ Use the `--force` flag to force certain actions, such as when unsetting directories with the `-u` flag.
130
+
131
+ - Example:
132
+
133
+ ```bash
134
+ twd -u --force
135
+ TWD File deleted and TWD unset
136
+ ```
137
+
138
+ ## Contribution
139
+
140
+ To set up a development environment:
141
+
142
+ 1. Clone the repository:
143
+
144
+ ```bash
145
+ git clone https://github.com/m4sc0/twd
146
+ cd twd
147
+ ```
148
+
149
+ 2. Install the package in editable mode using `pip`:
150
+
151
+ ```bash
152
+ pip install -e .
153
+ ```
154
+
155
+ 3. Make your changes, and contribute!
@@ -0,0 +1,142 @@
1
+
2
+ # twd-m4sc0
3
+
4
+ `twd-m4sc0` is a command-line tool that allows you to temporarily save a working directory and easily navigate back to it. It's designed for developers and users who frequently need to switch between directories in the terminal.
5
+
6
+ ## Features
7
+
8
+ - Save the current or specified working directory.
9
+ - Go back to a saved directory using an optional alias.
10
+ - List all saved directories with metadata.
11
+ - Unset and delete saved directories.
12
+ - Integrates with your shell for seamless directory management.
13
+
14
+ ## Installation
15
+
16
+ ### Installation using `pip`:
17
+
18
+ 1. Install the package from the `pypi` repository:
19
+
20
+ ```bash
21
+ pip install twd-m4sc0
22
+ ```
23
+
24
+ 2. Add the following line to your `.bashrc` or `.zshrc` to set up the shell function:
25
+
26
+ > Since 1.5.4 you can also set a different command for the `twd` program by replacing `[alias]` in the following code by your custom alias
27
+
28
+ ```bash
29
+ eval $(python3 -m twd --shell [alias])
30
+ ```
31
+
32
+ 3. Exit and reopen the terminal or reload using:
33
+
34
+ ```bash
35
+ source ~/.bashrc
36
+ # or
37
+ source ~/.zshrc
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### Save a directory
43
+
44
+ - Save the current directory or a specified directory:
45
+
46
+ ```bash
47
+ twd -s [path] [alias]
48
+ ```
49
+
50
+ If no path is specified, the current directory is saved. The alias is optional, and if not provided, an auto-generated ID will be used.
51
+
52
+ ### Go to a saved directory
53
+
54
+ - Navigate to a saved directory using an optional alias:
55
+
56
+ ```bash
57
+ twd -g [alias]
58
+ ```
59
+
60
+ If no alias is provided, the most recently saved directory will be used. If an alias is provided, it will navigate to the directory associated with that alias.
61
+
62
+ ### List saved directories
63
+
64
+ - Display a list of all saved directories:
65
+
66
+ ```bash
67
+ twd -l
68
+ ```
69
+
70
+ ### Unset the TWD and delete the data file
71
+
72
+ - Unset and delete the saved directories:
73
+
74
+ ```bash
75
+ twd -u
76
+ ```
77
+
78
+ You can force this action using the `--force` flag to avoid accidental execution.
79
+
80
+ ```bash
81
+ twd -u --force
82
+ ```
83
+
84
+ ### Optional Parameters
85
+
86
+ #### Simple Output
87
+
88
+ For cleaner, minimal output intended for scripting or piping.
89
+
90
+ - Example with `--simple-output`:
91
+
92
+ ```bash
93
+ twd -s --simple-output
94
+ /home/user/.config
95
+ ```
96
+
97
+ - Example without `--simple-output`:
98
+
99
+ ```bash
100
+ Saved TWD to /home/user/.config
101
+ ```
102
+
103
+ #### No Output
104
+
105
+ Suppresses all output (including confirmation messages).
106
+
107
+ - Example with `--no-output`:
108
+
109
+ ```bash
110
+ twd -s --no-output
111
+ # No output
112
+ ```
113
+
114
+ #### Force
115
+
116
+ Use the `--force` flag to force certain actions, such as when unsetting directories with the `-u` flag.
117
+
118
+ - Example:
119
+
120
+ ```bash
121
+ twd -u --force
122
+ TWD File deleted and TWD unset
123
+ ```
124
+
125
+ ## Contribution
126
+
127
+ To set up a development environment:
128
+
129
+ 1. Clone the repository:
130
+
131
+ ```bash
132
+ git clone https://github.com/m4sc0/twd
133
+ cd twd
134
+ ```
135
+
136
+ 2. Install the package in editable mode using `pip`:
137
+
138
+ ```bash
139
+ pip install -e .
140
+ ```
141
+
142
+ 3. Make your changes, and contribute!
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="twd_m4sc0",
5
- version="1.5.2",
5
+ version="1.5.4",
6
6
  packages=find_packages(),
7
7
  entry_points={
8
8
  "console_scripts": [
@@ -18,7 +18,7 @@ setup(
18
18
  "License :: OSI Approved :: MIT License",
19
19
  "Operating System :: OS Independent",
20
20
  ],
21
- python_requires='>=3.6',
21
+ python_requires=">=3.6",
22
22
  long_description=open("README.md").read(),
23
23
  long_description_content_type="text/markdown",
24
24
  )
@@ -0,0 +1,47 @@
1
+ import os
2
+ import time
3
+
4
+
5
+ def format_message(message, config):
6
+ """Format the log message according to the provided config."""
7
+ result = config.get("log_format", "[$T]: $M")
8
+ result = result.replace("$T", time.strftime("%Y-%m-%d %H:%M:%S"))
9
+ result = result.replace("$M", message)
10
+ return result
11
+
12
+
13
+ def ensure_directory_exists(file_path):
14
+ """Ensure the directory for the given file path exists."""
15
+ directory = os.path.dirname(file_path)
16
+ if not os.path.exists(directory):
17
+ os.makedirs(directory, exist_ok=True)
18
+
19
+
20
+ def write_log(message, config):
21
+ """Write log message to the log file specified in the config."""
22
+ log_file = os.path.expanduser(config.get("log_file"))
23
+ log_file = os.path.abspath(log_file)
24
+ ensure_directory_exists(log_file) # Ensure the directory exists
25
+ with open(log_file, "a+") as f:
26
+ f.write(message + "\n")
27
+
28
+
29
+ def write_error(message, config):
30
+ """Write error message to the error file specified in the config."""
31
+ error_file = os.path.expanduser(config.get("error_file"))
32
+ error_file = os.path.abspath(error_file)
33
+ ensure_directory_exists(error_file) # Ensure the directory exists
34
+ with open(error_file, "a+") as f:
35
+ f.write(message + "\n")
36
+
37
+
38
+ def log(message, config):
39
+ """Log the message using the provided config."""
40
+ formatted_message = format_message(message, config)
41
+ write_log(formatted_message, config)
42
+
43
+
44
+ def error(message, config):
45
+ """Log the error using the provided config."""
46
+ formatted_message = format_message(message, config)
47
+ write_error(formatted_message, config)
@@ -0,0 +1,348 @@
1
+ import os
2
+ import argparse
3
+ import json
4
+ import hashlib
5
+ import time
6
+ import re
7
+ from importlib.metadata import version, PackageNotFoundError
8
+ from .logger import log, error
9
+
10
+ TWD_DIR = os.path.join(os.path.expanduser("~"), ".twd")
11
+ CONFIG_FILE = os.path.join(TWD_DIR, "config")
12
+
13
+ DEFAULT_CONFIG = {
14
+ "data_file": os.path.expanduser("~/.twd/data"),
15
+ "output_behaviour": 2,
16
+ "log_file": os.path.expanduser("~/.twd/log"),
17
+ "error_file": os.path.expanduser("~/.twd/error"),
18
+ "log_format": "[$T]: $M",
19
+ }
20
+
21
+ # os.makedirs(TWD_DIR, exist_ok=True)
22
+
23
+
24
+ def create_alias_id():
25
+ data = str(time.time()) + str(os.urandom(16))
26
+ return hashlib.sha256(data.encode()).hexdigest()[:12]
27
+
28
+
29
+ def load_config():
30
+ if not os.path.exists(CONFIG_FILE):
31
+ with open(CONFIG_FILE, "w") as file:
32
+ json.dump(DEFAULT_CONFIG, file, indent=4)
33
+ return DEFAULT_CONFIG
34
+ else:
35
+ with open(CONFIG_FILE, "r") as file:
36
+ try:
37
+ return json.load(file)
38
+ except json.JSONDecodeError as e:
39
+ error(f"Error loading config: {e}", DEFAULT_CONFIG)
40
+ return DEFAULT_CONFIG
41
+
42
+
43
+ CONFIG = load_config()
44
+
45
+ TWD_FILE = os.path.expanduser(CONFIG.get("data_file", "~/.twd/data"))
46
+
47
+
48
+ def ensure_data_file_exists():
49
+ if not os.path.exists(TWD_FILE):
50
+ try:
51
+ with open(TWD_FILE, "w") as f:
52
+ json.dump({}, f)
53
+ except OSError as e:
54
+ error(f"Error creating data file: {e}", CONFIG)
55
+
56
+ log_file = os.path.expanduser(CONFIG.get("log_file"))
57
+ error_file = os.path.expanduser(CONFIG.get("error_file"))
58
+
59
+ if not os.path.exists(log_file):
60
+ try:
61
+ with open(log_file, "w+") as f:
62
+ f.write("")
63
+ except OSError as e:
64
+ error(f"Error creating log file: {e}", CONFIG)
65
+
66
+ if not os.path.exists(error_file):
67
+ try:
68
+ with open(error_file, "w+") as f:
69
+ f.write("")
70
+ except OSError as e:
71
+ error(f"Error creating error file: {e}", CONFIG)
72
+
73
+
74
+ ensure_data_file_exists()
75
+
76
+
77
+ def get_absolute_path(path):
78
+ try:
79
+ return os.path.abspath(path)
80
+ except Exception as e:
81
+ error(f"Error getting absolute path for {path}: {e}", CONFIG)
82
+ raise
83
+
84
+
85
+ def validate_alias(alias):
86
+ """Ensure the alias contains only valid characters."""
87
+ if not re.match(r"^[\w-]+$", alias):
88
+ error(f"Invalid alias provided: {alias}", CONFIG)
89
+ raise ValueError(
90
+ f"Invalid alias: '{alias}'. Aliases can only contain alphanumeric characters, dashes, and underscores."
91
+ )
92
+ return alias
93
+
94
+
95
+ def output_handler(
96
+ message=None, path=None, output=True, simple_output=False, message_type=0
97
+ ):
98
+ log(f"Type: {message_type}, Msg: {message or path}", CONFIG)
99
+
100
+ if not output or CONFIG["output_behaviour"] == 0:
101
+ return
102
+
103
+ if not message and not path:
104
+ return
105
+
106
+ if message_type == 1:
107
+ print(f"1;{message}")
108
+ elif message_type == 0:
109
+ if simple_output and path:
110
+ print(f"0;{path}")
111
+ elif not simple_output and message:
112
+ print(f"0;{message}")
113
+
114
+
115
+ def save_directory(path=None, alias=None, output=True, simple_output=False):
116
+ if path is None:
117
+ path = os.getcwd()
118
+ else:
119
+ path = get_absolute_path(path)
120
+
121
+ if alias:
122
+ alias = validate_alias(alias)
123
+
124
+ try:
125
+ with open(TWD_FILE, "r") as f:
126
+ data = json.load(f)
127
+ except json.JSONDecodeError as e:
128
+ error(f"Error reading TWD file: {e}", CONFIG)
129
+ data = {}
130
+
131
+ alias_id = create_alias_id()
132
+ data[alias_id] = {
133
+ "path": path,
134
+ "alias": alias if alias else alias_id,
135
+ "created_at": time.time(),
136
+ }
137
+
138
+ try:
139
+ with open(TWD_FILE, "w") as f:
140
+ json.dump(data, f, indent=4)
141
+ except OSError as e:
142
+ error(f"Error writing to TWD file: {e}", CONFIG)
143
+ raise
144
+
145
+ output_handler(
146
+ f"Saved TWD to {path} with alias '{alias or alias_id}'",
147
+ path,
148
+ output,
149
+ simple_output,
150
+ )
151
+
152
+
153
+ def load_directory():
154
+ if not os.path.exists(TWD_FILE):
155
+ return None
156
+
157
+ try:
158
+ with open(TWD_FILE, "r") as f:
159
+ return json.load(f)
160
+ except json.JSONDecodeError as e:
161
+ error(f"Error loading TWD file: {e}", CONFIG)
162
+ return None
163
+
164
+
165
+ def go_to_directory(alias=None, output=True, simple_output=False):
166
+ dirs = load_directory()
167
+
168
+ if not dirs:
169
+ output_handler("No TWD found", None, output, simple_output)
170
+ return 1
171
+ else:
172
+ for entry_id, entry in dirs.items():
173
+ if "alias" in entry and entry["alias"] and entry["alias"] == alias:
174
+ TWD = entry["path"]
175
+
176
+ if os.path.exists(TWD):
177
+ output_handler(
178
+ f"cd {TWD}", TWD, output, simple_output, message_type=1
179
+ )
180
+ return 0
181
+ else:
182
+ error(f"Directory does not exist: {TWD}", CONFIG)
183
+ output_handler(
184
+ f"Directory does not exist: {TWD}", None, output, simple_output
185
+ )
186
+ return 1
187
+
188
+ output_handler("No TWD with alias found", None, output, simple_output)
189
+ return 1
190
+
191
+
192
+ def show_directory(output=True, simple_output=False):
193
+ dirs = load_directory()
194
+
195
+ if not dirs:
196
+ output_handler("No TWD set", None, output, simple_output)
197
+ return
198
+
199
+ max_alias_len = max(len(entry["alias"]) for entry in dirs.values()) if dirs else 0
200
+ max_id_len = max(len(alias_id) for alias_id in dirs.keys()) if dirs else 0
201
+ max_path_len = max(len(entry["path"]) for entry in dirs.values()) if dirs else 0
202
+
203
+ header = f"{'Alias'.ljust(max_alias_len)} {'ID'.ljust(max_id_len)} {'Path'.ljust(max_path_len)} Created At"
204
+ print(header)
205
+ print("-" * len(header))
206
+
207
+ for alias_id, entry in dirs.items():
208
+ alias = entry["alias"].ljust(max_alias_len)
209
+ path = entry["path"].ljust(max_path_len)
210
+ created_at = time.strftime(
211
+ "%Y-%m-%d %H:%M:%S", time.localtime(entry["created_at"])
212
+ )
213
+ alias_id_str = alias_id.ljust(max_id_len)
214
+ output_handler(
215
+ f"{alias} {alias_id_str} {path} {created_at}",
216
+ None,
217
+ output,
218
+ simple_output,
219
+ )
220
+
221
+
222
+ def unset_directory(output=True, simple_output=False, force=False):
223
+ if not os.path.exists(TWD_FILE):
224
+ output_handler(f"No TWD file found", None, output, simple_output)
225
+ else:
226
+ if not force:
227
+ output_handler(
228
+ r"""If you want to execute deleting and therefore unsetting all set TWD's, please use "--force" or "-f" and run again.
229
+
230
+
231
+ This feature is to prevent accidental execution.""",
232
+ None,
233
+ True,
234
+ False,
235
+ )
236
+ return
237
+ try:
238
+ os.remove(TWD_FILE)
239
+ except OSError as e:
240
+ error(f"Error deleting TWD file: {e}", CONFIG)
241
+ raise
242
+ output_handler(f"TWD File deleted and TWD unset", None, output, simple_output)
243
+
244
+
245
+ def get_package_version():
246
+ try:
247
+ return version("twd_m4sc0")
248
+ except PackageNotFoundError as e:
249
+ error(f"Package version not found: {e}", CONFIG)
250
+ return "Unknown version"
251
+
252
+
253
+ def main():
254
+ global TWD_FILE
255
+
256
+ parser = argparse.ArgumentParser(
257
+ description="Temporarily save and navigate to working directories."
258
+ )
259
+
260
+ # Positional arguments
261
+ parser.add_argument("directory", nargs="?", help="Directory to save")
262
+ parser.add_argument(
263
+ "alias", nargs="?", help="Alias for the saved directory (optional)"
264
+ )
265
+
266
+ # Optional Arguments/Flags
267
+ parser.add_argument(
268
+ "-s",
269
+ "--save",
270
+ action="store_true",
271
+ help="Save the current or specified directory",
272
+ )
273
+ parser.add_argument("-d", "--dir", nargs="?", help="Directory to save")
274
+ parser.add_argument("-a", "--ali", nargs="?", help="Alias for the saved directory")
275
+ parser.add_argument(
276
+ "-g", "--go", nargs="?", const=None, help="Go to the saved directory"
277
+ )
278
+ parser.add_argument("-l", "--list", action="store_true", help="Show saved TWD")
279
+ parser.add_argument(
280
+ "-u", "--unset", action="store_true", help="Unset the saved TWD"
281
+ )
282
+ parser.add_argument(
283
+ "-v",
284
+ "--version",
285
+ action="version",
286
+ version=f"TWD Version: {get_package_version()}",
287
+ help="Show the current version of TWD installed",
288
+ )
289
+ parser.add_argument("-f", "--force", action="store_true", help="Force an action")
290
+ parser.add_argument(
291
+ "--shell", nargs="?", const="twd", help="Output shell function for integration"
292
+ )
293
+ parser.add_argument(
294
+ "--simple-output",
295
+ action="store_true",
296
+ help="Only print essential output (new directory, absolute path, etc.)",
297
+ )
298
+ parser.add_argument(
299
+ "--no-output",
300
+ action="store_true",
301
+ help="Prevents the console from sending output",
302
+ )
303
+ args = parser.parse_args()
304
+
305
+ output = not args.no_output
306
+ simple_output = args.simple_output
307
+
308
+ if args.shell:
309
+ print(rf"""
310
+ function {args.shell}() {{
311
+ output=$(python3 -m twd "$@");
312
+ while IFS= read -r line; do
313
+ if [[ -z "$line" ]]; then
314
+ continue;
315
+ fi;
316
+ type=$(echo "$line" | cut -d';' -f1);
317
+ message=$(echo "$line" | cut -d';' -f2-);
318
+ if [[ "$type" == "1" ]]; then
319
+ eval "$message";
320
+ else
321
+ echo "$message";
322
+ fi;
323
+ done <<< "$output";
324
+ }}
325
+ """)
326
+ return 0
327
+
328
+ directory = args.directory or args.dir
329
+ alias = args.alias or args.ali
330
+
331
+ if args.save:
332
+ if not directory:
333
+ directory = args.directory or os.getcwd()
334
+
335
+ alias = args.alias or args.ali
336
+
337
+ save_directory(directory, alias, output, simple_output)
338
+ elif args.go:
339
+ alias = args.go
340
+ return go_to_directory(alias, output, simple_output)
341
+ elif args.list:
342
+ show_directory(output, simple_output)
343
+ elif args.unset:
344
+ force = args.force
345
+ unset_directory(output, simple_output, force)
346
+ else:
347
+ parser.print_help()
348
+ return 1