twd-m4sc0 1.5.4__tar.gz → 2.0.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twd_m4sc0
3
- Version: 1.5.4
3
+ Version: 2.0.1
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
 
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="twd_m4sc0",
5
- version="1.5.4",
5
+ version="2.0.1",
6
6
  packages=find_packages(),
7
7
  entry_points={
8
8
  "console_scripts": [
@@ -0,0 +1,104 @@
1
+ import os
2
+ import json
3
+ import hashlib
4
+ import time
5
+ import logging
6
+ from collections import OrderedDict
7
+
8
+ log = logging.getLogger("log")
9
+ error_log = logging.getLogger("error")
10
+
11
+
12
+ def create_alias_id():
13
+ data = str(time.time()) + str(os.urandom(16))
14
+ return hashlib.sha256(data.encode()).hexdigest()[:12]
15
+
16
+
17
+ def get_data_file(config):
18
+ return os.path.expanduser(config.get("data_file", "~/.twd/data"))
19
+
20
+
21
+ def ensure_data_file_exists(config):
22
+ data_file = get_data_file(config)
23
+ if not os.path.exists(data_file):
24
+ try:
25
+ with open(data_file, "w") as f:
26
+ json.dump({}, f)
27
+ log.info(f"Created data file at {data_file}")
28
+ except OSError as e:
29
+ error_log.error(f"Error creating data file: {e}")
30
+
31
+
32
+ def load_data(config):
33
+ data_file = get_data_file(config)
34
+ if not os.path.exists(data_file):
35
+ ensure_data_file_exists(config)
36
+ try:
37
+ with open(data_file, "r") as f:
38
+ data = json.load(f)
39
+ log.info(f"Loaded data from {data_file}")
40
+ return data
41
+ except json.JSONDecodeError as e:
42
+ error_log.error(f"Error reading data file: {e}")
43
+ return {}
44
+ except OSError as e:
45
+ error_log.error(f"Error reading data file: {e}")
46
+ return {}
47
+
48
+
49
+ def save_data(config, data):
50
+ data_file = get_data_file(config)
51
+ try:
52
+ sorted_data = OrderedDict(
53
+ sorted(data.items(), key=lambda item: item[1]["alias"])
54
+ )
55
+ with open(data_file, "w") as f:
56
+ json.dump(sorted_data, f, indent=4)
57
+ log.info(f"Saved data to {data_file}")
58
+ except OSError as e:
59
+ error_log.error(f"Error writing to data file: {e}")
60
+
61
+
62
+ def create_entry(config, data, path, alias=None):
63
+ alias_id = create_alias_id()
64
+ data[alias_id] = {
65
+ "path": path,
66
+ "alias": alias if alias else "no_alias",
67
+ "created_at": time.time(),
68
+ }
69
+ save_data(config, data)
70
+ log.info(f"Created new entry with alias_id '{alias_id}' and path '{path}'")
71
+ return alias_id
72
+
73
+
74
+ def delete_entry(config, data, entry_id):
75
+ if entry_id in data:
76
+ del data[entry_id]
77
+ save_data(config, data)
78
+ log.info(f"Deleted entry with alias_id '{entry_id}'")
79
+ else:
80
+ error_log.error(f"Entry ID '{entry_id}' not found")
81
+ raise KeyError(f"Entry ID {entry_id} not found")
82
+
83
+
84
+ def update_entry(config, data, entry_id, entry):
85
+ if entry_id in data:
86
+ data[entry_id] = entry
87
+ save_data(config, data)
88
+ log.info(f"Updated entry with alias_id '{entry_id}'")
89
+ else:
90
+ error_log.error(f"Entry ID '{entry_id}' not found")
91
+ raise KeyError(f"Entry ID {entry_id} not found")
92
+
93
+
94
+ def delete_data_file(config):
95
+ data_file = get_data_file(config)
96
+ if os.path.exists(data_file):
97
+ try:
98
+ os.remove(data_file)
99
+ log.info(f"Deleted data file at {data_file}")
100
+ except OSError as e:
101
+ error_log.error(f"Error deleting data file: {e}")
102
+ raise
103
+ else:
104
+ error_log.error("No data file found to delete")
@@ -0,0 +1,47 @@
1
+ import logging
2
+ import os
3
+ from logging.handlers import RotatingFileHandler
4
+
5
+
6
+ def setup_logger(config):
7
+ """Set up loggers based on the configuration provided."""
8
+ log_file = os.path.expanduser(config.get("log_file"))
9
+ error_file = os.path.expanduser(config.get("error_file"))
10
+ log_level = config.get("log_level", "INFO").upper()
11
+ max_bytes = config.get("log_max_bytes", 5 * 1024 * 1024) # Default 5MB
12
+ backup_count = config.get("log_backup_count", 3) # Default 3 backup files
13
+
14
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
15
+ os.makedirs(os.path.dirname(error_file), exist_ok=True)
16
+
17
+ # General log configuration
18
+ log_formatter = logging.Formatter(
19
+ config.get("log_format", "%(asctime)s - %(levelname)s - %(message)s")
20
+ )
21
+
22
+ # Avoid duplicate handlers
23
+ logger = logging.getLogger("log")
24
+ if not logger.hasHandlers():
25
+ logger.setLevel(log_level)
26
+ log_handler = RotatingFileHandler(
27
+ log_file, maxBytes=max_bytes, backupCount=backup_count
28
+ )
29
+ log_handler.setFormatter(log_formatter)
30
+ logger.addHandler(log_handler)
31
+
32
+ # Error log configuration
33
+ error_logger = logging.getLogger("error")
34
+ if not error_logger.hasHandlers():
35
+ error_logger.setLevel(logging.ERROR)
36
+ error_handler = RotatingFileHandler(
37
+ error_file, maxBytes=max_bytes, backupCount=backup_count
38
+ )
39
+ error_handler.setFormatter(log_formatter)
40
+ error_logger.addHandler(error_handler)
41
+
42
+ return logger, error_logger
43
+
44
+
45
+ def initialize_logging(config):
46
+ setup_logger(config)
47
+
@@ -0,0 +1,201 @@
1
+ import curses
2
+ import time
3
+ import os
4
+ from . import crud
5
+ import logging
6
+
7
+ log = logging.getLogger("log")
8
+ error_log = logging.getLogger("error")
9
+
10
+ CONFIG = None
11
+ DIRS = None
12
+ filtered_DIRS = None
13
+ search_query = ""
14
+ original_DIRS = None
15
+
16
+
17
+ def draw_hr(stdscr, y, mode=None):
18
+ _, max_cols = stdscr.getmaxyx()
19
+ mode = mode if mode is not None else curses.A_NORMAL
20
+ stdscr.addstr(y, 1, "─" * (max_cols - 2), mode)
21
+
22
+
23
+ def filter_dirs_by_search(query):
24
+ global filtered_DIRS
25
+ filtered_DIRS = (
26
+ {k: v for k, v in DIRS.items() if query.lower() in v["alias"].lower()}
27
+ if query
28
+ else DIRS
29
+ )
30
+
31
+
32
+ def display_select_screen(stdscr):
33
+ global search_query, filtered_DIRS, original_DIRS
34
+ selected_entry = 0
35
+ pre_selected_path = None
36
+ confirm_mode = False
37
+ action = None
38
+ search_mode = False
39
+ post_search_mode = False
40
+
41
+ running = True
42
+
43
+ while running:
44
+ max_items = len(filtered_DIRS)
45
+ stdscr.clear()
46
+
47
+ # Border setup
48
+ height, width = stdscr.getmaxyx()
49
+ stdscr.addstr(0, 0, "╭")
50
+ stdscr.addstr(0, 1, "─" * (width - 2))
51
+ stdscr.addstr(0, width - 1, "╮")
52
+ stdscr.addstr(height - 1, 0, "╰")
53
+ stdscr.addstr(height - 1, 1, "─" * (width - 2))
54
+ stdscr.addstr(height - 2, width - 1, "╯")
55
+ for i in range(1, height - 1):
56
+ stdscr.addstr(i, 0, "│")
57
+ stdscr.addstr(i, width - 1, "│")
58
+
59
+ inner_height = height - 2
60
+ inner_width = width - 2
61
+ stdscr.addstr(1, 1, f"Current directory: {os.getcwd()}")
62
+
63
+ draw_hr(stdscr, 2)
64
+
65
+ # Header
66
+ max_alias_len = max(
67
+ max(len(entry["alias"]) for entry in filtered_DIRS.values()), 5
68
+ )
69
+ max_path_len = max(
70
+ max(len(entry["path"]) for entry in filtered_DIRS.values()), 4
71
+ )
72
+ max_id_len = max(max(len(alias_id) for alias_id in filtered_DIRS.keys()), 2)
73
+
74
+ alias_col = max_alias_len + 2
75
+ id_col = max_id_len + 2
76
+ path_col = max_path_len
77
+
78
+ header_text = f"{'ALIAS'.ljust(alias_col)}{'ID'.ljust(id_col)}{'PATH'.ljust(path_col)} CREATED AT"
79
+ stdscr.addstr(3, 1, header_text[:inner_width])
80
+
81
+ draw_hr(stdscr, 4)
82
+
83
+ # List entries
84
+ line_start = 5
85
+ for entry_id, entry in enumerate(filtered_DIRS.values()):
86
+ if line_start >= inner_height - 5:
87
+ break
88
+ alias = entry["alias"].ljust(max_alias_len)
89
+ path = entry["path"].ljust(max_path_len)
90
+ alias_id = list(filtered_DIRS.keys())[entry_id].ljust(max_id_len)
91
+ created_at = time.strftime(
92
+ "%Y-%m-%d %H:%M:%S", time.localtime(entry["created_at"])
93
+ )
94
+
95
+ line_text = f"{alias} {alias_id} {path} {created_at}"
96
+ if entry_id == selected_entry:
97
+ stdscr.addstr(line_start, 1, line_text[:inner_width], curses.A_REVERSE)
98
+ pre_selected_path = entry["path"]
99
+ else:
100
+ stdscr.addstr(line_start, 1, line_text[:inner_width])
101
+
102
+ line_start += 1
103
+
104
+ # Controls
105
+ controls_y = height - 5
106
+ draw_hr(stdscr, controls_y, curses.A_DIM)
107
+ controls_text = (
108
+ "ctrls: enter=select"
109
+ if search_mode
110
+ else "ctrls: ↑/k=up ↓/j=down enter=select d/backspace=delete q=exit search s=search"
111
+ if post_search_mode
112
+ else "ctrls: ↑/k=up ↓/j=down enter=select d/backspace=delete q=quit s=search"
113
+ )
114
+ stdscr.addstr(controls_y + 1, 1, controls_text, curses.A_DIM)
115
+
116
+ # Action area
117
+ action_area_y = height - 3
118
+ draw_hr(stdscr, action_area_y)
119
+
120
+ if search_mode:
121
+ stdscr.addstr(action_area_y + 1, 1, f"Search: {search_query}")
122
+ elif confirm_mode and action == "delete":
123
+ entry = filtered_DIRS[list(filtered_DIRS.keys())[selected_entry]]
124
+ stdscr.addstr(
125
+ action_area_y + 1,
126
+ 1,
127
+ f"Delete entry '{entry['alias']}' ({entry['path']})? [enter/q]",
128
+ )
129
+ elif pre_selected_path:
130
+ stdscr.addstr(
131
+ action_area_y + 1,
132
+ 1,
133
+ f"Command: cd {os.path.abspath(os.path.expanduser(pre_selected_path))}",
134
+ )
135
+
136
+ stdscr.refresh()
137
+
138
+ # Handle key events
139
+ key = stdscr.getch()
140
+
141
+ if search_mode:
142
+ if key == ord("\n"):
143
+ search_mode = False
144
+ post_search_mode = True
145
+ elif key == curses.KEY_BACKSPACE or key == 127:
146
+ search_query = search_query[:-1]
147
+ filter_dirs_by_search(search_query)
148
+ else:
149
+ search_query += chr(key)
150
+ filter_dirs_by_search(search_query)
151
+ elif post_search_mode:
152
+ if key == ord("q") or key == 27: # 'q' or 'esc'
153
+ filtered_DIRS = original_DIRS
154
+ post_search_mode = False
155
+ elif key == curses.KEY_UP or key == ord("k"):
156
+ selected_entry = max(0, selected_entry - 1)
157
+ elif key == curses.KEY_DOWN or key == ord("j"):
158
+ selected_entry = min(max_items - 1, selected_entry + 1)
159
+ elif key == ord("\n"):
160
+ selected_entry_id = list(filtered_DIRS.keys())[selected_entry]
161
+ return filtered_DIRS[selected_entry_id]
162
+ elif confirm_mode:
163
+ if key == ord("\n") and action == "delete":
164
+ selected_entry_id = list(filtered_DIRS.keys())[selected_entry]
165
+ data = crud.load_data(CONFIG)
166
+ try:
167
+ crud.delete_entry(CONFIG, data, selected_entry_id)
168
+ except KeyError:
169
+ error_log.error(f"Entry ID {selected_entry_id} not found")
170
+ del filtered_DIRS[selected_entry_id]
171
+ if selected_entry >= len(filtered_DIRS):
172
+ selected_entry = max(len(filtered_DIRS) - 1, 0)
173
+ confirm_mode = False
174
+ else:
175
+ confirm_mode = False
176
+ else:
177
+ if key == curses.KEY_UP or key == ord("k"):
178
+ selected_entry = (selected_entry - 1) % max_items
179
+ elif key == curses.KEY_DOWN or key == ord("j"):
180
+ selected_entry = (selected_entry + 1) % max_items
181
+ elif key == ord("\n"):
182
+ selected_entry_id = list(filtered_DIRS.keys())[selected_entry]
183
+ return filtered_DIRS[selected_entry_id]
184
+ elif key == ord("q"):
185
+ return None
186
+ elif key == ord("d") or key == curses.KEY_BACKSPACE:
187
+ confirm_mode = True
188
+ action = "delete"
189
+ elif key == ord("s"):
190
+ search_mode = True
191
+ selected_entry = 0
192
+
193
+
194
+ def display_select(config, dirs):
195
+ global CONFIG, DIRS, filtered_DIRS, search_query, original_DIRS
196
+ CONFIG = config
197
+ DIRS = dirs
198
+ filtered_DIRS = DIRS
199
+ original_DIRS = DIRS
200
+ search_query = ""
201
+ return curses.wrapper(display_select_screen)
@@ -1,11 +1,16 @@
1
1
  import os
2
2
  import argparse
3
3
  import json
4
- import hashlib
5
4
  import time
6
5
  import re
6
+ import logging
7
7
  from importlib.metadata import version, PackageNotFoundError
8
- from .logger import log, error
8
+ from .logger import initialize_logging
9
+ from .screen import display_select
10
+ from . import crud
11
+
12
+ log = logging.getLogger("log")
13
+ error_log = logging.getLogger("error")
9
14
 
10
15
  TWD_DIR = os.path.join(os.path.expanduser("~"), ".twd")
11
16
  CONFIG_FILE = os.path.join(TWD_DIR, "config")
@@ -15,19 +20,16 @@ DEFAULT_CONFIG = {
15
20
  "output_behaviour": 2,
16
21
  "log_file": os.path.expanduser("~/.twd/log"),
17
22
  "error_file": os.path.expanduser("~/.twd/error"),
18
- "log_format": "[$T]: $M",
23
+ "log_format": "%(asctime)s - %(levelname)s - %(message)s",
24
+ "log_level": "INFO",
25
+ "log_max_bytes": 5 * 1024 * 1024, # 5 MB log rotation
26
+ "log_backup_count": 3,
19
27
  }
20
28
 
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
 
29
30
  def load_config():
30
31
  if not os.path.exists(CONFIG_FILE):
32
+ os.makedirs(TWD_DIR, exist_ok=True)
31
33
  with open(CONFIG_FILE, "w") as file:
32
34
  json.dump(DEFAULT_CONFIG, file, indent=4)
33
35
  return DEFAULT_CONFIG
@@ -36,23 +38,18 @@ def load_config():
36
38
  try:
37
39
  return json.load(file)
38
40
  except json.JSONDecodeError as e:
39
- error(f"Error loading config: {e}", DEFAULT_CONFIG)
41
+ error_log.error(f"Error loading config: {e}")
40
42
  return DEFAULT_CONFIG
41
43
 
42
44
 
43
45
  CONFIG = load_config()
46
+ initialize_logging(CONFIG)
44
47
 
45
- TWD_FILE = os.path.expanduser(CONFIG.get("data_file", "~/.twd/data"))
46
-
48
+ crud.ensure_data_file_exists(CONFIG)
47
49
 
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
50
 
51
+ def ensure_log_error_files():
52
+ """Ensure that log and error files exist based on configuration settings."""
56
53
  log_file = os.path.expanduser(CONFIG.get("log_file"))
57
54
  error_file = os.path.expanduser(CONFIG.get("error_file"))
58
55
 
@@ -61,31 +58,31 @@ def ensure_data_file_exists():
61
58
  with open(log_file, "w+") as f:
62
59
  f.write("")
63
60
  except OSError as e:
64
- error(f"Error creating log file: {e}", CONFIG)
61
+ error_log.error(f"Error creating log file: {e}")
65
62
 
66
63
  if not os.path.exists(error_file):
67
64
  try:
68
65
  with open(error_file, "w+") as f:
69
66
  f.write("")
70
67
  except OSError as e:
71
- error(f"Error creating error file: {e}", CONFIG)
68
+ error_log.error(f"Error creating error file: {e}")
72
69
 
73
70
 
74
- ensure_data_file_exists()
71
+ ensure_log_error_files()
75
72
 
76
73
 
77
74
  def get_absolute_path(path):
78
75
  try:
79
76
  return os.path.abspath(path)
80
77
  except Exception as e:
81
- error(f"Error getting absolute path for {path}: {e}", CONFIG)
78
+ error_log.error(f"Error getting absolute path for {path}: {e}")
82
79
  raise
83
80
 
84
81
 
85
82
  def validate_alias(alias):
86
83
  """Ensure the alias contains only valid characters."""
87
84
  if not re.match(r"^[\w-]+$", alias):
88
- error(f"Invalid alias provided: {alias}", CONFIG)
85
+ error_log.error(f"Invalid alias provided: {alias}")
89
86
  raise ValueError(
90
87
  f"Invalid alias: '{alias}'. Aliases can only contain alphanumeric characters, dashes, and underscores."
91
88
  )
@@ -95,21 +92,20 @@ def validate_alias(alias):
95
92
  def output_handler(
96
93
  message=None, path=None, output=True, simple_output=False, message_type=0
97
94
  ):
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}")
95
+ log.info(f"Type: {message_type}, Msg: {message or path}")
96
+
97
+ if CONFIG["output_behaviour"] == 1 or simple_output:
98
+ if path:
99
+ with open("/tmp/twd_path", "w") as f:
100
+ f.write(path)
101
+ if output:
102
+ print(path)
103
+ elif CONFIG["output_behaviour"] == 2:
104
+ if path:
105
+ with open("/tmp/twd_path", "w") as f:
106
+ f.write(path)
107
+ if output:
108
+ print(message)
113
109
 
114
110
 
115
111
  def save_directory(path=None, alias=None, output=True, simple_output=False):
@@ -121,26 +117,8 @@ def save_directory(path=None, alias=None, output=True, simple_output=False):
121
117
  if alias:
122
118
  alias = validate_alias(alias)
123
119
 
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
120
+ data = crud.load_data(CONFIG)
121
+ alias_id = crud.create_entry(CONFIG, data, path, alias)
144
122
 
145
123
  output_handler(
146
124
  f"Saved TWD to {path} with alias '{alias or alias_id}'",
@@ -151,47 +129,79 @@ def save_directory(path=None, alias=None, output=True, simple_output=False):
151
129
 
152
130
 
153
131
  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
132
+ data = crud.load_data(CONFIG)
133
+ return data if data else None
163
134
 
164
135
 
165
- def go_to_directory(alias=None, output=True, simple_output=False):
136
+ def show_main(alias=None, output=True, simple_output=False):
166
137
  dirs = load_directory()
167
-
168
- if not dirs:
138
+ if dirs is None:
169
139
  output_handler("No TWD found", None, output, simple_output)
170
140
  return 1
171
141
  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
-
142
+ if alias:
143
+ matched_dirs = []
144
+
145
+ for entry_id, entry in dirs.items():
146
+ if (
147
+ "alias" in entry
148
+ and entry["alias"]
149
+ and entry["alias"].startswith(alias)
150
+ ):
151
+ entry["id"] = entry_id
152
+ matched_dirs.append(entry)
153
+ elif entry_id.startswith(alias):
154
+ entry["id"] = entry_id
155
+ matched_dirs.append(entry)
156
+
157
+ if len(matched_dirs) == 1:
158
+ TWD = matched_dirs[0]["path"]
176
159
  if os.path.exists(TWD):
177
160
  output_handler(
178
161
  f"cd {TWD}", TWD, output, simple_output, message_type=1
179
162
  )
180
163
  return 0
181
164
  else:
182
- error(f"Directory does not exist: {TWD}", CONFIG)
165
+ error_log.error(f"Directory does not exist: {TWD}")
183
166
  output_handler(
184
167
  f"Directory does not exist: {TWD}", None, output, simple_output
185
168
  )
186
169
  return 1
187
-
188
- output_handler("No TWD with alias found", None, output, simple_output)
189
- return 1
170
+ elif len(matched_dirs) > 1:
171
+ output_handler(
172
+ f"Multiple TWDs match for '{alias}':", None, output, simple_output
173
+ )
174
+ for match in matched_dirs:
175
+ output_handler(
176
+ f"{match['alias']} {match['id']} {match['path']}",
177
+ None,
178
+ output,
179
+ simple_output,
180
+ )
181
+ return 1
182
+ else:
183
+ output_handler("No TWD with alias found", None, output, simple_output)
184
+ return 1
185
+
186
+ selected_dir = display_select(CONFIG, dirs)
187
+ if selected_dir is None:
188
+ output_handler("No TWD selected", None, output, simple_output)
189
+ return 0
190
+ else:
191
+ TWD = selected_dir["path"]
192
+ if os.path.exists(TWD):
193
+ output_handler(f"cd {TWD}", TWD, output, simple_output, message_type=1)
194
+ return 0
195
+ else:
196
+ error_log.error(f"Directory does not exist: {TWD}")
197
+ output_handler(
198
+ f"Directory does not exist: {TWD}", None, output, simple_output
199
+ )
200
+ return 1
190
201
 
191
202
 
192
203
  def show_directory(output=True, simple_output=False):
193
204
  dirs = load_directory()
194
-
195
205
  if not dirs:
196
206
  output_handler("No TWD set", None, output, simple_output)
197
207
  return
@@ -220,50 +230,41 @@ def show_directory(output=True, simple_output=False):
220
230
 
221
231
 
222
232
  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
-
233
+ if not force:
234
+ output_handler(
235
+ r"""If you want to execute deleting and therefore unsetting all set TWD's, please use "--force" or "-f" and run again.
230
236
 
231
237
  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)
238
+ None,
239
+ True,
240
+ False,
241
+ )
242
+ return
243
+ try:
244
+ crud.delete_data_file(CONFIG)
245
+ except OSError as e:
246
+ error_log.error(f"Error deleting TWD file: {e}")
247
+ raise
248
+ output_handler("TWD File deleted and TWD unset", None, output, simple_output)
243
249
 
244
250
 
245
251
  def get_package_version():
246
252
  try:
247
253
  return version("twd_m4sc0")
248
254
  except PackageNotFoundError as e:
249
- error(f"Package version not found: {e}", CONFIG)
255
+ error_log.error(f"Package version not found: {e}")
250
256
  return "Unknown version"
251
257
 
252
258
 
253
259
  def main():
254
- global TWD_FILE
255
-
256
260
  parser = argparse.ArgumentParser(
257
261
  description="Temporarily save and navigate to working directories."
258
262
  )
259
263
 
260
- # Positional arguments
261
264
  parser.add_argument("directory", nargs="?", help="Directory to save")
262
265
  parser.add_argument(
263
266
  "alias", nargs="?", help="Alias for the saved directory (optional)"
264
267
  )
265
-
266
- # Optional Arguments/Flags
267
268
  parser.add_argument(
268
269
  "-s",
269
270
  "--save",
@@ -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=None, help="Go to the saved directory"
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,66 +284,49 @@ def main():
283
284
  "-v",
284
285
  "--version",
285
286
  action="version",
286
- version=f"TWD Version: {get_package_version()}",
287
- help="Show the current version of TWD installed",
287
+ version=f"TWD Version: v{get_package_version()}",
288
288
  )
289
289
  parser.add_argument("-f", "--force", action="store_true", help="Force an action")
290
290
  parser.add_argument(
291
291
  "--shell", nargs="?", const="twd", help="Output shell function for integration"
292
292
  )
293
293
  parser.add_argument(
294
- "--simple-output",
295
- action="store_true",
296
- help="Only print essential output (new directory, absolute path, etc.)",
294
+ "--simple-output", action="store_true", help="Only print essential output"
297
295
  )
298
296
  parser.add_argument(
299
297
  "--no-output",
300
298
  action="store_true",
301
299
  help="Prevents the console from sending output",
302
300
  )
303
- args = parser.parse_args()
304
301
 
302
+ args = parser.parse_args()
305
303
  output = not args.no_output
306
304
  simple_output = args.simple_output
307
305
 
308
306
  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
- """)
307
+ print(rf"""function {args.shell}() {{
308
+ python3 -m twd "$@";
309
+ if [ -f /tmp/twd_path ]; then
310
+ cd "$(cat /tmp/twd_path)";
311
+ /bin/rm -f /tmp/twd_path;
312
+ fi;
313
+ }}""")
326
314
  return 0
327
315
 
328
316
  directory = args.directory or args.dir
329
317
  alias = args.alias or args.ali
330
318
 
331
319
  if args.save:
332
- if not directory:
333
- directory = args.directory or os.getcwd()
334
-
335
- alias = args.alias or args.ali
336
-
337
320
  save_directory(directory, alias, output, simple_output)
338
321
  elif args.go:
339
- alias = args.go
340
- return go_to_directory(alias, output, simple_output)
322
+ show_main(alias, output, simple_output)
341
323
  elif args.list:
342
324
  show_directory(output, simple_output)
343
325
  elif args.unset:
344
- force = args.force
345
- unset_directory(output, simple_output, force)
326
+ unset_directory(output, simple_output, force=args.force)
346
327
  else:
347
- parser.print_help()
348
- return 1
328
+ show_main(None, output, simple_output)
329
+
330
+
331
+ if __name__ == "__main__":
332
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: twd_m4sc0
3
- Version: 1.5.4
3
+ Version: 2.0.1
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
 
@@ -5,7 +5,9 @@ tests/__init__.py
5
5
  tests/test_twd.py
6
6
  twd/__init__.py
7
7
  twd/__main__.py
8
+ twd/crud.py
8
9
  twd/logger.py
10
+ twd/screen.py
9
11
  twd/twd.py
10
12
  twd_m4sc0.egg-info/PKG-INFO
11
13
  twd_m4sc0.egg-info/SOURCES.txt
@@ -1,47 +0,0 @@
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)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes