提交完整的joyhub_backend目录,包含library和test_case
This commit is contained in:
189
joyhub_backend/library/hubops_parser.py
Normal file
189
joyhub_backend/library/hubops_parser.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
DEFAULT_HUBOPS_PATH = os.path.join(PROJECT_ROOT, "HubOps.md")
|
||||
|
||||
|
||||
class HubOpsParser(object):
|
||||
def __init__(self, file_path=None):
|
||||
self.file_path = file_path or os.getenv("JOYHUB_DOC_PATH", DEFAULT_HUBOPS_PATH)
|
||||
|
||||
def parse(self):
|
||||
with open(self.file_path, "r", encoding="utf-8") as file:
|
||||
lines = file.read().splitlines()
|
||||
|
||||
cases = []
|
||||
headings = []
|
||||
for index, line in enumerate(lines):
|
||||
heading = self._parse_heading(line)
|
||||
if heading:
|
||||
level, title = heading
|
||||
headings = [item for item in headings if item[0] < level]
|
||||
headings.append((level, title))
|
||||
continue
|
||||
|
||||
if line.strip() != "**接口URL**":
|
||||
continue
|
||||
|
||||
case = self._parse_case(lines, index, headings)
|
||||
if case.get("url"):
|
||||
case["case_id"] = "joyhub_{:04d}".format(len(cases) + 1)
|
||||
cases.append(case)
|
||||
return cases
|
||||
|
||||
def _parse_case(self, lines, url_index, headings):
|
||||
title = self._case_title(headings, url_index)
|
||||
url = self._next_quote_value(lines, url_index)
|
||||
method = self._find_section_quote(lines, url_index, "**请求方式**") or "POST"
|
||||
content_type = self._find_section_quote(lines, url_index, "**Content-Type**") or "json"
|
||||
body_text = self._find_code_block(lines, url_index, "**请求Body参数**")
|
||||
query = self._find_param_table(lines, url_index, "**请求Query参数**")
|
||||
headers = self._find_headers(lines, url_index)
|
||||
body = self._parse_body(body_text)
|
||||
return {
|
||||
"name": title,
|
||||
"method": method.upper(),
|
||||
"url": url,
|
||||
"content_type": content_type,
|
||||
"headers": headers,
|
||||
"query": query,
|
||||
"body": body,
|
||||
"raw_body": body_text,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _parse_heading(line):
|
||||
match = re.match(r"^(#{2,6})\s+(.+?)\s*$", line)
|
||||
if not match:
|
||||
return None
|
||||
return len(match.group(1)), match.group(2).strip()
|
||||
|
||||
@staticmethod
|
||||
def _case_title(headings, url_index):
|
||||
if headings:
|
||||
return " / ".join(title for _, title in headings[-3:])
|
||||
return "HubOps接口{}".format(url_index + 1)
|
||||
|
||||
@staticmethod
|
||||
def _next_quote_value(lines, start):
|
||||
for index in range(start + 1, min(start + 8, len(lines))):
|
||||
line = lines[index].strip()
|
||||
if line.startswith(">"):
|
||||
value = line[1:].strip()
|
||||
if value and value != "暂无参数":
|
||||
return value
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _section_end(lines, start):
|
||||
for index in range(start + 1, len(lines)):
|
||||
line = lines[index].strip()
|
||||
if line.startswith("## ") or line.startswith("### ") or line == "**接口URL**":
|
||||
return index
|
||||
return len(lines)
|
||||
|
||||
def _find_section_quote(self, lines, url_index, section_name):
|
||||
start = max(0, url_index - 80)
|
||||
end = self._section_end(lines, url_index)
|
||||
for index in range(start, min(end, len(lines))):
|
||||
if lines[index].strip() == section_name:
|
||||
return self._next_quote_value(lines, index)
|
||||
return None
|
||||
|
||||
def _find_code_block(self, lines, url_index, section_name):
|
||||
end = self._section_end(lines, url_index)
|
||||
for index in range(url_index, end):
|
||||
if lines[index].strip() != section_name:
|
||||
continue
|
||||
for code_start in range(index + 1, end):
|
||||
if lines[code_start].strip().startswith("```"):
|
||||
block = []
|
||||
for code_end in range(code_start + 1, end):
|
||||
if lines[code_end].strip().startswith("```"):
|
||||
return "\n".join(block).strip()
|
||||
block.append(lines[code_end])
|
||||
return ""
|
||||
|
||||
def _find_param_table(self, lines, url_index, section_name):
|
||||
end = self._section_end(lines, url_index)
|
||||
for index in range(url_index, end):
|
||||
if lines[index].strip() != section_name:
|
||||
continue
|
||||
params = {}
|
||||
for row_index in range(index + 1, end):
|
||||
row = lines[row_index].strip()
|
||||
if not row.startswith("|"):
|
||||
if params:
|
||||
break
|
||||
continue
|
||||
if "---" in row or "参数名" in row or "暂无参数" in row:
|
||||
continue
|
||||
columns = [column.strip() for column in row.strip("|").split("|")]
|
||||
if len(columns) >= 2 and columns[0]:
|
||||
params[columns[0]] = self._coerce_value(columns[1])
|
||||
return params
|
||||
return {}
|
||||
|
||||
def _find_headers(self, lines, url_index):
|
||||
headers = {}
|
||||
end = self._section_end(lines, url_index)
|
||||
for index in range(url_index, end):
|
||||
if lines[index].strip() != "**请求Header参数**":
|
||||
continue
|
||||
table_started = False
|
||||
for row_index in range(index + 1, end):
|
||||
row = lines[row_index].strip()
|
||||
if row.startswith("**") or row.startswith("#") or row.startswith("*"):
|
||||
break
|
||||
if not row.startswith("|"):
|
||||
if table_started:
|
||||
break
|
||||
continue
|
||||
table_started = True
|
||||
if "---" in row or "参数名" in row or "暂无参数" in row:
|
||||
continue
|
||||
columns = [column.strip() for column in row.strip("|").split("|")]
|
||||
if len(columns) >= 2 and columns[0] and columns[0] != "Authorization":
|
||||
headers[columns[0]] = columns[1]
|
||||
return headers
|
||||
return headers
|
||||
|
||||
@classmethod
|
||||
def _parse_body(cls, body_text):
|
||||
if not body_text or body_text == "暂无数据":
|
||||
return {}
|
||||
text = cls._clean_body(body_text)
|
||||
for loader in (json.loads, ast.literal_eval):
|
||||
try:
|
||||
value = loader(text)
|
||||
return value if isinstance(value, (dict, list)) else {}
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _clean_body(text):
|
||||
text = re.sub(r"//.*", "", text)
|
||||
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
|
||||
text = text.replace("{{token}}", "")
|
||||
text = re.sub(r",\s*([}\]])", r"\1", text)
|
||||
return text.strip()
|
||||
|
||||
@staticmethod
|
||||
def _coerce_value(value):
|
||||
if value in ("-", "暂无参数", ""):
|
||||
return ""
|
||||
if value.isdigit():
|
||||
return int(value)
|
||||
if value.lower() in ("true", "false"):
|
||||
return value.lower() == "true"
|
||||
return value
|
||||
|
||||
|
||||
def load_hubops_cases():
|
||||
return HubOpsParser().parse()
|
||||
Reference in New Issue
Block a user