hexlogger 2.0.2__tar.gz → 2.0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexlogger
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Premium console logging library with themes, gradients, panels, tables, progress bars, and spinners.
5
5
  Author: Itz Montage
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
- __version__ = "2.0.2"
1
+ __version__ = "2.0.3"
2
2
  __author__ = "Montage"
3
3
 
4
4
  from .colors import Colors, rgb, bgrgb, hexclr, bghex
@@ -14,16 +14,6 @@ def _get_time():
14
14
  return ""
15
15
 
16
16
 
17
- def _write_to_file(message):
18
- cfg = get_config()
19
- if cfg._file_handle:
20
- try:
21
- cfg._file_handle.write(strip_ansi(message) + "\n")
22
- cfg._file_handle.flush()
23
- except Exception:
24
- pass
25
-
26
-
27
17
  def _log(level, message, level_enum):
28
18
  try:
29
19
  cfg = get_config()
@@ -31,13 +21,16 @@ def _log(level, message, level_enum):
31
21
  return
32
22
  t = cfg.theme
33
23
  now = _get_time()
34
- level_color = t.get(level)
35
- level_label = t.get(f"label_{level}")
36
24
  style_fn = STYLES.get(cfg.style, STYLES["default"])
37
- line = style_fn(now, level, level_color, level_label, str(message), t, cfg.show_time)
25
+ line = style_fn(now, level, t.get(level), t.get(f"label_{level}"), str(message), t, cfg.show_time)
38
26
  with cfg._lock:
39
27
  print(line)
40
- _write_to_file(line)
28
+ if cfg._file_handle:
29
+ try:
30
+ cfg._file_handle.write(strip_ansi(line) + "\n")
31
+ cfg._file_handle.flush()
32
+ except Exception:
33
+ pass
41
34
  except Exception:
42
35
  try:
43
36
  print(f"[{level.upper()}] {message}")
@@ -1,12 +1,11 @@
1
1
  from .colors import Colors
2
2
  from .config import get_config
3
- from .utils import strip_ansi
3
+ from .utils import strip_ansi, get_terminal_width, wrap_lines
4
4
 
5
5
  __all__ = ["divider", "section", "blank", "rule", "banner", "pair"]
6
6
 
7
7
 
8
- def _write(message):
9
- cfg = get_config()
8
+ def _write(cfg, message):
10
9
  if cfg._file_handle:
11
10
  try:
12
11
  cfg._file_handle.write(strip_ansi(message) + "\n")
@@ -19,11 +18,11 @@ def divider(char="─", length=60, color=None):
19
18
  try:
20
19
  cfg = get_config()
21
20
  c = color or cfg.theme.get("divider")
22
- length = max(1, int(length))
21
+ length = min(max(1, int(length)), get_terminal_width())
23
22
  line = f"{c}{str(char)[0] * length}{Colors.RESET}"
24
23
  with cfg._lock:
25
24
  print(line)
26
- _write(line)
25
+ _write(cfg, line)
27
26
  except Exception:
28
27
  print(str(char) * max(1, int(length)))
29
28
 
@@ -33,8 +32,9 @@ def section(title="", char="═", length=60):
33
32
  cfg = get_config()
34
33
  t = cfg.theme
35
34
  title = str(title)
36
- length = max(len(title) + 4, int(length))
37
- pad = length - len(title) - 2
35
+ title_len = len(strip_ansi(title))
36
+ length = min(max(title_len + 4, int(length)), get_terminal_width())
37
+ pad = length - title_len - 2
38
38
  left = pad // 2
39
39
  right = pad - left
40
40
  line = (
@@ -44,14 +44,20 @@ def section(title="", char="═", length=60):
44
44
  )
45
45
  with cfg._lock:
46
46
  print(line)
47
- _write(line)
47
+ _write(cfg, line)
48
48
  except Exception:
49
49
  print(f"== {title} ==")
50
50
 
51
51
 
52
52
  def blank(count=1):
53
- for _ in range(max(1, int(count))):
54
- print()
53
+ try:
54
+ cfg = get_config()
55
+ with cfg._lock:
56
+ for _ in range(max(1, int(count))):
57
+ print()
58
+ except Exception:
59
+ for _ in range(max(1, int(count))):
60
+ print()
55
61
 
56
62
 
57
63
  def rule(text="", char="─", length=60):
@@ -66,16 +72,23 @@ def banner(text="", char="═", padding=2):
66
72
  cfg = get_config()
67
73
  text = str(text)
68
74
  padding = max(0, int(padding))
