nginx-lens 0.1.5__tar.gz → 0.1.7__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.
Files changed (49) hide show
  1. nginx_lens-0.1.7/LICENSE +21 -0
  2. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/PKG-INFO +3 -1
  3. nginx_lens-0.1.7/README.md +81 -0
  4. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/analyze.py +8 -1
  5. nginx_lens-0.1.7/commands/diff.py +51 -0
  6. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/graph.py +60 -9
  7. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/health.py +8 -1
  8. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/include.py +8 -1
  9. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/logs.py +25 -17
  10. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/route.py +13 -6
  11. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/syntax.py +7 -2
  12. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/tree.py +8 -1
  13. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/nginx_lens.egg-info/PKG-INFO +3 -1
  14. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/nginx_lens.egg-info/SOURCES.txt +2 -0
  15. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/setup.py +1 -1
  16. nginx_lens-0.1.5/commands/diff.py +0 -39
  17. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/__init__.py +0 -0
  18. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/base.py +0 -0
  19. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/conflicts.py +0 -0
  20. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/dead_locations.py +0 -0
  21. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/diff.py +0 -0
  22. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/duplicates.py +0 -0
  23. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/empty_blocks.py +0 -0
  24. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/include.py +0 -0
  25. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/rewrite.py +0 -0
  26. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/route.py +0 -0
  27. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/unused.py +0 -0
  28. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/analyzer/warnings.py +0 -0
  29. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/__init__.py +0 -0
  30. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/commands/cli.py +0 -0
  31. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/exporter/__init__.py +0 -0
  32. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/exporter/graph.py +0 -0
  33. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/exporter/html.py +0 -0
  34. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/exporter/markdown.py +0 -0
  35. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/nginx_lens.egg-info/dependency_links.txt +0 -0
  36. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/nginx_lens.egg-info/entry_points.txt +0 -0
  37. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/nginx_lens.egg-info/requires.txt +0 -0
  38. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/nginx_lens.egg-info/top_level.txt +0 -0
  39. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/parser/__init__.py +0 -0
  40. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/parser/nginx_parser.py +0 -0
  41. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/pyproject.toml +0 -0
  42. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/setup.cfg +0 -0
  43. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/tests/test_conflicts.py +0 -0
  44. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/tests/test_duplicates.py +0 -0
  45. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/tests/test_empty_blocks.py +0 -0
  46. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/tests/test_health.py +0 -0
  47. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/tests/test_parser.py +0 -0
  48. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/upstream_checker/__init__.py +0 -0
  49. {nginx_lens-0.1.5 → nginx_lens-0.1.7}/upstream_checker/checker.py +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Daniil Astrouski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nginx-lens
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx
5
5
  Author: Daniil Astrouski
6
6
  Author-email: shelovesuastra@gmail.com
7
7
  Requires-Python: >=3.8
8
+ License-File: LICENSE
8
9
  Requires-Dist: typer[all]>=0.9.0
9
10
  Requires-Dist: rich>=13.0.0
10
11
  Requires-Dist: requests>=2.25.0
11
12
  Dynamic: author
12
13
  Dynamic: author-email
14
+ Dynamic: license-file
13
15
  Dynamic: requires-dist
14
16
  Dynamic: requires-python
15
17
  Dynamic: summary
