twd-m4sc0 1.5.1__tar.gz → 1.5.3__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,153 @@
1
+ Metadata-Version: 2.1
2
+ Name: twd_m4sc0
3
+ Version: 1.5.3
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
+ ```bash
40
+ eval $(python3 -m twd --shell)
41
+ ```
42
+
43
+ 3. Exit and reopen the terminal or reload using:
44
+
45
+ ```bash
46
+ source ~/.bashrc
47
+ # or
48
+ source ~/.zshrc
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ### Save a directory
54
+
55
+ - Save the current directory or a specified directory:
56
+
57
+ ```bash
58
+ twd -s [path] [alias]
59
+ ```
60
+
61
+ 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.
62
+
63
+ ### Go to a saved directory
64
+
65
+ - Navigate to a saved directory using an optional alias:
66
+
67
+ ```bash
68
+ twd -g [alias]
69
+ ```
70
+
71
+ 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.
72
+
73
+ ### List saved directories
74
+
75
+ - Display a list of all saved directories:
76
+
77
+ ```bash
78
+ twd -l
79
+ ```
80
+
81
+ ### Unset the TWD and delete the data file
82
+
83
+ - Unset and delete the saved directories:
84
+
85
+ ```bash
86
+ twd -u
87
+ ```
88
+
89
+ You can force this action using the `--force` flag to avoid accidental execution.
90
+
91
+ ```bash
92
+ twd -u --force
93
+ ```
94
+
95
+ ### Optional Parameters
96
+
97
+ #### Simple Output
98
+
99
+ For cleaner, minimal output intended for scripting or piping.
100
+
101
+ - Example with `--simple-output`:
102
+
103
+ ```bash
104
+ twd -s --simple-output
105
+ /home/user/.config
106
+ ```
107
+
108
+ - Example without `--simple-output`:
109
+
110
+ ```bash
111
+ Saved TWD to /home/user/.config
112
+ ```
113
+
114
+ #### No Output
115
+
116
+ Suppresses all output (including confirmation messages).
117
+
118
+ - Example with `--no-output`:
119
+
120
+ ```bash
121
+ twd -s --no-output
122
+ # No output
123
+ ```
124
+
125
+ #### Force
126
+
127
+ Use the `--force` flag to force certain actions, such as when unsetting directories with the `-u` flag.
128
+
129
+ - Example:
130
+
131
+ ```bash
132
+ twd -u --force
133
+ TWD File deleted and TWD unset
134
+ ```
135
+
136
+ ## Contribution
137
+
138
+ To set up a development environment:
139
+
140
+ 1. Clone the repository:
141
+
142
+ ```bash
143
+ git clone https://github.com/m4sc0/twd
144
+ cd twd
145
+ ```
146
+
147
+ 2. Install the package in editable mode using `pip`:
148
+
149
+ ```bash
150
+ pip install -e .
151
+ ```
152
+
153
+ 3. Make your changes, and contribute!
@@ -0,0 +1,140 @@
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
+ ```bash
27
+ eval $(python3 -m twd --shell)
28
+ ```
29
+
30
+ 3. Exit and reopen the terminal or reload using:
31
+
32
+ ```bash
33
+ source ~/.bashrc
34
+ # or
35
+ source ~/.zshrc
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ### Save a directory
41
+
42
+ - Save the current directory or a specified directory:
43
+
44
+ ```bash
45
+ twd -s [path] [alias]
46
+ ```
47
+
48
+ 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.
49
+
50
+ ### Go to a saved directory
51
+
52
+ - Navigate to a saved directory using an optional alias:
53
+
54
+ ```bash
55
+ twd -g [alias]
56
+ ```
57
+
58
+ 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.
59
+
60
+ ### List saved directories
61
+
62
+ - Display a list of all saved directories:
63
+
64
+ ```bash
65
+ twd -l
66
+ ```
67
+
68
+ ### Unset the TWD and delete the data file
69
+
70
+ - Unset and delete the saved directories:
71
+
72
+ ```bash
73
+ twd -u
74
+ ```
75
+
76
+ You can force this action using the `--force` flag to avoid accidental execution.
77
+
78
+ ```bash
79
+ twd -u --force
80
+ ```
81
+
82
+ ### Optional Parameters
83
+
84
+ #### Simple Output
85
+
86
+ For cleaner, minimal output intended for scripting or piping.
87
+
88
+ - Example with `--simple-output`:
89
+
90
+ ```bash
91
+ twd -s --simple-output
92
+ /home/user/.config
93
+ ```
94
+
95
+ - Example without `--simple-output`:
96
+
97
+ ```bash
98
+ Saved TWD to /home/user/.config
99
+ ```
100
+
101
+ #### No Output
102
+
103
+ Suppresses all output (including confirmation messages).
104
+
105
+ - Example with `--no-output`:
106
+
107
+ ```bash
108
+ twd -s --no-output
109
+ # No output
110
+ ```
111
+
112
+ #### Force
113
+
114
+ Use the `--force` flag to force certain actions, such as when unsetting directories with the `-u` flag.
115
+
116
+ - Example:
117
+
118
+ ```bash
119
+ twd -u --force
120
+ TWD File deleted and TWD unset
121
+ ```
122
+
123
+ ## Contribution
124
+
125
+ To set up a development environment:
126
+
127
+ 1. Clone the repository:
128
+
129
+ ```bash
130
+ git clone https://github.com/m4sc0/twd
131
+ cd twd
132
+ ```
133
+
134
+ 2. Install the package in editable mode using `pip`:
135
+
136
+ ```bash
137
+ pip install -e .
138
+ ```
139
+
140
+ 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.1",
5
+ version="1.5.3",
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", action="store_true", 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(r"""
310
+ function twd() {
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