69
- lines = text.split("\n")
70
- max_len = max((len(l) for l in lines), default=0)
71
- width = max_len + (padding * 2) + 2
75
+ term_w = get_terminal_width()
76
+ max_inner = max(term_w - 2, 1)
77
+ lines = wrap_lines(text.split("\n"), max_inner - (padding * 2))
78
+ max_len = max((len(strip_ansi(l)) for l in lines), default=0)
79
+ width = min(max_len + (padding * 2) + 2, term_w)
72
80
  t = cfg.theme
73
81
  bc, tc, R = t.get("info"), t.get("success"), Colors.RESET
74
82
  ch = str(char)[0] if char else "═"
75
83
  with cfg._lock:
76
84
  print(f"{bc}╔{ch * (width - 2)}╗{R}")
77
85
  for l in lines:
78
- print(f"{bc}║{R}{' ' * padding}{tc}{l.center(max_len)}{R}{' ' * padding}{bc}║{R}")
86
+ s_len = len(strip_ansi(l))
87
+ space = max(max_len - s_len, 0)
88
+ lp = space // 2
89
+ rp = space - lp
90
+ padded = f"{' ' * lp}{l}{' ' * rp}"
91
+ print(f"{bc}║{R}{' ' * padding}{tc}{padded}{R}{' ' * padding}{bc}║{R}")
79
92
  print(f"{bc}╚{ch * (width - 2)}╝{R}")
80
93
  except Exception:
81
94
  print(text)
@@ -89,6 +102,6 @@ def pair(k, v, k_clr=None, v_clr=None):
89
102
  k, v = str(k), str(v)
90
103
  with cfg._lock:
91
104
  print(f" {kc}{Colors.BOLD}{k}{Colors.RESET}: {vc}{v}{Colors.RESET}")
92
- _write(f" {k}: {v}")
105
+ _write(cfg, f" {k}: {v}")
93
106
  except Exception:
94
107
  print(f" {k}: {v}")
@@ -4,6 +4,7 @@ import threading
4
4
 
5
5
  from .colors import Colors
6
6
  from .config import get_config
7
+ from .utils import get_terminal_width
7
8
 
8
9
  __all__ = ["Progress", "Spinner", "Timer"]
9
10
 
@@ -28,18 +29,25 @@ class Progress:
28
29
 
29
30
  def _render(self):
30
31
  try:
32
+ term_w = get_terminal_width()
31
33
  pct = self.current / self.total
32
- filled = int(self.width * pct)
33
- bar = self.fill * filled + self.empty * (self.width - filled)
34
+ stats = f" ({self.current}/{self.total}) [{time.time() - self._start:.1f}s]"
35
+ label_part = f"{self.color}{self.label}{Colors.RESET} │"
36
+ bar_end = f"│ {Colors.BOLD}{pct * 100:5.1f}%{Colors.RESET}{stats}"
37
+ overhead = len(self.label) + 3 + 10 + len(stats)
38
+ bar_w = min(self.width, max(5, term_w - overhead))
39
+ filled = int(bar_w * pct)
40
+ bar = self.fill * filled + self.empty * (bar_w - filled)
34
41
  elapsed = time.time() - self._start
35
42
  eta = (elapsed / pct - elapsed) if pct > 0 else 0