@@ -0,0 +1,81 @@
1
+ # nginx-lens
2
+
3
+ **nginx-lens** — современный CLI-инструмент для анализа, диагностики и визуализации конфигураций Nginx.
4
+
5
+ ## Зачем нужен nginx-lens?
6
+
7
+ - Быстро находит ошибки и потенциальные проблемы в ваших nginx-конфигах.
8
+ - Визуализирует маршруты и структуру — легко понять, как устроен ваш nginx.
9
+ - Показывает, какой location/server обслуживает конкретный URL.
10
+ - Анализирует логи и помогает выявить аномалии.
11
+ - Упрощает аудит, миграцию и поддержку сложных инфраструктур.
12
+
13
+ ## Примеры работы
14
+
15
+ ### Справочник утилиты
16
+
17
+ ![nginx-lens --help](docs/main-help.png)
18
+
19
+ ### Справочник для команд
20
+
21
+ ![nginx-lens <команда> --help](docs/command-help.png)
22
+
23
+ ### Доступность upstream-серверов
24
+
25
+ ![nginx-lens health <путь_к_конфигу>](docs/example-health.png)
26
+
27
+ ### Древовидная визуализация структуры конфига
28
+
29
+ ![nginx-lens tree <путь_к_конфигу>](docs/example-tree.png)
30
+
31
+ ### Древовидная визуализация include'ов
32
+
33
+ ![nginx-lens include-tree <путь_к_конфигу>](docs/example-include-tree.png)
34
+
35
+ ### Удобный анализатор логов
36
+
37
+ ![nginx-lens logs <путь_к_файлу_лога>](docs/example-logs.png)
38
+
39
+ ### Аудит конфигурации
40
+
41
+ ![nginx-lens analyze <путь_к_конфигу>](docs/example-analyze.png)
42
+
43
+ ### Визуализация маршрутов
44
+
45
+ ![nginx-lens graph <путь_к_конфигу>](docs/example-graph.png)
46
+
47
+ ### Поиск маршрута для URL
48
+
49
+ ![nginx-lens route <URL>](docs/example-route.png)
50
+
51
+ ### Сравнение конфигов
52
+
53
+ ![nginx-lens diff <путь_к_первому_конфигу> <путь_к_второму_конфигу>](docs/example-diff.png)
54
+
55
+ ### Удобная проверка синтаксиса конфига
56
+
57
+ ![nginx-lens syntax](docs/example-syntax.png)
58
+
59
+
60
+ ## Установка и системные требования
61
+
62
+ - **Python 3.8+**
63
+ - Linux/macOS (работает и под Windows WSL)
64
+
65
+ ### Установка через PyPI (рекомендуется)
66
+
67
+ ```bash
68
+ pipx install nginx-lens
69
+ ```
70
+ или
71
+ ```bash
72
+ pip install nginx-lens
73
+ ```
74
+
75
+ ## Автор
76
+
77
+ [Daniil Astrouski](https://github.com/shelovesuastra)
78
+
79
+ ## Лицензия
80
+
81
+ MIT
@@ -58,7 +58,14 @@ def analyze(config_path: str = typer.Argument(..., help="Путь к nginx.conf"
58
58
  Пример:
59
59
  nginx-lens analyze /etc/nginx/nginx.conf
60
60
  """
61
- tree = parse_nginx_config(config_path)
61
+ try:
62
+ tree = parse_nginx_config(config_path)
63
+ except FileNotFoundError:
64
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
65
+ return
66
+ except Exception as e:
67
+ console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
68
+ return
62
69
  conflicts = find_location_conflicts(tree)
63
70
  dups = find_duplicate_directives(tree)
64
71
  empties = find_empty_blocks(tree)
@@ -0,0 +1,51 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from rich.table import Table
4
+ from analyzer.diff import diff_trees
5
+ from parser.nginx_parser import parse_nginx_config
6
+ import difflib
7
+
8
+ app = typer.Typer()
9
+ console = Console()
10
+
11
+ def diff(
12
+ config1: str = typer.Argument(..., help="Первый nginx.conf"),
13
+ config2: str = typer.Argument(..., help="Второй nginx.conf")
14
+ ):
15
+ """
16
+ Сравнивает две конфигурации Nginx и выводит отличия построчно side-by-side с подсветкой.
17
+
18
+ Пример:
19
+ nginx-lens diff /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
20
+ """
21
+ try:
22
+ with open(config1) as f1, open(config2) as f2:
23
+ lines1 = f1.readlines()
24
+ lines2 = f2.readlines()
25
+ except FileNotFoundError as e:
26
+ console.print(f"[red]Файл {e.filename} не найден. Проверьте путь к конфигу.[/red]")
27
+ return
28
+ except Exception as e:
29
+ console.print(f"[red]Ошибка при чтении файлов: {e}[/red]")
30
+ return
31
+ maxlen = max(len(lines1), len(lines2))
32
+ # Выравниваем длины
33
+ lines1 += [''] * (maxlen - len(lines1))
34
+ lines2 += [''] * (maxlen - len(lines2))
35
+ table = Table(show_header=True, header_style="bold blue")
36
+ table.add_column("№", style="dim", width=4)
37
+ table.add_column("Config 1", style="white")
38
+ table.add_column("№", style="dim", width=4)
39
+ table.add_column("Config 2", style="white")
40
+ for i in range(maxlen):
41
+ l1 = lines1[i].rstrip('\n')
42
+ l2 = lines2[i].rstrip('\n')
43
+ n1 = str(i+1) if l1 else ''
44
+ n2 = str(i+1) if l2 else ''
45
+ if l1 == l2:
46
+ table.add_row(n1, l1, n2, l2)
47
+ else:
48
+ style1 = "red" if l1 else "on red"
49
+ style2 = "green" if l2 else "on green"
50
+ table.add_row(f"[bold]{n1}[/bold]" if l1 else n1, f"[{style1}]{l1}[/{style1}]" if l1 or l2 else '', f"[bold]{n2}[/bold]" if l2 else n2, f"[{style2}]{l2}[/{style2}]" if l1 or l2 else '')
51
+ console.print(table)
@@ -16,7 +16,14 @@ def graph(
16
16
  Пример:
17
17
  nginx-lens graph /etc/nginx/nginx.conf
18
18
  """
19
- tree = parse_nginx_config(config_path)
19
+ try:
20
+ tree = parse_nginx_config(config_path)
21
+ except FileNotFoundError:
22
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
23
+ return
24
+ except Exception as e:
25
+ console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
26
+ return
20
27
  routes = []
21
28
  # Для каждого server/location строим маршрут
22
29
  def walk(d, chain, upstreams):
@@ -59,17 +66,61 @@ def graph(
59
66
  console.print("[yellow]Не найдено ни одного маршрута[/yellow]")
60
67
  return
61
68
  # Красивый вывод
69
+ seen = set()
62
70
  for route in routes:
71
+ if not route or route[0][0] != 'server':
72
+ continue
73
+ key = tuple(route)
74
+ if key in seen:
75
+ continue
76
+ seen.add(key)
63
77
  t = Text()
64
- for i, (typ, val) in enumerate(route):
65
- if typ == 'server':
66
- t.append(f"server: {val}", style="bold blue")
67
- elif typ == 'location':
68
- t.append(f" -> location: {val}", style="yellow")
78
+ # Получаем label для server
79
+ server_val = route[0][1]
80
+ server_block = None
81
+ # Найти сам блок server по arg
82
+ for d in tree.directives:
83
+ if d.get('block') == 'server' and (d.get('arg','') == server_val or (not d.get('arg') and (server_val == '[no arg]' or not server_val))):
84
+ server_block = d
85
+ break
86
+ label = get_server_label(server_block) if server_block else (server_val or 'default')
87
+ t.append(f"[", style="white")
88
+ t.append(f"server: {label}", style="bold blue")
89
+ t.append("]", style="white")
90
+ for i, (typ, val) in enumerate(route[1:]):
91
+ if typ == 'location':
92
+ t.append(f" -> [", style="white")
93
+ t.append(f"location: {val}", style="yellow")
94
+ t.append("]", style="white")
69
95
  elif typ == 'proxy_pass':
70
96
  t.append(f" -> proxy_pass: {val}", style="green")
71
97
  elif typ == 'upstream':
72
- t.append(f" -> upstream: {val}", style="magenta")
98
+ t.append(f" -> [", style="white")
99
+ t.append(f"upstream: {val}", style="magenta")
100
+ t.append("]", style="white")
73
101
  elif typ == 'upstream_server':
74
- t.append(f" -> server: {val}", style="grey50")
75
- console.print(t)
102
+ t.append(f" -> [", style="white")
103
+ t.append(f"server: {val}", style="grey50")
104
+ t.append("]", style="white")
105
+ console.print(t)
106
+
107
+ def get_server_label(server_block):
108
+ arg = server_block.get('arg')
109
+ if arg and arg != '[no arg]':
110
+ return arg
111
+ # Ищем server_name
112
+ names = []
113
+ listens = []
114
+ for sub in server_block.get('directives', []):
115
+ if sub.get('directive') == 'server_name':
116
+ names += sub.get('args', '').split()
117
+ if sub.get('directive') == 'listen':
118
+ listens.append(sub.get('args', ''))
119
+ if names:
120
+ return ' '.join(names)
121
+ if listens:
122
+ return ','.join(listens)
123
+ return 'default'
124
+
125
+ if __name__ == "__main__":
126
+ app()
@@ -19,7 +19,14 @@ def health(
19
19
  nginx-lens health /etc/nginx/nginx.conf
20
20
  nginx-lens health /etc/nginx/nginx.conf --timeout 5 --retries 3
21
21
  """
22
- tree = parse_nginx_config(config_path)
22
+ try:
23
+ tree = parse_nginx_config(config_path)
24
+ except FileNotFoundError:
25
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
26
+ return
27
+ except Exception as e:
28
+ console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
29
+ return
23
30
  upstreams = tree.get_upstreams()
24
31
  results = check_upstreams(upstreams, timeout=timeout, retries=retries)
25
32
  table = Table(show_header=True, header_style="bold blue")
@@ -18,7 +18,14 @@ def include_tree(
18
18
  nginx-lens include-tree /etc/nginx/nginx.conf
19
19
  nginx-lens include-tree /etc/nginx/nginx.conf --directive server_name
20
20
  """
21
- tree = build_include_tree(config_path)
21
+ try:
22
+ tree = build_include_tree(config_path)
23
+ except FileNotFoundError:
24
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
25
+ return
26
+ except Exception as e:
27
+ console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
28
+ return
22
29
  rich_tree = Tree(f"[bold blue]{config_path}[/bold blue]")
23
30
  def _add(node, t):
24
31
  for k, v in t.items():
@@ -26,28 +26,36 @@ def logs(
26
26
  Пример:
27
27
  nginx-lens logs /var/log/nginx/access.log --top 20
28
28
  """
29
+ try:
30
+ with open(log_path) as f:
31
+ lines = list(f)
32
+ except FileNotFoundError:
33
+ console.print(f"[red]Файл {log_path} не найден. Проверьте путь к логу.[/red]")
34
+ return
35
+ except Exception as e:
36
+ console.print(f"[red]Ошибка при чтении {log_path}: {e}[/red]")
37
+ return
29
38
  status_counter = Counter()
30
39
  path_counter = Counter()
31
40
  ip_counter = Counter()
32
41
  user_agent_counter = Counter()
33
42
  errors = defaultdict(list)
34
- with open(log_path) as f:
35
- for line in f:
36
- m = log_line_re.search(line)
37
- if m:
38
- ip = m.group('ip')
39
- path = m.group('path')
40
- status = m.group('status')
41
- status_counter[status] += 1
42
- path_counter[path] += 1
43
- ip_counter[ip] += 1
44
- if status.startswith('4') or status.startswith('5'):
45
- errors[status].append(path)
46
- # user-agent (если есть)
47
- if '" "' in line:
48
- ua = line.rsplit('" "', 1)[-1].strip().strip('"')
49
- if ua:
50
- user_agent_counter[ua] += 1
43
+ for line in lines:
44
+ m = log_line_re.search(line)
45
+ if m:
46
+ ip = m.group('ip')
47
+ path = m.group('path')
48
+ status = m.group('status')
49
+ status_counter[status] += 1
50
+ path_counter[path] += 1
51
+ ip_counter[ip] += 1
52
+ if status.startswith('4') or status.startswith('5'):
53
+ errors[status].append(path)
54
+ # user-agent (если есть)
55
+ if '" "' in line:
56
+ ua = line.rsplit('" "', 1)[-1].strip().strip('"')
57
+ if ua:
58
+ user_agent_counter[ua] += 1
51
59
  # Топ статусов
52
60
  table = Table(title="Top HTTP Status Codes", show_header=True, header_style="bold blue")
53
61
  table.add_column("Status")
@@ -6,19 +6,22 @@ from parser.nginx_parser import parse_nginx_config
6
6
  import glob
7
7
  import os
8
8
 
9
- app = typer.Typer()
9
+ app = typer.Typer(help="Показывает, какой server/location обслуживает указанный URL. По умолчанию ищет во всех .conf в /etc/nginx/. Для кастомного пути используйте -c/--config.")
10
10
  console = Console()
11
11
 
12
12
  def route(
13
- config_path: str = typer.Argument(None, help="Путь к nginx.conf (если не указан — поиск по всем .conf в /etc/nginx)", show_default=False),
14
- url: str = typer.Argument(..., help="URL для маршрутизации (например, http://host/path)")
13
+ url: str = typer.Argument(..., help="URL для маршрутизации (например, http://host/path)"),
14
+ config_path: str = typer.Option(None, "-c", "--config", help="Путь к кастомному nginx.conf (если не указан — поиск по всем .conf в /etc/nginx)")
15
15
  ):
16
16
  """
17
17
  Показывает, какой server/location обслуживает указанный URL.
18
18
 
19
- Пример:
20
- nginx-lens route /etc/nginx/nginx.conf http://example.com/api/v1
19
+ По умолчанию ищет во всех .conf в /etc/nginx/.
20
+ Для кастомного пути используйте опцию -c/--config.
21
+
22
+ Примеры:
21
23
  nginx-lens route http://example.com/api/v1
24
+ nginx-lens route -c /etc/nginx/nginx.conf http://example.com/api/v1
22
25
  """
23
26
  configs = []
24
27
  if config_path:
@@ -31,8 +34,12 @@ def route(
31
34
  for conf in configs:
32
35
  try:
33
36
  tree = parse_nginx_config(conf)
37
+ except FileNotFoundError:
38
+ console.print(f"[red]Файл {conf} не найден. Проверьте путь к конфигу.[/red]")
39
+ continue
34
40
  except Exception as e:
35
- continue # пропускаем битые/невалидные
41
+ console.print(f"[red]Ошибка при разборе {conf}: {e}[/red]")
42
+ continue
36
43
  res = find_route(tree, url)
37
44
  if res:
38
45
  server = res['server']
@@ -33,7 +33,12 @@ def syntax(
33
33
  if not config_path:
34
34
  console.print("[red]Не удалось найти nginx.conf. Укажите путь через -c.[/red]")
35
35
  return
36
+ if not os.path.isfile(config_path):
37
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
38
+ return
36
39
  cmd = [nginx_path, "-t", "-c", os.path.abspath(config_path)]
40
+ if hasattr(os, 'geteuid') and os.geteuid() != 0:
41
+ cmd = ["sudo"] + cmd
37
42
  try:
38
43
  result = subprocess.run(cmd, capture_output=True, text=True, check=False)
39
44
  if result.returncode == 0:
@@ -41,8 +46,8 @@ def syntax(
41
46
  return
42
47
  else:
43
48
  console.print("[red]Ошибка синтаксиса![/red]")
44
- console.print(result.stdout)
45
- console.print(result.stderr)
49
+ console.print(result.stdout)
50
+ console.print(result.stderr)
46
51
  # Парсим все ошибки
47
52
  err = result.stderr or result.stdout
48
53
  errors = list(ERRORS_RE.finditer(err))
@@ -34,7 +34,14 @@ def tree(
34
34
  nginx-lens tree /etc/nginx/nginx.conf --markdown
35
35
  nginx-lens tree /etc/nginx/nginx.conf --html
36
36
  """
37
- tree_obj = parse_nginx_config(config_path)
37
+ try:
38
+ tree_obj = parse_nginx_config(config_path)
39
+ except FileNotFoundError:
40
+ console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
41
+ return
42
+ except Exception as e:
43
+ console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
44
+ return
38
45
  root = RichTree(f"[bold blue]nginx.conf[/bold blue]")
39
46
  _build_tree(tree_obj.directives, root)
40
47
  if markdown:
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nginx-lens
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx
5
5
  Author: Daniil Astrouski
6
6
  Author-email: shelovesuastra@gmail.com
7
7
  Requires-Python: >=3.8
8
+ License-File: LICENSE
8
9
  Requires-Dist: typer[all]>=0.9.0
9
10
  Requires-Dist: rich>=13.0.0
10
11
  Requires-Dist: requests>=2.25.0
11
12
  Dynamic: author
12
13
  Dynamic: author-email
14
+ Dynamic: license-file
13
15
  Dynamic: requires-dist
14
16
  Dynamic: requires-python
15
17
  Dynamic: summary
@@ -1,3 +1,5 @@
1
+ LICENSE
2
+ README.md
1
3
  pyproject.toml
2
4
  setup.py
3
5
  analyzer/__init__.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="nginx-lens",
5
- version="0.1.5",
5
+ version="0.1.7",
6
6
  description="CLI-инструмент для анализа, визуализации и диагностики конфигураций Nginx",
7
7
  author="Daniil Astrouski",
8
8
  author_email="shelovesuastra@gmail.com",
@@ -1,39 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
- from rich.table import Table
4
- from analyzer.diff import diff_trees
5
- from parser.nginx_parser import parse_nginx_config
6
-
7
- app = typer.Typer()
8
- console = Console()
9
-
10
- def diff(
11
- config1: str = typer.Argument(..., help="Первый nginx.conf"),
12
- config2: str = typer.Argument(..., help="Второй nginx.conf")
13
- ):
14
- """
15
- Сравнивает две конфигурации Nginx и выводит отличия side-by-side.
16
-
17
- Пример:
18
- nginx-lens diff /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
19
- """
20
- tree1 = parse_nginx_config(config1)
21
- tree2 = parse_nginx_config(config2)
22
- diffs = diff_trees(tree1, tree2)
23
- if not diffs:
24
- console.print("[green]Конфигурации идентичны[/green]")
25
- return
26
- table = Table(show_header=True, header_style="bold blue")
27
- table.add_column("Config 1", style="red")
28
- table.add_column("Config 2", style="green")
29
- for d in diffs:
30
- path = "/".join(d['path'])
31
- if d['type'] == 'added':
32
- table.add_row("", f"+ {path}")
33
- elif d['type'] == 'removed':
34
- table.add_row(f"- {path}", "")
35
- elif d['type'] == 'changed':
36
- v1 = str(d['value1'])
37
- v2 = str(d['value2'])
38
- table.add_row(f"! {path}\n{v1}", f"! {path}\n{v2}")
39
- console.print(table)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes