Skip to content

Commit 1e87257

Browse files
authored
Merge pull request #12 from DogFortune/issue/4-console
コンソール出力の実装
2 parents 912ef50 + 3351503 commit 1e87257

14 files changed

Lines changed: 204 additions & 90 deletions

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Contribution Guide
2+
3+
## Pull Request
4+
We welcome anyone who can adhere to our [code of conduct.](CODE_OF_CONDUCT_JP.md)
5+
6+
## Test
7+
### Code Style
8+
Please adhere to the `black` style guide. Please adhere to the `flake8` lint rules.

Pipfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ verify_ssl = true
44
name = "pypi"
55

66
[packages]
7-
tqdm = "*"
87
dataclasses-json = "*"
98

109
[dev-packages]

Pipfile.lock

Lines changed: 21 additions & 38 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,31 @@
11
# linkstat
2+
[![test-lint-format](https://github.com/DogFortune/linkstat/actions/workflows/lint-test-format.yml/badge.svg?branch=main)](https://github.com/DogFortune/linkstat/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
23

4+
_linkstat_ is a script that verifies the connectivity of links documented in the documentation. By detecting broken links early, it maintains the integrity of the documentation.
5+
Currently, only Markdown files (*.md) are supported.
6+
7+
## Caution
8+
This library accesses services during runtime, so executing it in large quantities will cause load on the target service. When performing functional verification or integrating into CI/CD, please ensure the load on the linked service is minimized as much as possible.
9+
10+
## Install
11+
12+
```sh
13+
pip install linkstat
14+
```
15+
16+
## Usage
17+
18+
```sh
19+
linkstat {source_file_or_directory}
20+
```
21+
22+
## Output
23+
24+
You can output reports in JSON format by using the option.
25+
26+
```sh
27+
linkstat --report-json {path} {source_file_or_directory}
28+
```
29+
30+
## Contribute
31+
[Guideline](CONTRIBUTING.md)

README_JP.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# linkstat
2-
<div align="center">
3-
[![test-lint-format](https://github.com/DogFortune/linkstat/actions/workflows/lint-test-format.yml/badge.svg?branch=main)](https://github.com/DogFortune/linkstat/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4-
</div>
2+
3+
[![test-lint-format](https://github.com/DogFortune/linkstat/actions/workflows/lint-test-format.yml/badge.svg)](https://github.com/DogFortune/linkstat/actions/workflows/lint-test-format.yml)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
55

66
_linkstat_ はドキュメントに記載されているリンクの疎通確認を行うスクリプトです。リンク切れの早期発見を行う事でドキュメントの健全性を保ちます。
77
現在対応しているのはMarkdownファイル(*.md)のみです。
88

9+
## 注意
10+
本ライブラリは実行時にサービスへアクセスするため、大量に実行すると相手サービスに負荷が発生します。動作確認及びCI/CDに取り込む場合は、リンク先のサービス負荷は限りなく少なくして下さい。
11+
912
## インストール
1013

1114
```sh
@@ -18,8 +21,6 @@ pip install linkstat
1821
linkstat {source_file_or_directory}
1922
```
2023

21-
パスを指定しない場合はカレントディレクトリを検査対象とします。
22-
2324
## 出力
2425
オプションを使用することでJSON形式のレポートを出力できます。
2526

THIRD-PARTY-LICENSES.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ The MIT License (MIT)
3030
Copyright (c) 2018 Łukasz Langa
3131
https://github.com/psf/black/blob/43135e9fafccbca723910a45aa62f0f182e85e5f/LICENSE
3232

33-
## tqdm
34-
MPL v2.0 and MIT
35-
Copyright (c) 2013 noamraph
36-
https://github.com/tqdm/tqdm/blob/0ed5d7f18fa3153834cbac0aa57e8092b217cc16/LICENCE
37-
3833
## Dataclasses JSON
3934
The MIT License
4035
Copyright (c) 2019 Charles Li and contributors

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ description = "Perform connectivity checks on URLs listed in the Markdown"
55
license-files = ["LICENSE"]
66
readme = "README.md"
77
requires-python = ">=3.10"
8-
version = "0.0.2"
9-
dependencies = ["tqdm", "dataclasses-json"]
8+
version = "1.0.0"
9+
dependencies = ["dataclasses-json"]
1010
keywords = ["check", "url", "link"]
1111
classifiers = [
12-
"Development Status :: 3 - Alpha",
12+
"Development Status :: 5 - Production/Stable",
1313
"Intended Audience :: Developers",
1414
"License :: OSI Approved :: MIT License",
1515
"Programming Language :: Python :: 3 :: Only",

src/linkstat/analyzer.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from linkstat.reporter import ReportData
66
from dataclasses import dataclass
77
import re
8-
from tqdm import tqdm
98

109
URL_PATTERN = r'https?://[^\s\)\]>"]+'
1110
URL_RE = re.compile(URL_PATTERN)
@@ -50,29 +49,28 @@ def request(url: str) -> AnalyzeResponse:
5049

5150

5251
def check_links(links: dict[str, URLInfo]) -> list[ReportData]:
53-
"""URLの疎通確認を行います。確認を行うのは重複していないものだけです
52+
"""URLの疎通確認を行います。確認を行うのは重複していないものだけ
5453
5554
:param links: URLリスト
5655
:type links: dict[str, URLInfo]
57-
:return: 確認結果
56+
:return: 確認結果(重複したURLは除く)
5857
:rtype: list[ReportData]
5958
"""
6059
results = []
61-
with tqdm(links.items()) as links_prog:
62-
for file_path, link_items in links_prog:
63-
links_prog.set_description(file_path)
64-
for item in tqdm(link_items):
65-
if not item.duplicate:
66-
res = request(item.url)
67-
data = ReportData(
68-
file_path,
69-
item.line,
70-
item.url,
71-
res.result,
72-
res.code,
73-
res.reason,
74-
)
75-
results.append(data)
60+
for file_path, link_items in links.items():
61+
for item in link_items:
62+
if not item.duplicate:
63+
res = request(item.url)
64+
data = ReportData(
65+
file_path,
66+
item.line,
67+
item.url,
68+
res.result,
69+
res.code,
70+
res.reason,
71+
)
72+
print(f"{data.url}: {data.result}")
73+
results.append(data)
7674
return results
7775

7876

src/linkstat/app.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from pathlib import Path
32
from . import analyzer
43
from . import reporter
@@ -19,7 +18,7 @@ def __output(data: list[ReportData], format: OutputType, args):
1918
"""
2019
match format:
2120
case OutputType.Console:
22-
line = reporter.console(data)
21+
line = reporter.get_summary_message(data)
2322
print(line)
2423
case OutputType.Json:
2524
output_path = args.report_json
@@ -46,7 +45,7 @@ def __format_setting(args) -> OutputType:
4645

4746
def create_parser():
4847
parser = argparse.ArgumentParser()
49-
parser.add_argument("src", default=os.environ.get("SRC_DIR", "."))
48+
parser.add_argument("src")
5049
parser.add_argument(
5150
"--report-json", type=str, help="Create json report file at given path"
5251
)
@@ -60,6 +59,8 @@ def main(args=None):
6059
format = __format_setting(parsed_args)
6160
src = parsed_args.src
6261

62+
start_msg = reporter.get_fill_plain_message(" linkstat start ")
63+
print(start_msg)
6364
files = analyzer.search(src)
6465
links = analyzer.extract_url(files)
6566
report_data_list = analyzer.check_links(links)

src/linkstat/reporter.py

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from dataclasses import dataclass
22
from dataclasses_json import dataclass_json
3-
from pprint import pformat
3+
from linkstat.enums import Result
44
from typing import List
55
import json
66
import os
7+
import shutil
78

89

910
@dataclass_json
@@ -12,9 +13,9 @@ class ReportData:
1213
file: str
1314
line: int
1415
url: str
15-
result: str
16+
result: Result
1617
code: int
17-
reason: str
18+
reason: str | None
1819

1920

2021
@dataclass_json
@@ -31,10 +32,78 @@ def default(self, obj):
3132
return super().default(obj)
3233

3334

34-
def console(data: list[ReportData]):
35-
# TODO: 出力形式は仮でpformatを設定中。
36-
line = pformat(data)
37-
return line
35+
class Colors:
36+
"""カラーコード"""
37+
38+
RED = "\033[91m"
39+
GREEN = "\033[92m"
40+
YELLOW = "\033[93m"
41+
RESET = "\033[0m"
42+
43+
44+
def get_fill_plain_message(msg: str) -> str:
45+
"""区切り線が入ったメッセージテキストを作成します。
46+
メソッド自体はメッセージを作るだけで、出力は呼び出し側が行って下さい。
47+
48+
:param msg: _description_
49+
:type msg: str
50+
:return: _description_
51+
:rtype: str
52+
"""
53+
fill_char = "="
54+
terminal_width = shutil.get_terminal_size(fallback=(80, 24)).columns
55+
if terminal_width < 40:
56+
terminal_width = 80
57+
58+
total_fill = terminal_width - len(msg)
59+
left_fill = total_fill // 2
60+
right_fill = total_fill - left_fill
61+
62+
start_message = f"{fill_char*left_fill}{msg}{fill_char*right_fill}"
63+
return start_message
64+
65+
66+
def get_summary_message(data: list[ReportData]):
67+
"""レポート内容を元にサマリーを作成します。
68+
チェックしたURLの数、OK,NGの数、NGのものはURLを出す。
69+
70+
:param data: _description_
71+
:type data: list[ReportData]
72+
:return: _description_
73+
:rtype: _type_
74+
"""
75+
total_count = len(data)
76+
ok_count = sum(item.result == Result.OK for item in data)
77+
ng_items = [item for item in data if item.result == Result.NG]
78+
79+
total_part = f"{Colors.GREEN}{total_count} Total{Colors.RESET}"
80+
ok_part = f"{Colors.GREEN}{ok_count} OK{Colors.RESET}"
81+
82+
color_message = f" {total_part}, {ok_part}"
83+
plain_message = f" {total_count} Total, {ok_count} OK"
84+
summary_message = ""
85+
86+
if (ng_count := len(ng_items)) == 0:
87+
fill_char = f"{Colors.GREEN}={Colors.RESET}"
88+
else:
89+
print(get_fill_plain_message(" FAILURES "))
90+
ng_detail = "\n".join([f"{item.url}: {item.reason}" for item in ng_items])
91+
summary_message += f"{ng_detail}" + "\n"
92+
ng_part = f"{Colors.RED}{ng_count} NG{Colors.RESET}"
93+
fill_char = f"{Colors.RED}={Colors.RESET}"
94+
color_message += f", {ng_part} "
95+
plain_message += f", {ng_count} NG "
96+
97+
terminal_width = shutil.get_terminal_size(fallback=(80, 24)).columns
98+
if terminal_width < 40:
99+
terminal_width = 80
100+
101+
total_fill = terminal_width - len(plain_message)
102+
left_fill = total_fill // 2
103+
right_fill = total_fill - left_fill
104+
105+
summary_message += f"{fill_char*left_fill}{color_message}{fill_char*right_fill}"
106+
return summary_message
38107

39108

40109
def dump_json(data: list[ReportData], output_path: str):

0 commit comments

Comments
 (0)