36
- sys.stdout.write(
43
+ line = (
37
44
  f"\r{self.color}{self.label}{Colors.RESET} "
38
45
  f"│{self.color}{bar}{Colors.RESET}│ "
39
46
  f"{Colors.BOLD}{pct * 100:5.1f}%{Colors.RESET} "
40
47
  f"({self.current}/{self.total}) "
41
48
  f"[{elapsed:.1f}s / ETA {eta:.1f}s]"
42
49
  )
50
+ sys.stdout.write(line)
43
51
  sys.stdout.flush()
44
52
  if self.current >= self.total:
45
53
  print()
@@ -64,18 +72,18 @@ class Spinner:
64
72
  self.color = color or get_config().theme.get("info")
65
73
  except Exception:
66
74
  self.color = ""
67
- self._run = False
75
+ self._stop_event = threading.Event()
68
76
  self._thread = None
69
77
 
70
78
  def start(self):
71
- self._run = True
79
+ self._stop_event.clear()
72
80
  self._thread = threading.Thread(target=self._spin, daemon=True)
73
81
  self._thread.start()
74
82
  return self
75
83
 
76
84
  def _spin(self):
77
85
  i = 0
78
- while self._run:
86
+ while not self._stop_event.is_set():
79
87
  try:
80
88
  frame = self.frames[i % len(self.frames)]
81
89
  sys.stdout.write(f"\r{self.color}{frame}{Colors.RESET} {self.msg}")
@@ -83,17 +91,17 @@ class Spinner:
83
91
  except Exception:
84
92
  pass
85
93
  i += 1
86
- time.sleep(0.08)
94
+ self._stop_event.wait(0.08)
87
95
 
88
96
  def stop(self, final=""):
89
- self._run = False
97
+ self._stop_event.set()
90
98
  if self._thread:
91
99
  try:
92
100
  self._thread.join(timeout=2)
93
101
  except Exception:
94
102
  pass
95
103
  try:
96
- sys.stdout.write("\r" + " " * (len(self.msg) + 10) + "\r")
104
+ sys.stdout.write(f"\r\033[2K\r")
97
105
  sys.stdout.flush()
98
106
  except Exception:
99
107
  pass
@@ -0,0 +1,74 @@
1
+ import re
2
+ import shutil
3
+
4
+ from .colors import Colors
5
+
6
+ __all__ = ["strip_ansi", "clr", "bold", "italic", "uline"]
7
+
8
+ _ANSI_RE = re.compile(r'\033\[[0-9;]*m')
9
+
10
+
11
+ def strip_ansi(text):
12
+ try:
13
+ return _ANSI_RE.sub('', str(text))
14
+ except Exception:
15
+ return str(text)
16
+
17
+
18
+ def get_terminal_width():
19
+ try:
20
+ return shutil.get_terminal_size().columns
21
+ except Exception:
22
+ return 120
23
+
24
+
25
+ def wrap_line(text, max_width):
26
+ if len(strip_ansi(text)) <= max_width:
27
+ return [text]
28
+ wrapped = []
29
+ current = ""
30
+ current_len = 0
31
+ i = 0
32
+ while i < len(text):
33
+ if text[i] == '\033' and i + 1 < len(text) and text[i + 1] == '[':
34
+ j = i + 2
35
+ while j < len(text) and text[j] not in 'mGHJK':
36
+ j += 1
37
+ if j < len(text):
38
+ j += 1
39
+ current += text[i:j]
40
+ i = j
41
+ else:
42
+ if current_len >= max_width:
43
+ wrapped.append(current)
44
+ current = ""
45
+ current_len = 0
46
+ current += text[i]
47
+ current_len += 1
48
+ i += 1
49
+ if current:
50
+ wrapped.append(current)
51
+ return wrapped if wrapped else [text]
52
+
53
+
54
+ def wrap_lines(lines, max_width):
55
+ result = []
56
+ for line in lines:
57
+ result.extend(wrap_line(line, max_width))
58
+ return result
59
+
60
+
61
+ def clr(text, color):
62
+ return f"{color}{text}{Colors.RESET}"
63
+
64
+
65
+ def bold(text):
66
+ return f"{Colors.BOLD}{text}{Colors.RESET}"
67
+
68
+
69
+ def italic(text):
70
+ return f"{Colors.ITALIC}{text}{Colors.RESET}"
71
+
72
+
73
+ def uline(text):
74
+ return f"{Colors.UNDERLINE}{text}{Colors.RESET}"
@@ -1,6 +1,6 @@
1
1
  from .colors import Colors
2
2
  from .config import get_config
3
- from .utils import strip_ansi
3
+ from .utils import strip_ansi, get_terminal_width, wrap_lines
4
4
 
5
5
  __all__ = ["panel", "table", "box"]
6
6
 
@@ -34,12 +34,18 @@ def panel(content="", title="", width=50, color=None, style="rounded"):
34
34
  R = Colors.RESET
35
35
  tl, tr, bl, br, h, v = _PANEL_CHARS.get(style, _PANEL_CHARS["rounded"])
36
36
  content = str(content)
37
- lines = content.split("\n")
38
- width = max(width, 8)
37
+ term_w = get_terminal_width()
38
+ max_inner = max(term_w - 4, 8)
39
+ lines = wrap_lines(content.split("\n"), max_inner)
40
+ max_line_len = max((len(strip_ansi(l)) for l in lines), default=0)
41
+ width = max(width, max_line_len + 4, 8)
42
+ if title:
43
+ title = str(title)
44
+ width = max(width, len(strip_ansi(title)) + 6)
45
+ width = min(width, term_w)
39
46
  inner_w = width - 4
40
47
  with cfg._lock:
41
48
  if title:
42
- title = str(title)
43
49
  header_pad = max(width - len(strip_ansi(title)) - 5, 1)
44
50
  print(f"{bc}{tl}{h} {Colors.BOLD}{title}{R}{bc} {h * header_pad}{tr}{R}")
45
51
  else:
@@ -64,13 +70,14 @@ def table(headers=None, rows=None, color=None, style="rounded"):
64
70
  hc = cfg.theme.get("success")
65
71
  R, B = Colors.RESET, Colors.BOLD
66
72
  tl, tr, bl, br, h, v, tm, bm, ml, mr, mm = _TABLE_CHARS.get(style, _TABLE_CHARS["rounded"])
67
- col_w = [len(hdr) for hdr in headers]
73
+ col_w = [len(strip_ansi(hdr)) for hdr in headers]
68
74
  for r in rows:
69
75
  for i, c in enumerate(r):
76
+ vl = len(strip_ansi(c))
70
77
  if i < len(col_w):
71
- col_w[i] = max(col_w[i], len(c))
78
+ col_w[i] = max(col_w[i], vl)
72
79
  else:
73
- col_w.append(len(c))
80
+ col_w.append(vl)
74
81
 
75
82
  def _sep(left, mid, right, fill):
76
83
  return f"{bc}{left}{mid.join(fill * (w + 2) for w in col_w)}{right}{R}"
@@ -78,8 +85,9 @@ def table(headers=None, rows=None, color=None, style="rounded"):
78
85
  def _row(cells, c=""):
79
86
  padded = []
80
87
  for i, cell in enumerate(cells):
81
- w = col_w[i] if i < len(col_w) else len(cell)
82
- padded.append(f" {c}{cell.ljust(w)}{R} ")
88
+ w = col_w[i] if i < len(col_w) else len(strip_ansi(cell))
89
+ pad = max(w - len(strip_ansi(cell)), 0)
90
+ padded.append(f" {c}{cell}{' ' * pad}{R} ")
83
91
  return f"{bc}{v}{R}" + f"{bc}{v}{R}".join(padded) + f"{bc}{v}{R}"
84
92
 
85
93
  with cfg._lock:
@@ -105,10 +113,13 @@ def box(text="", width=None, style="rounded", color=None, text_clr=None, align="
105
113
  tl, tr, bl, br, h, v = _BOX_CHARS.get(style, _BOX_CHARS["rounded"])
106
114
  text = str(text)
107
115
  padding = max(0, int(padding))
108
- lines = text.split("\n")
116
+ term_w = get_terminal_width()
117
+ max_inner = max(term_w - 4, 1)
118
+ lines = wrap_lines(text.split("\n"), max_inner)
109
119
  max_line = max((len(strip_ansi(l)) for l in lines), default=0)
110
120
  inner_w = (width - 4) if width else max_line + (padding * 2)
111
- inner_w = max(inner_w, 1)
121
+ inner_w = max(inner_w, max_line, 1)
122
+ inner_w = min(inner_w, max_inner)
112
123
  total_w = inner_w + 4
113
124
  with cfg._lock:
114
125
  print(f"{bc}{tl}{h * (total_w - 2)}{tr}{R}")
@@ -116,9 +127,9 @@ def box(text="", width=None, style="rounded", color=None, text_clr=None, align="
116
127
  s_len = len(strip_ansi(l))
117
128
  space = max(inner_w - s_len, 0)
118
129
  if align == "center":
119
- left_pad = space // 2
120
- right_pad = space - left_pad
121
- padded = f"{' ' * left_pad}{tc}{l}{R}{' ' * right_pad}"
130
+ lp = space // 2
131
+ rp = space - lp
132
+ padded = f"{' ' * lp}{tc}{l}{R}{' ' * rp}"
122
133
  elif align == "right":
123
134
  padded = f"{' ' * space}{tc}{l}{R}"
124
135
  else:
@@ -127,4 +138,3 @@ def box(text="", width=None, style="rounded", color=None, text_clr=None, align="
127
138
  print(f"{bc}{bl}{h * (total_w - 2)}{br}{R}")
128
139
  except Exception:
129
140
  print(text)
130
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexlogger
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Premium console logging library with themes, gradients, panels, tables, progress bars, and spinners.
5
5
  Author: Itz Montage
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hexlogger"
7
- version = "2.0.2"
7
+ version = "2.0.3"
8
8
  description = "Premium console logging library with themes, gradients, panels, tables, progress bars, and spinners."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,30 +0,0 @@
1
- import re
2
-
3
- from .colors import Colors
4
-
5
- __all__ = ["strip_ansi", "clr", "bold", "italic", "uline"]
6
-
7
- _ANSI_RE = re.compile(r'\033\[[0-9;]*m')
8
-
9
-
10
- def strip_ansi(text):
11
- try:
12
- return _ANSI_RE.sub('', str(text))
13
- except Exception:
14
- return str(text)
15
-
16
-
17
- def clr(text, color):
18
- return f"{color}{text}{Colors.RESET}"
19
-
20
-
21
- def bold(text):
22
- return f"{Colors.BOLD}{text}{Colors.RESET}"
23
-
24
-
25
- def italic(text):
26
- return f"{Colors.ITALIC}{text}{Colors.RESET}"
27
-
28
-
29
- def uline(text):
30
- return f"{Colors.UNDERLINE}{text}{Colors.RESET}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes