twd-m4sc0 1.5.4__tar.gz → 2.0.0__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.
- {twd_m4sc0-1.5.4/twd_m4sc0.egg-info → twd_m4sc0-2.0.0}/PKG-INFO +4 -1
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/README.md +3 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/setup.py +1 -1
- twd_m4sc0-2.0.0/twd/crud.py +94 -0
- twd_m4sc0-2.0.0/twd/screen.py +199 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd/twd.py +111 -112
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0/twd_m4sc0.egg-info}/PKG-INFO +4 -1
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd_m4sc0.egg-info/SOURCES.txt +2 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/LICENSE +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/setup.cfg +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/tests/__init__.py +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/tests/test_twd.py +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd/__init__.py +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd/__main__.py +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd/logger.py +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd_m4sc0.egg-info/dependency_links.txt +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd_m4sc0.egg-info/entry_points.txt +0 -0
- {twd_m4sc0-1.5.4 → twd_m4sc0-2.0.0}/twd_m4sc0.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: twd_m4sc0
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A tool to temporarily save and go to a working directory
|
|
5
5
|
Home-page: https://github.com/m4sc0/twd
|
|
6
6
|
Author: m4sc0
|
|
@@ -16,6 +16,8 @@ License-File: LICENSE
|
|
|
16
16
|
|
|
17
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
18
|
|
|
19
|
+
> All Versions `< v1.5` are considered deprecated and should not be used anymore because of the `config` file that was introduced in that version. This file is incompatible with newer versions and might cause issues or break the program.
|
|
20
|
+
|
|
19
21
|
## Features
|
|
20
22
|
|
|
21
23
|
- Save the current or specified working directory.
|
|
@@ -23,6 +25,7 @@ License-File: LICENSE
|
|
|
23
25
|
- List all saved directories with metadata.
|
|
24
26
|
- Unset and delete saved directories.
|
|
25
27
|
- Integrates with your shell for seamless directory management.
|
|
28
|
+
- Some options can be configured using the `config` file. For more information please visit the [Config](CONFIG.md) documentation.
|
|
26
29
|
|
|
27
30
|
## Installation
|
|
28
31
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
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
5
|
|
|
6
|
+
> All Versions `< v1.5` are considered deprecated and should not be used anymore because of the `config` file that was introduced in that version. This file is incompatible with newer versions and might cause issues or break the program.
|
|
7
|
+
|
|
6
8
|
## Features
|
|
7
9
|
|
|
8
10
|
- Save the current or specified working directory.
|
|
@@ -10,6 +12,7 @@
|
|
|
10
12
|
- List all saved directories with metadata.
|
|
11
13
|
- Unset and delete saved directories.
|
|
12
14
|
- Integrates with your shell for seamless directory management.
|
|
15
|
+
- Some options can be configured using the `config` file. For more information please visit the [Config](CONFIG.md) documentation.
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import hashlib
|
|
4
|
+
import time
|
|
5
|
+
from .logger import log, error
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_alias_id():
|
|
10
|
+
data = str(time.time()) + str(os.urandom(16))
|
|
11
|
+
return hashlib.sha256(data.encode()).hexdigest()[:12]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_data_file(config):
|
|
15
|
+
return os.path.expanduser(config.get("data_file", "~/.twd/data"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ensure_data_file_exists(config):
|
|
19
|
+
data_file = get_data_file(config)
|
|
20
|
+
if not os.path.exists(data_file):
|
|
21
|
+
try:
|
|
22
|
+
with open(data_file, "w") as f:
|
|
23
|
+
json.dump({}, f)
|
|
24
|
+
except OSError as e:
|
|
25
|
+
error(f"Error creating data file: {e}", config)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_data(config):
|
|
29
|
+
data_file = get_data_file(config)
|
|
30
|
+
if not os.path.exists(data_file):
|
|
31
|
+
ensure_data_file_exists(config)
|
|
32
|
+
try:
|
|
33
|
+
with open(data_file, "r") as f:
|
|
34
|
+
data = json.load(f)
|
|
35
|
+
return data
|
|
36
|
+
except json.JSONDecodeError as e:
|
|
37
|
+
error(f"Error reading data file: {e}", config)
|
|
38
|
+
return {}
|
|
39
|
+
except OSError as e:
|
|
40
|
+
error(f"Error reading data file: {e}", config)
|
|
41
|
+
return {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def save_data(config, data):
|
|
45
|
+
data_file = get_data_file(config)
|
|
46
|
+
try:
|
|
47
|
+
sorted_data = OrderedDict(
|
|
48
|
+
sorted(data.items(), key=lambda item: item[1]["alias"])
|
|
49
|
+
)
|
|
50
|
+
with open(data_file, "w") as f:
|
|
51
|
+
json.dump(sorted_data, f, indent=4)
|
|
52
|
+
except OSError as e:
|
|
53
|
+
error(f"Error writing to data file: {e}", config)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_entry(config, data, path, alias=None):
|
|
57
|
+
alias_id = create_alias_id()
|
|
58
|
+
data[alias_id] = {
|
|
59
|
+
"path": path,
|
|
60
|
+
"alias": alias if alias else "no_alias",
|
|
61
|
+
"created_at": time.time(),
|
|
62
|
+
}
|
|
63
|
+
save_data(config, data)
|
|
64
|
+
return alias_id
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def delete_entry(config, data, entry_id):
|
|
68
|
+
if entry_id in data:
|
|
69
|
+
del data[entry_id]
|
|
70
|
+
save_data(config, data)
|
|
71
|
+
else:
|
|
72
|
+
error(f"Entry ID {entry_id} not found", config)
|
|
73
|
+
raise KeyError(f"Entry ID {entry_id} not found")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def update_entry(config, data, entry_id, entry):
|
|
77
|
+
if entry_id in data:
|
|
78
|
+
data[entry_id] = entry
|
|
79
|
+
save_data(config, data)
|
|
80
|
+
else:
|
|
81
|
+
error(f"Entry ID {entry_id} not found", config)
|
|
82
|
+
raise KeyError(f"Entry ID {entry_id} not found")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def delete_data_file(config):
|
|
86
|
+
data_file = get_data_file(config)
|
|
87
|
+
if os.path.exists(data_file):
|
|
88
|
+
try:
|
|
89
|
+
os.remove(data_file)
|
|
90
|
+
except OSError as e:
|
|
91
|
+
error(f"Error deleting data file: {e}", config)
|
|
92
|
+
raise
|
|
93
|
+
else:
|
|
94
|
+
error("No data file found to delete", config)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import curses
|
|
2
|
+
import time
|
|
3
|
+
import os
|
|
4
|
+
from . import crud
|
|
5
|
+
from .logger import error
|
|
6
|
+
|
|
7
|
+
CONFIG = None
|
|
8
|
+
DIRS = None
|
|
9
|
+
filtered_DIRS = None
|
|
10
|
+
search_query = ""
|
|
11
|
+
original_DIRS = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def draw_hr(stdscr, y, mode=None):
|
|
15
|
+
_, max_cols = stdscr.getmaxyx()
|
|
16
|
+
mode = mode if mode is not None else curses.A_NORMAL
|
|
17
|
+
stdscr.addstr(y, 1, "─" * (max_cols - 2), mode)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def filter_dirs_by_search(query):
|
|
21
|
+
global filtered_DIRS
|
|
22
|
+
filtered_DIRS = (
|
|
23
|
+
{k: v for k, v in DIRS.items() if query.lower() in v["alias"].lower()}
|
|
24
|
+
if query
|
|
25
|
+
else DIRS
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def display_select_screen(stdscr):
|
|
30
|
+
global search_query, filtered_DIRS, original_DIRS
|
|
31
|
+
selected_entry = 0
|
|
32
|
+
pre_selected_path = None
|
|
33
|
+
confirm_mode = False
|
|
34
|
+
action = None
|
|
35
|
+
search_mode = False
|
|
36
|
+
post_search_mode = False
|
|
37
|
+
|
|
38
|
+
running = True
|
|
39
|
+
|
|
40
|
+
while running:
|
|
41
|
+
max_items = len(filtered_DIRS)
|
|
42
|
+
stdscr.clear()
|
|
43
|
+
|
|
44
|
+
# Border setup
|
|
45
|
+
height, width = stdscr.getmaxyx()
|
|
46
|
+
stdscr.addstr(0, 0, "╭")
|
|
47
|
+
stdscr.addstr(0, 1, "─" * (width - 2))
|
|
48
|
+
stdscr.addstr(0, width - 1, "╮")
|
|
49
|
+
stdscr.addstr(height - 1, 0, "╰")
|
|
50
|
+
stdscr.addstr(height - 1, 1, "─" * (width - 2))
|
|
51
|
+
stdscr.addstr(height - 2, width - 1, "╯")
|
|
52
|
+
for i in range(1, height - 1):
|
|
53
|
+
stdscr.addstr(i, 0, "│")
|
|
54
|
+
stdscr.addstr(i, width - 1, "│")
|
|
55
|
+
|
|
56
|
+
inner_height = height - 2
|
|
57
|
+
inner_width = width - 2
|
|
58
|
+
stdscr.addstr(1, 1, f"Current directory: {os.getcwd()}")
|
|
59
|
+
|
|
60
|
+
draw_hr(stdscr, 2)
|
|
61
|
+
|
|
62
|
+
# Header
|
|
63
|
+
max_alias_len = max(
|
|
64
|
+
max(len(entry["alias"]) for entry in filtered_DIRS.values()), 5
|
|
65
|
+
)
|
|
66
|
+
max_path_len = max(
|
|
67
|
+
max(len(entry["path"]) for entry in filtered_DIRS.values()), 4
|
|
68
|
+
)
|
|
69
|
+
max_id_len = max(max(len(alias_id) for alias_id in filtered_DIRS.keys()), 2)
|
|
70
|
+
|
|
71
|
+
alias_col = max_alias_len + 2
|
|
72
|
+
id_col = max_id_len + 2
|
|
73
|
+
path_col = max_path_len
|
|
74
|
+
|
|
75
|
+
header_text = f"{'ALIAS'.ljust(alias_col)}{'ID'.ljust(id_col)}{'PATH'.ljust(path_col)} CREATED AT"
|
|
76
|
+
stdscr.addstr(3, 1, header_text[:inner_width])
|
|
77
|
+
|
|
78
|
+
draw_hr(stdscr, 4)
|
|
79
|
+
|
|
80
|
+
# List entries
|
|
81
|
+
line_start = 5
|
|
82
|
+
for entry_id, entry in enumerate(filtered_DIRS.values()):
|
|
83
|
+
if line_start >= inner_height - 5:
|
|
84
|
+
break
|
|
85
|
+
alias = entry["alias"].ljust(max_alias_len)
|
|
86
|
+
path = entry["path"].ljust(max_path_len)
|
|
87
|
+
alias_id = list(filtered_DIRS.keys())[entry_id].ljust(max_id_len)
|
|
88
|
+
created_at = time.strftime(
|
|
89
|
+
"%Y-%m-%d %H:%M:%S", time.localtime(entry["created_at"])
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
line_text = f"{alias} {alias_id} {path} {created_at}"
|
|
93
|
+
if entry_id == selected_entry:
|
|
94
|
+
stdscr.addstr(line_start, 1, line_text[:inner_width], curses.A_REVERSE)
|
|
95
|
+
pre_selected_path = entry["path"]
|
|
96
|
+
else:
|
|
97
|
+
stdscr.addstr(line_start, 1, line_text[:inner_width])
|
|
98
|
+
|
|
99
|
+
line_start += 1
|
|
100
|
+
|
|
101
|
+
# Controls
|
|
102
|
+
controls_y = height - 5
|
|
103
|
+
draw_hr(stdscr, controls_y, curses.A_DIM)
|
|
104
|
+
controls_text = (
|
|
105
|
+
"ctrls: enter=select"
|
|
106
|
+
if search_mode
|
|
107
|
+
else "ctrls: ↑/k=up ↓/j=down enter=select d/backspace=delete q=exit search s=search"
|
|
108
|
+
if post_search_mode
|
|
109
|
+
else "ctrls: ↑/k=up ↓/j=down enter=select d/backspace=delete q=quit s=search"
|
|
110
|
+
)
|
|
111
|
+
stdscr.addstr(controls_y + 1, 1, controls_text, curses.A_DIM)
|
|
112
|
+
|
|
113
|
+
# Action area
|
|
114
|
+
action_area_y = height - 3
|
|
115
|
+
draw_hr(stdscr, action_area_y)
|
|
116
|
+
|
|
117
|
+
if search_mode:
|
|
118
|
+
stdscr.addstr(action_area_y + 1, 1, f"Search: {search_query}")
|
|
119
|
+
elif confirm_mode and action == "delete":
|
|
120
|
+
entry = filtered_DIRS[list(filtered_DIRS.keys())[selected_entry]]
|
|
121
|
+
stdscr.addstr(
|
|
122
|
+
action_area_y + 1,
|
|
123
|
+
1,
|
|
124
|
+
f"Delete entry '{entry['alias']}' ({entry['path']})? [enter/q]",
|
|
125
|
+
)
|
|
126
|
+
elif pre_selected_path:
|
|
127
|
+
stdscr.addstr(
|
|
128
|
+
action_area_y + 1,
|
|
129
|
+
1,
|
|
130
|
+
f"Command: cd {os.path.abspath(os.path.expanduser(pre_selected_path))}",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
stdscr.refresh()
|
|
134
|
+
|
|
135
|
+
# Handle key events
|
|
136
|
+
key = stdscr.getch()
|
|
137
|
+
|
|
138
|
+
if search_mode:
|
|
139
|
+
if key == ord("\n"):
|
|
140
|
+
search_mode = False
|
|
141
|
+
post_search_mode = True
|
|
142
|
+
elif key == curses.KEY_BACKSPACE or key == 127:
|
|
143
|
+
search_query = search_query[:-1]
|
|
144
|
+
filter_dirs_by_search(search_query)
|
|
145
|
+
else:
|
|
146
|
+
search_query += chr(key)
|
|
147
|
+
filter_dirs_by_search(search_query)
|
|
148
|
+
elif post_search_mode:
|
|
149
|
+
if key == ord("q") or key == 27: # 'q' or 'esc'
|
|
150
|
+
filtered_DIRS = original_DIRS
|
|
151
|
+
post_search_mode = False
|
|
152
|
+
elif key == curses.KEY_UP or key == ord("k"):
|
|
153
|
+
selected_entry = max(0, selected_entry - 1)
|
|
154
|
+
elif key == curses.KEY_DOWN or key == ord("j"):
|
|
155
|
+
selected_entry = min(max_items - 1, selected_entry + 1)
|
|
156
|
+
elif key == ord("\n"):
|
|
157
|
+
selected_entry_id = list(filtered_DIRS.keys())[selected_entry]
|
|
158
|
+
return filtered_DIRS[selected_entry_id]
|
|
159
|
+
elif confirm_mode:
|
|
160
|
+
if key == ord("\n") and action == "delete":
|
|
161
|
+
selected_entry_id = list(filtered_DIRS.keys())[selected_entry]
|
|
162
|
+
data = crud.load_data(CONFIG)
|
|
163
|
+
try:
|
|
164
|
+
crud.delete_entry(CONFIG, data, selected_entry_id)
|
|
165
|
+
except KeyError:
|
|
166
|
+
error(f"Entry ID {selected_entry_id} not found", CONFIG)
|
|
167
|
+
del filtered_DIRS[selected_entry_id]
|
|
168
|
+
if selected_entry >= len(filtered_DIRS):
|
|
169
|
+
selected_entry = max(len(filtered_DIRS) - 1, 0)
|
|
170
|
+
confirm_mode = False
|
|
171
|
+
else:
|
|
172
|
+
confirm_mode = False
|
|
173
|
+
else:
|
|
174
|
+
if key == curses.KEY_UP or key == ord("k"):
|
|
175
|
+
selected_entry = (selected_entry - 1) % max_items
|
|
176
|
+
elif key == curses.KEY_DOWN or key == ord("j"):
|
|
177
|
+
selected_entry = (selected_entry + 1) % max_items
|
|
178
|
+
elif key == ord("\n"):
|
|
179
|
+
selected_entry_id = list(filtered_DIRS.keys())[selected_entry]
|
|
180
|
+
return filtered_DIRS[selected_entry_id]
|
|
181
|
+
elif key == ord("q"):
|
|
182
|
+
return None
|
|
183
|
+
elif key == ord("d") or key == curses.KEY_BACKSPACE:
|
|
184
|
+
confirm_mode = True
|
|
185
|
+
action = "delete"
|
|
186
|
+
elif key == ord("s"):
|
|
187
|
+
search_mode = True
|
|
188
|
+
selected_entry = 0
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def display_select(config, dirs):
|
|
192
|
+
global CONFIG, DIRS, filtered_DIRS, search_query, original_DIRS
|
|
193
|
+
CONFIG = config
|
|
194
|
+
DIRS = dirs
|
|
195
|
+
filtered_DIRS = DIRS
|
|
196
|
+
original_DIRS = DIRS
|
|
197
|
+
search_query = ""
|
|
198
|
+
return curses.wrapper(display_select_screen)
|
|
199
|
+
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import argparse
|
|
3
3
|
import json
|
|
4
|
-
import hashlib
|
|
5
4
|
import time
|
|
6
5
|
import re
|
|
7
6
|
from importlib.metadata import version, PackageNotFoundError
|
|
8
7
|
from .logger import log, error
|
|
8
|
+
from .screen import display_select
|
|
9
|
+
from . import crud
|
|
9
10
|
|
|
10
11
|
TWD_DIR = os.path.join(os.path.expanduser("~"), ".twd")
|
|
11
12
|
CONFIG_FILE = os.path.join(TWD_DIR, "config")
|
|
@@ -18,16 +19,10 @@ DEFAULT_CONFIG = {
|
|
|
18
19
|
"log_format": "[$T]: $M",
|
|
19
20
|
}
|
|
20
21
|
|
|
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
22
|
|
|
29
23
|
def load_config():
|
|
30
24
|
if not os.path.exists(CONFIG_FILE):
|
|
25
|
+
os.makedirs(TWD_DIR, exist_ok=True)
|
|
31
26
|
with open(CONFIG_FILE, "w") as file:
|
|
32
27
|
json.dump(DEFAULT_CONFIG, file, indent=4)
|
|
33
28
|
return DEFAULT_CONFIG
|
|
@@ -42,20 +37,13 @@ def load_config():
|
|
|
42
37
|
|
|
43
38
|
CONFIG = load_config()
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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)
|
|
40
|
+
# Ensure data files exist
|
|
41
|
+
crud.ensure_data_file_exists(CONFIG)
|
|
42
|
+
log_file = os.path.expanduser(CONFIG.get("log_file"))
|
|
43
|
+
error_file = os.path.expanduser(CONFIG.get("error_file"))
|
|
55
44
|
|
|
56
|
-
log_file = os.path.expanduser(CONFIG.get("log_file"))
|
|
57
|
-
error_file = os.path.expanduser(CONFIG.get("error_file"))
|
|
58
45
|
|
|
46
|
+
def ensure_log_error_files():
|
|
59
47
|
if not os.path.exists(log_file):
|
|
60
48
|
try:
|
|
61
49
|
with open(log_file, "w+") as f:
|
|
@@ -71,7 +59,7 @@ def ensure_data_file_exists():
|
|
|
71
59
|
error(f"Error creating error file: {e}", CONFIG)
|
|
72
60
|
|
|
73
61
|
|
|
74
|
-
|
|
62
|
+
ensure_log_error_files()
|
|
75
63
|
|
|
76
64
|
|
|
77
65
|
def get_absolute_path(path):
|
|
@@ -97,19 +85,18 @@ def output_handler(
|
|
|
97
85
|
):
|
|
98
86
|
log(f"Type: {message_type}, Msg: {message or path}", CONFIG)
|
|
99
87
|
|
|
100
|
-
if
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
print(f"0;{message}")
|
|
88
|
+
if CONFIG["output_behaviour"] == 1 or simple_output:
|
|
89
|
+
if path:
|
|
90
|
+
with open("/tmp/twd_path", "w") as f:
|
|
91
|
+
f.write(path)
|
|
92
|
+
if output:
|
|
93
|
+
print(path)
|
|
94
|
+
elif CONFIG["output_behaviour"] == 2:
|
|
95
|
+
if path:
|
|
96
|
+
with open("/tmp/twd_path", "w") as f:
|
|
97
|
+
f.write(path)
|
|
98
|
+
if output:
|
|
99
|
+
print(message)
|
|
113
100
|
|
|
114
101
|
|
|
115
102
|
def save_directory(path=None, alias=None, output=True, simple_output=False):
|
|
@@ -121,26 +108,8 @@ def save_directory(path=None, alias=None, output=True, simple_output=False):
|
|
|
121
108
|
if alias:
|
|
122
109
|
alias = validate_alias(alias)
|
|
123
110
|
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
111
|
+
data = crud.load_data(CONFIG)
|
|
112
|
+
alias_id = crud.create_entry(CONFIG, data, path, alias)
|
|
144
113
|
|
|
145
114
|
output_handler(
|
|
146
115
|
f"Saved TWD to {path} with alias '{alias or alias_id}'",
|
|
@@ -151,27 +120,35 @@ def save_directory(path=None, alias=None, output=True, simple_output=False):
|
|
|
151
120
|
|
|
152
121
|
|
|
153
122
|
def load_directory():
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
123
|
+
data = crud.load_data(CONFIG)
|
|
124
|
+
return data if data else None
|
|
163
125
|
|
|
164
126
|
|
|
165
|
-
def
|
|
127
|
+
def show_main(alias=None, output=True, simple_output=False):
|
|
166
128
|
dirs = load_directory()
|
|
167
129
|
|
|
168
|
-
if
|
|
130
|
+
if dirs is None:
|
|
169
131
|
output_handler("No TWD found", None, output, simple_output)
|
|
170
132
|
return 1
|
|
171
133
|
else:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
134
|
+
# Use alias if provided
|
|
135
|
+
if alias:
|
|
136
|
+
matched_dirs = []
|
|
137
|
+
|
|
138
|
+
for entry_id, entry in dirs.items():
|
|
139
|
+
if (
|
|
140
|
+
"alias" in entry
|
|
141
|
+
and entry["alias"]
|
|
142
|
+
and entry["alias"].startswith(alias)
|
|
143
|
+
):
|
|
144
|
+
entry["id"] = entry_id
|
|
145
|
+
matched_dirs.append(entry)
|
|
146
|
+
elif entry_id.startswith(alias):
|
|
147
|
+
entry["id"] = entry_id
|
|
148
|
+
matched_dirs.append(entry)
|
|
149
|
+
|
|
150
|
+
if len(matched_dirs) == 1:
|
|
151
|
+
TWD = matched_dirs[0]["path"]
|
|
175
152
|
|
|
176
153
|
if os.path.exists(TWD):
|
|
177
154
|
output_handler(
|
|
@@ -184,9 +161,39 @@ def go_to_directory(alias=None, output=True, simple_output=False):
|
|
|
184
161
|
f"Directory does not exist: {TWD}", None, output, simple_output
|
|
185
162
|
)
|
|
186
163
|
return 1
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
164
|
+
elif len(matched_dirs) > 1:
|
|
165
|
+
output_handler(
|
|
166
|
+
f"Multiple TWDs match for '{alias}':", None, output, simple_output
|
|
167
|
+
)
|
|
168
|
+
for match in matched_dirs:
|
|
169
|
+
output_handler(
|
|
170
|
+
f"{match['alias']} {match['id']} {match['path']}",
|
|
171
|
+
None,
|
|
172
|
+
output,
|
|
173
|
+
simple_output,
|
|
174
|
+
)
|
|
175
|
+
return 1
|
|
176
|
+
else:
|
|
177
|
+
output_handler("No TWD with alias found", None, output, simple_output)
|
|
178
|
+
return 1
|
|
179
|
+
|
|
180
|
+
# Display selection using curses if alias is not given
|
|
181
|
+
selected_dir = display_select(CONFIG, dirs)
|
|
182
|
+
if selected_dir is None:
|
|
183
|
+
output_handler("No TWD selected", None, output, simple_output)
|
|
184
|
+
return 0
|
|
185
|
+
else:
|
|
186
|
+
TWD = selected_dir["path"]
|
|
187
|
+
|
|
188
|
+
if os.path.exists(TWD):
|
|
189
|
+
output_handler(f"cd {TWD}", TWD, output, simple_output, message_type=1)
|
|
190
|
+
return 0
|
|
191
|
+
else:
|
|
192
|
+
error(f"Directory does not exist: {TWD}", CONFIG)
|
|
193
|
+
output_handler(
|
|
194
|
+
f"Directory does not exist: {TWD}", None, output, simple_output
|
|
195
|
+
)
|
|
196
|
+
return 1
|
|
190
197
|
|
|
191
198
|
|
|
192
199
|
def show_directory(output=True, simple_output=False):
|
|
@@ -220,26 +227,22 @@ def show_directory(output=True, simple_output=False):
|
|
|
220
227
|
|
|
221
228
|
|
|
222
229
|
def unset_directory(output=True, simple_output=False, force=False):
|
|
223
|
-
if not
|
|
224
|
-
output_handler(
|
|
225
|
-
|
|
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
|
+
if not force:
|
|
231
|
+
output_handler(
|
|
232
|
+
r"""If you want to execute deleting and therefore unsetting all set TWD's, please use "--force" or "-f" and run again.
|
|
230
233
|
|
|
231
234
|
This feature is to prevent accidental execution.""",
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
235
|
+
None,
|
|
236
|
+
True,
|
|
237
|
+
False,
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
try:
|
|
241
|
+
crud.delete_data_file(CONFIG)
|
|
242
|
+
except OSError as e:
|
|
243
|
+
error(f"Error deleting TWD file: {e}", CONFIG)
|
|
244
|
+
raise
|
|
245
|
+
output_handler("TWD File deleted and TWD unset", None, output, simple_output)
|
|
243
246
|
|
|
244
247
|
|
|
245
248
|
def get_package_version():
|
|
@@ -251,8 +254,6 @@ def get_package_version():
|
|
|
251
254
|
|
|
252
255
|
|
|
253
256
|
def main():
|
|
254
|
-
global TWD_FILE
|
|
255
|
-
|
|
256
257
|
parser = argparse.ArgumentParser(
|
|
257
258
|
description="Temporarily save and navigate to working directories."
|
|
258
259
|
)
|
|
@@ -273,7 +274,7 @@ def main():
|
|
|
273
274
|
parser.add_argument("-d", "--dir", nargs="?", help="Directory to save")
|
|
274
275
|
parser.add_argument("-a", "--ali", nargs="?", help="Alias for the saved directory")
|
|
275
276
|
parser.add_argument(
|
|
276
|
-
"-g", "--go", nargs="?", const=
|
|
277
|
+
"-g", "--go", nargs="?", const=" ", help="Go to the saved directory"
|
|
277
278
|
)
|
|
278
279
|
parser.add_argument("-l", "--list", action="store_true", help="Show saved TWD")
|
|
279
280
|
parser.add_argument(
|
|
@@ -283,7 +284,7 @@ def main():
|
|
|
283
284
|
"-v",
|
|
284
285
|
"--version",
|
|
285
286
|
action="version",
|
|
286
|
-
version=f"TWD Version: {get_package_version()}",
|
|
287
|
+
version=f"TWD Version: v{get_package_version()}",
|
|
287
288
|
help="Show the current version of TWD installed",
|
|
288
289
|
)
|
|
289
290
|
parser.add_argument("-f", "--force", action="store_true", help="Force an action")
|
|
@@ -305,24 +306,17 @@ def main():
|
|
|
305
306
|
output = not args.no_output
|
|
306
307
|
simple_output = args.simple_output
|
|
307
308
|
|
|
309
|
+
# Shell function
|
|
308
310
|
if args.shell:
|
|
309
311
|
print(rf"""
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if [[ "$type" == "1" ]]; then
|
|
319
|
-
eval "$message";
|
|
320
|
-
else
|
|
321
|
-
echo "$message";
|
|
322
|
-
fi;
|
|
323
|
-
done <<< "$output";
|
|
324
|
-
}}
|
|
325
|
-
""")
|
|
312
|
+
function {args.shell}() {{
|
|
313
|
+
python3 -m twd "$@"
|
|
314
|
+
if [[ -f /tmp/twd_path ]]; then
|
|
315
|
+
cd "$(cat /tmp/twd_path)"
|
|
316
|
+
/bin/rm -f /tmp/twd_path
|
|
317
|
+
fi
|
|
318
|
+
}}
|
|
319
|
+
""")
|
|
326
320
|
return 0
|
|
327
321
|
|
|
328
322
|
directory = args.directory or args.dir
|
|
@@ -330,19 +324,24 @@ def main():
|
|
|
330
324
|
|
|
331
325
|
if args.save:
|
|
332
326
|
if not directory:
|
|
333
|
-
directory =
|
|
327
|
+
directory = os.getcwd()
|
|
334
328
|
|
|
335
329
|
alias = args.alias or args.ali
|
|
336
330
|
|
|
337
331
|
save_directory(directory, alias, output, simple_output)
|
|
338
332
|
elif args.go:
|
|
339
333
|
alias = args.go
|
|
340
|
-
return
|
|
334
|
+
return show_main(alias, output, simple_output)
|
|
341
335
|
elif args.list:
|
|
342
336
|
show_directory(output, simple_output)
|
|
343
337
|
elif args.unset:
|
|
344
338
|
force = args.force
|
|
345
339
|
unset_directory(output, simple_output, force)
|
|
346
340
|
else:
|
|
347
|
-
|
|
341
|
+
show_main(None, output, simple_output)
|
|
348
342
|
return 1
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
main()
|
|
347
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: twd_m4sc0
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A tool to temporarily save and go to a working directory
|
|
5
5
|
Home-page: https://github.com/m4sc0/twd
|
|
6
6
|
Author: m4sc0
|
|
@@ -16,6 +16,8 @@ License-File: LICENSE
|
|
|
16
16
|
|
|
17
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
18
|
|
|
19
|
+
> All Versions `< v1.5` are considered deprecated and should not be used anymore because of the `config` file that was introduced in that version. This file is incompatible with newer versions and might cause issues or break the program.
|
|
20
|
+
|
|
19
21
|
## Features
|
|
20
22
|
|
|
21
23
|
- Save the current or specified working directory.
|
|
@@ -23,6 +25,7 @@ License-File: LICENSE
|
|
|
23
25
|
- List all saved directories with metadata.
|
|
24
26
|
- Unset and delete saved directories.
|
|
25
27
|
- Integrates with your shell for seamless directory management.
|
|
28
|
+
- Some options can be configured using the `config` file. For more information please visit the [Config](CONFIG.md) documentation.
|
|
26
29
|
|
|
27
30
|
## Installation
|
|
28
31
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|