Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,21 @@ WEBLATE_SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https
# ---------------------------------------------------------------------------
# PostgreSQL (host)
# ---------------------------------------------------------------------------
# CD compose pins POSTGRES_HOST and POSTGRES_PORT in docker-compose.cd.yml
# (host.docker.internal, default 5432). Values below apply via env_file for user/db.
# CD compose defaults (override in .env if host Postgres/Redis differ).
POSTGRES_HOST=host.docker.internal
POSTGRES_PORT=5432

POSTGRES_USER=weblate_app
# Canonical name per Weblate Docker docs (POSTGRES_DATABASE is an alias some images accept).
POSTGRES_DB=weblate_db
POSTGRES_DATABASE=weblate_db
# Ignored in CD (compose pins host/port). Documented for reference / non-CD stacks.
# POSTGRES_HOST=host.docker.internal
# POSTGRES_PORT=5432

# Optional: POSTGRES_PASSWORD_FILE=/run/secrets/db_password

# Logical DB 1 avoids clashing with other apps on the same Redis (default for Weblate is 1).
REDIS_DB=1

# Reference only for CI / local overrides (not used by docker-compose.cd.yml):
# REDIS_HOST=redis
# REDIS_PORT=6379
REDIS_HOST=redis
REDIS_PORT=6379

