Skip to content

Commit 10a538c

Browse files
DanMeonclaude
andcommitted
refactor: UTF-16 → codepoint 변환을 상류 SSOT 로 단일화 (v0.3.2)
변경사항: - src/ir.rs::utf16_to_cp 자체 복사본 + u32::MAX short-circuit + fallback_end 인자 + 짝 단위 테스트 2건 제거 - build_char_runs 호출부를 para.utf16_pos_to_char_idx(start_utf16) / (end_utf16) 로 치환 (상류 PR #494 / Task #484, v0.7.9 GA) - Cargo.toml 0.3.1 → 0.3.2 - CHANGELOG.md [0.3.2] 섹션 신설 (### Build — SSOT 단일화 명시 + 핀 0fb3e67 유지) - docs/upstream/issue-utf16-pos-to-char-idx.md frontmatter Active → Frozen + RESOLVED 인용 블록 추가 - docs/upstream/README.md 인덱스 row Frozen + RESOLVED 컬럼 채움 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 00174ea commit 10a538c

5 files changed

Lines changed: 19 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- 본 PR 의 a/b/c 결정 비교 + 14개 결정 historical record 는 [docs/implementation/spec-system-overhaul.md](docs/implementation/spec-system-overhaul.md) (Frozen) 가 보유.
2323
- spec body 구조 SSOT 정착 — `/new-spec` skill 안 `templates/spec.md` + `templates/adr.md` 신설 (skeleton + 섹션별 룰 보유, body 구조 SSOT). `docs/CONVENTIONS.md` 에 § Spec / ADR 본문 구조 (짧은 pointer) + § 섹션 역할 분리 — 정보 배치 룩업 (spec ↔ ADR ↔ CHANGELOG ↔ implementation log 의 cross-cutting 표) 신설. SKILL.md step 2/4/5 가 두 template 파일을 markdown 링크로 자연 참조 (공식 Claude Code skill 패턴 — 명령형). multi-template `templates/` sub-dir 은 Anthropic 공식 docs 의 `examples/` / `scripts/` 카테고리 sub-dir 패턴 + GitHub ISSUE_TEMPLATE 선례 follow.
2424

25+
## [0.3.2] — 2026-05-03
26+
27+
PATCH release. v0.2.0 IR 매핑이 보유해 온 자체 UTF-16 → codepoint 변환 복사본 (`src/ir.rs::utf16_to_cp`) 을 상류 `Paragraph::utf16_pos_to_char_idx` ([PR #494](https://github.com/edwardkim/rhwp/pull/494) / [Task #484](https://github.com/edwardkim/rhwp/issues/484), v0.7.9 GA) 로 치환해 SSOT 를 단일화한다. 알고리즘 동등 — IR 출력 byte-equal, 공개 API 변경 없음, SchemaVersion `"1.1"` 유지.
28+
29+
### Build
30+
31+
- `src/ir.rs::utf16_to_cp` 자체 복사본 + `u32::MAX` short-circuit + `fallback_end` 인자 + 짝 단위 테스트 2건 (`utf16_to_cp_sentinel_returns_fallback`, `utf16_to_cp_matches_first_ge`) 제거. `build_char_runs` 호출부를 `para.utf16_pos_to_char_idx(start_utf16)` / `(end_utf16)` 로 치환. 본 binding 운영 정책 ("상류 신뢰 + 결함 시 PR") 일관 적용 — v0.3.1 의 `Paragraph::control_text_positions` 채택과 같은 결.
32+
- `external/rhwp` submodule pin `0fb3e67` 유지 — 핀 history 에 PR #494 머지 commit `60eaa91` (2026-04-30) 포함, `cargo build` 가 시그니처 해소로 직접 검증. v0.7.9 GA 흡수는 직교 영역, 본 PATCH 영구 비목표.
33+
- 부수 정리: 본 binding 이 제출한 issue 초안 `docs/upstream/issue-utf16-pos-to-char-idx.md` in-place Frozen 전환 + `docs/upstream/README.md` 인덱스 RESOLVED 컬럼 채움.
34+
2535
## [0.3.1] — 2026-05-02
2636

2737
PATCH release. v0.3.0 의 IR 출고에서 inline 컨트롤 마커의 `Provenance.char_start` / `char_end` 가 항상 null 이던 문제를 정정. 상류 v0.7.8 의 `Paragraph::control_text_positions()` ([PR #405](https://github.com/edwardkim/rhwp/pull/405) / [Task #390](https://github.com/edwardkim/rhwp/issues/390)) 노출을 활용해 7 종 블록 (각주·미주 마커, 그림, 수식, 필드, TOC, 표) 의 zero-width character 위치를 채운다. SchemaVersion 변경 없음 (`"1.1"` 유지) — 기존에 nullable 슬롯에 정의된 `int | None` 에 non-null 값을 출고할 뿐, schema 호환 100%.
@@ -245,7 +255,8 @@ The `rhwp` Rust core is consumed via git submodule pinned to upstream commit `16
245255
- Local `maturin build --release` wheel (3.0 MB) verified end-to-end in a clean venv: install → import → `rhwp.parse``HwpLoader` load. (Note: the v0.1.0 sdist exceeded PyPI's 100 MB limit and did not upload; fixed in [0.1.1](#011--2026-04-23).)
246256
- GitHub Actions workflow (`publish.yml`) builds Linux (x86_64 + aarch64) / macOS (x86_64 + aarch64) / Windows wheels + sdist on release publish, then uploads via PyPI Trusted Publisher (OIDC).
247257

248-
[Unreleased]: https://github.com/DanMeon/rhwp-python/compare/v0.3.1...HEAD
258+
[Unreleased]: https://github.com/DanMeon/rhwp-python/compare/v0.3.2...HEAD
259+
[0.3.2]: https://github.com/DanMeon/rhwp-python/releases/tag/v0.3.2
249260
[0.3.1]: https://github.com/DanMeon/rhwp-python/releases/tag/v0.3.1
250261
[0.3.0]: https://github.com/DanMeon/rhwp-python/releases/tag/v0.3.0
251262
[0.2.0]: https://github.com/DanMeon/rhwp-python/releases/tag/v0.2.0

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rhwp-python"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
edition = "2021"
55
# ^ rust-version 미명시 — 상위 rhwp crate 정책(stable Rust, MSRV unclaimed) 준수.
66
# PyO3 0.28 이 Rust 1.83+ 요구하지만, 이는 README 에 문서로 안내

docs/upstream/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
| 이슈 | Status | 상류 등록 | RESOLVED | 비고 |
1010
|---|---|---|---|---|
1111
| [issue-find-control-text-positions.md](issue-find-control-text-positions.md) | Frozen | [edwardkim/rhwp#390](https://github.com/edwardkim/rhwp/issues/390) | 2026-04-28 ([PR #405](https://github.com/edwardkim/rhwp/pull/405)) | `Paragraph::control_text_positions(&self)` 옵션 A 채택. v0.3.1 spec 이 본 파일 참조 → 삭제 대신 in-place Frozen |
12-
| [issue-utf16-pos-to-char-idx.md](issue-utf16-pos-to-char-idx.md) | Active | [edwardkim/rhwp#484](https://github.com/edwardkim/rhwp/issues/484) | | #390 후속 같은 결. `helpers::utf16_pos_to_char_idx` 외부 노출 |
12+
| [issue-utf16-pos-to-char-idx.md](issue-utf16-pos-to-char-idx.md) | Frozen | [edwardkim/rhwp#484](https://github.com/edwardkim/rhwp/issues/484) | 2026-04-30 ([PR #494](https://github.com/edwardkim/rhwp/pull/494)) | #390 후속 같은 결. `Paragraph::utf16_pos_to_char_idx(&self)` 옵션 A 채택. v0.3.2 spec 이 본 파일 참조 → 삭제 대신 in-place Frozen |
1313

1414
## Archive 정책
1515

docs/upstream/issue-utf16-pos-to-char-idx.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
---
2-
status: Active
2+
status: Frozen
33
description: "업스트림 제안 — 'utf16_pos_to_char_idx' 외부 노출 (UTF-16 위치 → codepoint 인덱스 변환 helper). #390/PR #405 후속 같은 결"
44
last_updated: 2026-04-30
55
---
66

7+
> **RESOLVED 2026-04-30** — 상류 [PR #494](https://github.com/edwardkim/rhwp/pull/494) 머지로 본 issue 해결.
8+
79
# 업스트림 제안 — `utf16_pos_to_char_idx` 외부 노출
810

911
> 외부 binding (`rhwp-python`) 구현 중 업스트림에서 수정이 필요해 보이는 부분을 발견하여, Claude 로 조사 및 다차례 사실 검증을 거친 결과입니다.

src/ir.rs

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@ fn build_char_runs(para: &Paragraph, doc_info: &DocInfo) -> Vec<RawCharRun> {
393393
u32::MAX
394394
};
395395

396-
let start_cp = utf16_to_cp(&para.char_offsets, start_utf16, total_cp);
397-
let end_cp = utf16_to_cp(&para.char_offsets, end_utf16, total_cp);
396+
let start_cp = para.utf16_pos_to_char_idx(start_utf16);
397+
let end_cp = para.utf16_pos_to_char_idx(end_utf16);
398398

399399
if start_cp >= end_cp {
400400
continue;
@@ -418,23 +418,6 @@ fn build_char_runs(para: &Paragraph, doc_info: &DocInfo) -> Vec<RawCharRun> {
418418
runs
419419
}
420420

421-
/// UTF-16 offset → codepoint index 변환.
422-
///
423-
/// `char_offsets[i]` 는 `text.chars().nth(i)` 에 해당하는 UTF-16 시작 위치.
424-
/// 입력 `utf16` 이상인 첫 번째 codepoint 인덱스를 반환한다. 해당 offset 이
425-
/// 텍스트 끝을 넘어가면 `fallback_end` 를 반환 (텍스트 codepoint 총 길이).
426-
fn utf16_to_cp(char_offsets: &[u32], utf16: u32, fallback_end: usize) -> usize {
427-
if utf16 == u32::MAX {
428-
return fallback_end;
429-
}
430-
for (i, &off) in char_offsets.iter().enumerate() {
431-
if off >= utf16 {
432-
return i;
433-
}
434-
}
435-
fallback_end
436-
}
437-
438421
fn build_raw_table(
439422
table: &Table,
440423
outer_section: usize,
@@ -873,22 +856,6 @@ fn field_type_to_str(ft: FieldType) -> &'static str {
873856
mod tests {
874857
use super::*;
875858

876-
#[test]
877-
fn utf16_to_cp_sentinel_returns_fallback() {
878-
let offsets = vec![0u32, 1, 2];
879-
assert_eq!(utf16_to_cp(&offsets, u32::MAX, 3), 3);
880-
}
881-
882-
#[test]
883-
fn utf16_to_cp_matches_first_ge() {
884-
let offsets = vec![0u32, 1, 3, 4]; // ^ 2번째 codepoint 는 SMP 라 2 code units
885-
assert_eq!(utf16_to_cp(&offsets, 0, 4), 0);
886-
assert_eq!(utf16_to_cp(&offsets, 1, 4), 1);
887-
assert_eq!(utf16_to_cp(&offsets, 2, 4), 2); // offset 2 는 char_offsets 에 없음 → 다음 >=2 인 3을 가진 인덱스 2
888-
assert_eq!(utf16_to_cp(&offsets, 3, 4), 2);
889-
assert_eq!(utf16_to_cp(&offsets, 5, 4), 4); // fallback
890-
}
891-
892859
// * simple_eq_text_alt — 토큰 경계 인식 검증
893860

894861
#[test]

0 commit comments

Comments
 (0)