# ---------------------------------------------------------------------------
# Mail server (production: required for notifications; env_file only)
Expand Down
4 changes: 2 additions & 2 deletions docker/docker-compose.cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ services:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env}
WEBLATE_ADMIN_PASSWORD: ${WEBLATE_ADMIN_PASSWORD:?set in .env}
POSTGRES_HOST: host.docker.internal
POSTGRES_HOST: ${POSTGRES_HOST:-host.docker.internal}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
REDIS_HOST: redis
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
healthcheck:
test: [CMD, curl, -sf, 'http://localhost:8080${WEBLATE_URL_PREFIX:-}/healthz/']
Expand Down
208 changes: 112 additions & 96 deletions src/boost_weblate/utils/quickbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ def _parse_qbk(
break
continue

# Whitespace after inline bracket markup on the same line (e.g. "[@url] prose").
if ch in {" ", "\t"}:
while i < stop and content[i] in {" ", "\t"}:
i += 1
continue
Comment thread
AuraMindNest marked this conversation as resolved.

if content[i : i + 3] == "'''":
i += 3
while i < stop and content[i : i + 3] != "'''":
Expand All @@ -507,128 +513,138 @@ def _parse_qbk(
i = end + 1

if kw in _SKIP_KEYWORDS or kw in _SKIP_SINGLE_CHARS:
continue

raw_inner = block_text[content_off:-1]
lstrip_n = len(raw_inner) - len(raw_inner.lstrip())
rstrip_n = len(raw_inner) - len(raw_inner.rstrip())
inner = raw_inner.strip()
if not inner:
continue

inner_abs_start = bracket_start + content_off + lstrip_n
inner_abs_end = bracket_start + len(block_text) - 1 - rstrip_n
inner_multiline = "\n" in inner

if kw in _HEADING_KEYWORDS:
ctx = f"heading {kw[1]}" if kw != "heading" else "heading"
segments.append(
_Seg(
inner_abs_start,
inner_abs_end,
bracket_line,
"heading",
inner,
no_wrap=True,
context=ctx,
eol = end + 1
while eol < stop and content[eol] != "\n":
eol += 1
if _has_prose(content[end + 1 : eol]):
i = bracket_start
else:
continue
else:
raw_inner = block_text[content_off:-1]
lstrip_n = len(raw_inner) - len(raw_inner.lstrip())
rstrip_n = len(raw_inner) - len(raw_inner.rstrip())
inner = raw_inner.strip()
if not inner:
continue

inner_abs_start = bracket_start + content_off + lstrip_n
inner_abs_end = bracket_start + len(block_text) - 1 - rstrip_n
inner_multiline = "\n" in inner

if kw in _HEADING_KEYWORDS:
ctx = f"heading {kw[1]}" if kw != "heading" else "heading"
segments.append(
_Seg(
inner_abs_start,
inner_abs_end,
bracket_line,
"heading",
inner,
no_wrap=True,
context=ctx,
)
)
)
continue

if kw == "section":
if inner_multiline:
nl_pos = inner.index("\n")
raw_title_line = inner[:nl_pos]
title = raw_title_line.strip()
if title:
title_lead = raw_title_line.index(title)
title_abs_start = inner_abs_start + title_lead
title_abs_end = title_abs_start + len(title)
continue

if kw == "section":
if inner_multiline:
nl_pos = inner.index("\n")
raw_title_line = inner[:nl_pos]
title = raw_title_line.strip()
if title:
title_lead = raw_title_line.index(title)
title_abs_start = inner_abs_start + title_lead
title_abs_end = title_abs_start + len(title)
segments.append(
_Seg(
title_abs_start,
title_abs_end,
bracket_line,
"section-title",
title,
no_wrap=True,
context="section title",
)
)
body_abs_start = inner_abs_start + nl_pos + 1
if body_abs_start < inner_abs_end:
segments.extend(
_parse_qbk(
content, body_abs_start, inner_abs_end, _depth + 1
)
)
else:
segments.append(
_Seg(
title_abs_start,
title_abs_end,
inner_abs_start,
inner_abs_end,
bracket_line,
"section-title",
title,
inner,
no_wrap=True,
context="section title",
)
)
body_abs_start = inner_abs_start + nl_pos + 1
if body_abs_start < inner_abs_end:
continue

if kw in _ADMONITION_KEYWORDS:
if inner_multiline:
segments.extend(
_parse_qbk(
content, body_abs_start, inner_abs_end, _depth + 1
content, inner_abs_start, inner_abs_end, _depth + 1
)
)
else:
segments.append(
_Seg(
inner_abs_start,
inner_abs_end,
bracket_line,
"section-title",
inner,
no_wrap=True,
context="section title",
else:
segments.append(
_Seg(
inner_abs_start,
inner_abs_end,
bracket_line,
"admonition",
inner,
no_wrap=False,
context=kw,
)
)
)
continue
continue

if kw in _ADMONITION_KEYWORDS:
if inner_multiline:
segments.extend(
_parse_qbk(content, inner_abs_start, inner_abs_end, _depth + 1)
)
else:
segments.append(
_Seg(
inner_abs_start,
inner_abs_end,
bracket_line,
"admonition",
inner,
no_wrap=False,
context=kw,
if kw == ":":
if inner_multiline:
segments.extend(
_parse_qbk(
content, inner_abs_start, inner_abs_end, _depth + 1
)
)
)
continue
else:
segments.append(
_Seg(
inner_abs_start,
inner_abs_end,
bracket_line,
"blockquote",
inner,
no_wrap=False,
context="blockquote",
)
)
continue

if kw == ":":
if inner_multiline:
if kw in {"table", "variablelist"}:
segments.extend(
_parse_qbk(content, inner_abs_start, inner_abs_end, _depth + 1)
)
else:
segments.append(
_Seg(
_parse_table_inner(
content,
inner_abs_start,
inner_abs_end,
bracket_line,
"blockquote",
inner,
no_wrap=False,
context="blockquote",
kw,
_depth,
)
)
continue
continue

if kw in {"table", "variablelist"}:
segments.extend(
_parse_table_inner(
content,
inner_abs_start,
inner_abs_end,
bracket_line,
kw,
_depth,
)
)
continue

continue

para_start = i
para_line = line

Expand Down
13 changes: 13 additions & 0 deletions tests/utils/test_quickbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,19 @@ def test_parse_qbk_paragraph_breaks_on_soft_wrap_space_line() -> None:
assert segs[0].msgid == "first line"


def test_parse_qbk_inline_url_followed_by_prose_on_same_line() -> None:
segs = _parse_qbk("[@https://example.com/page HEAD 请求] 方法表示客户端。\n")
assert len(segs) == 1
assert segs[0].seg_type == "paragraph"
assert segs[0].msgid == "[@https://example.com/page HEAD 请求] 方法表示客户端。"

segs = _parse_qbk("[@https://example.com x] prose.\n")
assert len(segs) == 1
assert segs[0].msgid == "[@https://example.com x] prose."

assert _parse_qbk("[@https://example.com x]\n") == []

Comment thread
AuraMindNest marked this conversation as resolved.

def test_parse_qbk_paragraph_unclosed_bracket_then_para_break() -> None:
text = "intro\n[note not closed here\n[h2 real]\n"
segs = _parse_qbk(text)
Expand Down
Loading