Skip to content

v0.1.0

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Aug 09:19
· 168 commits to main since this release

첫 릴리즈입니다. 아래가 최근에 바뀐 것이고, 도구 전체의 모습은 절 끝의
담긴 것에 있습니다.

  • 샘플이 전부 합성 데이터가 되었습니다. 검증에 쓰던 상용 프로젝트의 워크북을 들어내고,
    같은 규모와 같은 표기를 재현하는 가상 코퍼스로 바꿨습니다
    (샘플).

    • sprout — 출시 전 소규모. 워크북 17개·테이블 71개·109,218행. 이 도구가 설계하지
      않은 규칙으로 쓰인 시트를 그대로 읽는 쪽이고, 변환하면 무엇이 나오는가의 답입니다 —
      지원하는 모든 언어의 산출물이 커밋되어 있습니다
    • canopy — 라이브 대규모. 워크북 42개·정의된 이름 549개·셀 873만. 테이블의 경계가
      워크북의 정의된 이름인 레이아웃이고, 중첩 컬럼·매트릭스 표·지역 변종·제약 행을 전부
      담습니다. 같은 코퍼스가 .xlsx.xlsb 양쪽에 있어 두 리더의 결과가 같아야 하는
      게이트
      로 섭니다
    • 값이 아니라 분포를 재현합니다. 정본은 schema/*.tsv이고 워크북은 생성기가 만듭니다.
      표기와 값이 갈라져 있어, 레이아웃을 읽는 방식이 바뀌면 그 파일들의 diff로 드러나고
      데이터가 바뀌면 드러나지 않습니다
    • 벤치마크를 전면 재측정했습니다. 데이터셋이 바뀌었으므로 파일 크기·로드 시간·CPU·
      메모리 전부 다시 쟀고, 기준이 바뀐 사실을 문서 앞에 적었습니다
      (벤치마크)
  • 찾은 문제를 로그가 아니라 보고서로 냅니다. 실행이 끝나면 HTML 한 장과 JSON 한 장을
    남기고, 경고 이상이면 브라우저로 엽니다
    (사용 · 설계).

    • 멈춘 실행이 본론입니다. 타깃은 임포트·쿠킹·검증을 전부 통과해야 도달하는데, 이
      산출물이 가장 필요한 실행은 거기까지 가지 못한 실행입니다. 그래서 타깃이 아니라 실행 자체에
      붙어 있고, 예외로 끝난 경로에서도 씁니다. 종료 코드에는 관여하지 않습니다.
    • 구글 시트의 셀은 클릭 한 번입니다. 위치가 이미 셀 단위 딥링크였고, 그것이 로그에서
      버려지고 있었습니다. xlsx는 셀을 여는 이식 가능한 URL이 없으므로 링크인 척하지 않고
      위치 텍스트와 복사 버튼을 냅니다.
    • 누적을 수치로 보입니다. 직전 리포트와 대조해 신규 · 지속 · 해소를 셉니다. 대조는
      참고이지 판정이 아니어서 종료 코드에도 검증에도 관여하지 않습니다 — 시트에 행이 끼어들면
      같은 문제가 신규+해소 한 쌍으로 나타나기 때문이고, 그 한계는 페이지에 적혀 있습니다.
    • 겉모습은 GitHub Primer입니다 — 색 토큰 · 서체 · 박스 · 알약 라벨 · 밑줄 내비게이션 ·
      버튼까지. 이 페이지를 읽는 사람은 하루의 대부분을 그렇게 생긴 화면 앞에서 보내고,
      도구마다 제 나름의 회색 상자 방언을 쓰면 내용을 읽기 전에 방언부터 익혀야 합니다.
      검색은 찾은 자리를 표시하고, 검색창에는 지움 버튼이 있습니다.
    • 행에서 앞자리는 내용입니다. 무엇이 잘못됐는지가 먼저 오고 위치는 행 끝입니다 —
      위치는 맥락이지 내용이 아닌데 맨 왼쪽 고정폭 자리를 차지하고 있었습니다. 보고가 백틱으로
      인용한 이름은 코드로 놓이고(그 이름이 윗 행과 다른 유일한 부분입니다), 모든 위치가
      공유하는 폴더는 머리글에서 한 번만 말합니다.
    • 복사 버튼은 보고 전체를 담습니다 — 문장, 그 아래 위치, 구글 시트면 셀을 여는 링크,
      그리고 메시지 ID. 콘솔이 찍는 형태 그대로라 동료에게 그대로 보낼 수 있습니다. 위치만
      복사하던 것은 아무도 보내지 않는 것이었습니다.
    • 목록은 한 번에 하나, 행은 한 줄입니다. 문제 · 알려진 문제 · 해소됨 · 기록은 탭이고,
      행은 한 줄로 잘려 클릭하면 펼쳐집니다 — 보고 하나는 문단이고 그런 문단 14개는 벽인데
      행마다 다른 부분은 문장 앞쪽에 있기 때문입니다. 메시지 ID는 닫힌 행에 없습니다:
      CI가 필터하는 이름이지 시트를 가진 사람이 할 수 있는 일이 아니라서, 펼친 행과 종류별
      묶기의 머리글에만 둡니다.
    • 평면 목록이 아니라 워크북·시트로 묶입니다. 한 실행이 5,831건을 낸 적이 있고, 그 규모의
      평면 목록은 벽입니다. 행 하나짜리 자리는 그룹을 만들지 않고 그냥 행입니다 — 실측에서
      시트 12개가 각각 1건이었고, 그러면 항목마다 머리글을 하나씩 무는 평면 목록이 됩니다.
      묶는 축은 둘이고(시트별은 일하는 순서, 종류별은 이해하는 순서) 전환은 클릭 하나입니다.
    • 보는 사람이 없으면 열지 않습니다. CI 환경 변수 · 리다이렉트된 출력 · --silent
      설정보다 우선합니다. 마지막 리포트는 --show-report로 변환 없이 다시 엽니다.
    • 새 검출은 하나도 추가하지 않았습니다. 단계마다 수집되어 인쇄되고 버려지던 것을 남길
      뿐이고, 콘솔과 파일 로그는 그대로입니다.
  • 바뀐 것이 없으면 아무것도 하지 않습니다. 지난 실행이 무엇을 읽고 무엇을 썼는지 기록해
    두었다가 비교하고, 전부 그대로면 즉시 종료합니다
    (사용 · 설계).

    • 실측이 먼저였습니다. 테이블 548개·워크북 36개(1,149 MB)를 7개 타깃으로 내는 실행이
      161초이고, 그 절반이 임포트와 쿠킹, 나머지 절반이 binary·json 익스포트입니다 —
      코드 생성 5개 언어는 합쳐 7.5초입니다. 어느 한쪽만 캐시해도 절반밖에 줄지 않으므로
      양쪽을 함께 건너뜁니다.
    • 키가 셋입니다. recipe의 전역 설정과 입력이 모델을 정하고, Validation 섹션이 검증을,
      Targets 항목 하나하나가 그 항목의 산출을 정합니다. 그래서 타깃 하나의 설정을 고친 것이
      워크북 36개를 다시 읽을 이유가 되지 않습니다
      — 그 항목만 다시 냅니다.
    • 수정 시각만 바뀐 것은 바뀐 것이 아닙니다. 크기와 시각이 어긋난 파일만 해시해서
      내용으로 판정합니다. git checkout은 받은 파일 전부의 수정 시각을 지금으로 바꾸므로,
      시각만 보는 캐시는 체크아웃 한 번을 넘기지 못합니다.
    • recipe의 주석과 공백, 설정 순서는 키에 들어가지 않습니다. 파싱된 문서를 정규화해서
      해시하므로, 제외 목록에 이유를 적어 넣는 것이 변환 한 번의 값이 되지 않습니다.
    • 디렉터리 목록도 기록합니다. 워크북이 하나 추가된 것은 기존 어느 파일도 바꾸지
      않으므로, 파일 목록만으로는 보이지 않습니다.
    • 강제가 둘입니다. --full은 캐시를 의심할 때(입력부터 전부 다시), --force-output
      캐시를 믿으면서 산출물만 다시 원할 때입니다. --validate-only와 함께 쓰는 것은 거절합니다.
    • 건너뛴 항목의 파일은 「이 실행이 쓴 것」에 포함됩니다. 스윕은 이 실행이 쓰지 않은 생성
      파일을 지우므로, 이것이 없으면 캐시가 맞다고 판정한 파일을 그 실행이 전부 지웁니다.
    • 건너뛰지 않는 것. 파일을 하나도 쓰지 않는 항목입니다 — 데이터베이스 익스포트와
      히스토리 기록이 그렇습니다. 산출물을 해시해서 확인할 수 있는 것만 건너뜁니다. 그리고
      히스토리·요약 타깃이 있는 recipe는 커밋 식별자가 키에 들어가므로, 새 커밋은 전체 실행입니다.
    • 검증은 캐시를 쓰지 않습니다. --validate-only가 「지난번에 통과했다」로 통과하면
      게이트가 아닙니다.
    • 왜 전체를 다시 하는지 항상 말합니다. 어느 워크북이 바뀌었는지, recipe의 어느 쪽이
      바뀌었는지, 다른 빌드가 쓴 캐시인지. 이유를 대지 못하는 캐시는 --full이 습관이 되어
      없는 것과 같아집니다.
    • 구글 시트는 Drive의 version으로 판정합니다. 문서는 크기도 수정 시각도 없으므로
      drive.metadata.readonly 스코프를 더 받아 카운터 하나를 읽습니다. 셀은 읽지 않습니다.
      물어볼 수 없으면 바뀐 것으로 보고 가져오고, 무엇을 허용하면 되는지와 어느 파일을 지우면
      동의를 다시 받는지까지 출력합니다.
    • 건너뛴 실행도 낡은 생성 파일은 지웁니다. 아무것도 하지 않고 끝나면 스윕에 도달하지
      못하므로, 「테이블을 지웠을 때 그 파일이 없어지는가」가 그날 다른 것이 바뀌었는지에
      달리게 됩니다. 이것을 잡은 것은 기존 스윕 게이트입니다 — 캐시가 「건너뛴다」고 보고하며
      파일을 남겼습니다.
    • --detailed-exit-code로 할 일이 없던 실행을 구분합니다. 종료 코드 2입니다. 다음
      단계가 배포인 파이프라인이 쓰는 형태이고, 플래그 뒤에 둔 이유는 호환입니다 — 기본으로
      0이 아닌 값을 내면 뒤에 단계를 이어 붙인 스크립트가 캐시가 처음 동작한 날 멈춥니다.
    • 캐시는 커밋 대상이 아닙니다. 절대 경로와 그 기계의 파일 상태를 담습니다.
  • 실행이 여는 두 줄에 버전을 적습니다. 도구 빌드가 한 줄, .tcb 형식 번호와 .NET
    런타임과 플랫폼이 다음 줄, 그 아래 빈 줄입니다 — 넷을 한 줄에 담으면 아무것도 찾을 수 없는
    줄이 되고, 빈 줄이 없으면 그 뒤 수백 줄의 앞 두 줄이 됩니다. 받은 로그에 대해 묻게 되는
    것이 대개 이 넷 중 하나이고, 그중 형식 번호가 유일하게 읽는 쪽과 어긋날 수 있는
    입니다. 빌드 문자열은 빌드 캐시의 키에 쓰는 것과
    같은 값이라 한 곳에서 나옵니다 — 「다른 빌드가 쓴 캐시입니다」라는 보고와 첫 줄이 어긋나면
    아무도 무엇을 해야 할지 알 수 없습니다. 표준 출력이 남의 입력인 실행
    (--new-encryption-key, --out 없는 조회)에는 적지 않고 파일 로그에만 남깁니다.

  • 실행이 단계별 소요 시간을 스스로 말합니다. 임포트·쿠킹·검증·산출·커밋과, --verbose에서는
    타깃별로. 지금까지는 총 소요만 나왔으므로 어디가 느린지 알려면 종료 지점을 바꿔 세 번 돌리고
    로그의 타임스탬프를 손으로 빼야 했습니다.

  • 명령줄 옵션마다 캐시에 대한 뜻을 선언합니다. --verbose는 산출을 바꾸지 않으므로 캐시를
    버리지 않고, --target-side는 바꾸므로 키에 들어갑니다. 선언이 없는 옵션이 있으면 테스트가
    실패합니다
    — 분류를 잊은 결과가 「조용히 낡은 산출」이 아니라 「빌드가 안 됨」이어야 합니다.

  • 로그 한 줄이 수준과 단계를 먼저 말합니다. [W] [Cooking ] ... 형식이고, 콘솔과 파일이
    같은 구성입니다 — 콘솔은 지켜보는 동안 읽는 것이라 수준을 한 글자로, 파일은 나중에 읽는
    것이라 시각과 함께 세 글자로 적습니다 (보기).

    • 단계는 그 줄을 쓴 클래스가 밝힙니다. 호출 스택에서 미루어 짐작하는 것이 아니라
      클래스마다 한 줄로 적어 둡니다. Loading·Importing·Cooking·Validating·Exporting·
      Committing·Recording 일곱 가지이고, 밝히지 않은 줄은 Tabbit으로 나갑니다.
    • 메시지 쪽은 하나도 손대지 않았습니다. 선언한 이름이 정적 Log를 가리므로 기존 호출
      132곳이 그대로입니다.
  • 시트의 날짜를 어느 시간대로 읽을지 recipe에 적습니다. TimeZone이 그것이고, 저장되는 값은
    언제나 UTC입니다 — 이 설정이 정하는 것은 시트에 적힌 시각을 어느 시간대의 것으로 읽을지 하나뿐입니다
    (설정 ·
    설계).

    • 지역 이름과 고정 오프셋을 모두 받습니다. Asia/Seoul·Korea Standard Time 같은 이름은
      서머타임을 포함한 그 지역의 역사를 따르고, +09:00·+0900·+09·Z는 일 년 내내 같습니다.
      후자는 기계에 시간대 데이터가 없어도 되므로, 컨테이너에서 도는 변환이 이름 조회에 매이지
      않습니다.
    • 소스 항목별로 다르게, 명령줄로 강제. 항목의 TimeZone이 recipe 전체 설정보다 우선하고,
      --time-zone그 둘 다를 덮어씁니다 — recipe가 시간대를 잘못 적고 있는 실행을 위한
      것이므로, 덮어쓸 대상이 바로 그 항목별 설정입니다. 적용한 값은 실행 로그에 한 줄로 남습니다.
    • 오프셋을 적은 셀이 실행 기계에 따라 달라지던 것을 고쳤습니다. 2022-01-24T10:30:00Z
      변환을 도는 기계의 시간대로 옮겨져 읽혔습니다 — KST PC에서는 19:30, UTC 러너에서는 10:30이
      되었고, 어느 쪽도 그 사실을 말하지 않았습니다. 이제 셀이 적은 오프셋이 그 값을 정하고,
      recipe 설정보다 우선합니다.
    • 서머타임의 두 자리. 시계가 건너뛴 시각은 그 지역에 존재하지 않았으므로 셀 위치와 함께
      거절합니다. 두 번 지나간 시각은 표준시 쪽으로 읽고 그런 셀이 몇 개였는지 보고합니다.
    • 비워 두면 지금까지와 같습니다. 셀이 이미 UTC로 적힌 것으로 보므로 값이 달라지지 않고,
      골든도 한 바이트도 바뀌지 않았습니다. 저장 타입의 크기(초 단위·32비트)는 별도 검토입니다.
  • HTML 문서를 데이터 확인 도구로. 생성되는 페이지 구성과 항해가 바뀌었습니다. 데이터·형식·
    생성기는 그대로이고, 바뀐 것은 이 타깃의 산출물뿐입니다
    (가이드 · 설계).

    • 테이블당 페이지 하나입니다. 전부 한 페이지였고, 최대 테이블이 10만 행인 워크북에서 그
      페이지가 36.9 MB였습니다 — 그 테이블 하나 때문에 나머지 66개를 볼 수 없었습니다.
      MaxRowsPerTable(기본 1,000, 0은 무제한)이 페이지가 보여주는 행 수를 정하고, 적용되면
      몇 행 중 몇 행인지 페이지에 적힙니다.
    • 모든 페이지에 상단 바·브레드크럼·형제 목록이 있습니다. 예전에는 상세 페이지에서 나가는
      링크가 하나도 없어 브라우저 뒤로가기가 유일한 수단이었습니다. 목록이 페이지 안에 있으므로
      파일 하나만 전달받아도 이동이 됩니다.
    • 전체 윤곽을 개요가 제시합니다. 테이블·행·컬럼·enum·레이블·상수·셀 개수와, 컬럼의
      타입별·역할별·측별 분포, 최대 테이블 목록입니다. 여기에 전체 컬럼 색인(fields.html)이
      더해집니다 — 같은 컬럼 이름의 타입이 어긋난 곳을 이름 정렬로 찾을 수 있습니다.
    • enum 값은 커서를 올리면 enum 전체가 카드로 나타납니다. 이동하지 않고 확인됩니다.
      정의는 페이지마다 한 번만 싣습니다 — 칸마다 title에 담으면 10만 행 테이블에서 enum 전체가
      10만 번 반복됩니다.
    • 참조와 사용처가 양방향입니다. 테이블 페이지가 자기를 가리키는 컬럼을, enum 페이지가
      자기를 쓰는 컬럼을 함께 냅니다.
    • 표에 필터와 정렬(5,000행까지)이 붙고, 빈 문자열·값 없음·빈 배열이 구분되어 표시되고,
      키 컬럼이 가로 스크롤에서 고정됩니다. 폐기된 font·align 마크업이 CSS로 바뀌면서
      어두운 테마도 따릅니다. 외부 요청은 계속 0개입니다.
    • 테마를 고를 수 있습니다. 상단 바의 버튼이 시스템 → 밝게 → 어둡게를 순환하고 선택은
      localStorage에 남습니다. 페이지가 그려지기 전에 적용되므로 이동할 때 다른 테마가
      번쩍이지 않습니다.
    • 종류마다 목록 페이지. tables.html · fields.html · enums.html ·
      constantsets.html이 각각 정렬·필터되는 목록이고, 개요는 카운터와 분포만 남습니다 —
      목록을 개요에 두면 프로젝트가 커질수록 카운터 아래가 이름의 벽이 됩니다.
    • 아이콘과 브랜드 마크. 테이블·컬럼·enum·상수 세트·워크북·시트에 인라인 SVG 심볼을
      붙였습니다(이모지가 아닌 것은 플랫폼마다 다른 그림이 되기 때문입니다). 상단 바에 브랜드
      마크가 들어가고, 스크롤바도 얇은 것으로 지정합니다.
    • 중첩·옵셔널 시트를 문서화합니다. 이 타깃은 네 가지 형태(레코드 그룹 · 다중 중첩 ·
      옵셔널 필드 · 옵셔널 원소) 중 어느 것도 선언하지 않고 있었습니다 — 그래서 그런
      워크북은 문서가 한 장도 생성되지 않았고, 그것이 데이터를 들여다볼 필요가 가장 큰 시트
      입니다. 컬럼을 그리는 타깃이라 구현이 필요한 것은 없었고, 선언과 그룹 이름을 읽는 자리
      하나였습니다.
    • 헤더와 키 컬럼이 고정됩니다. 표가 페이지가 아니라 자기 컨테이너 안에서 스크롤되므로
      값을 보면서 컬럼 이름을 잃지 않습니다. 헤더 행 높이를 고정해 스택이 첫 값을 덮던 것도
      함께 고쳤고, 설명이 하나도 없는 시트에서는 설명 행과 Description 열을 그리지 않습니다.
    • 참조 값이 그 행으로 갑니다. 키가 적혀 있는데 그 행을 찾는 방법이 없었습니다. 대상이
      여럿인 컬럼은 후보 테이블을 함께 내고 읽는 사람이 고릅니다. 행 상한 때문에 그 페이지에
      없는 행이면 페이지까지만 가리킵니다 — 없는 앵커를 만들지 않습니다.
    • 참조 그래프 페이지(references.html)를 추가했습니다. 테이블에 커서를 올리면 그
      테이블의 간선만 남습니다 — 실측 노드 323개·간선 637개에서 47개만 남으므로, 수백 개가
      겹치는 가운데에서도 읽힙니다. 층으로 배치해 모든 화살표가 앞을
      향하고, 자기 참조는 고리입니다. 좌표는 생성기가 계산하고 페이지는 인라인 SVG를 담습니다 —
      레이아웃 라이브러리를 싣지 않으므로 매번 같은 그림이고 커밋해서 diff할 수 있습니다.
    • 페이지의 문구가 한국어입니다. 시트에서 온 이름과 설명은 그대로이고, 타입 이름과 측
      표기(c·s·cs)는 recipe의 어휘라 번역하지 않습니다.
    • 목록의 기본 정렬은 이름 오름차순입니다 — 테이블·컬럼·enum·상수 세트 모두, 대소문자를
      가리지 않고. 서수 정렬은 대문자를 모두 소문자 앞에 두므로 찾는 이름이 예상한 자리에 없습니다.
    • 참조 그래프를 탐색기로. 끌어서 이동하고 휠로 확대하며, 테이블을 누르면 그 테이블의
      이웃만 그려집니다 — 왼쪽이 가리키는 테이블, 오른쪽이 가리켜지는 테이블이고, 이웃을 다시
      눌러 걸어갈 수 있습니다. 그림은 변환된 그룹 안에 있으므로 이동이 요소 하나의 속성 하나이고,
      호버 강조는 간선 전체를 담은 그룹의 불투명도 하나를 바꿉니다. 아래에 연결 정도 표를
      두어 어디서부터 볼지 정하게 했습니다.
    • 스크롤 영역은 페이지당 하나입니다. 표와 페이지가 함께 스크롤되던 동안에는 페이지가
      내려간 만큼 고정 헤더가 상단 바 뒤로 들어갔고, 표 아래의 가로 스크롤바가 화면 밖에 있었
      습니다. 표가 하나뿐인 페이지는 창이 틀이 됩니다.
    • 레코드는 객체로 나옵니다. 시트가 원소마다 멤버마다 한 컬럼으로 적는 것(statBonus[0]["Id"])을
      그대로 그리면 컬럼 33개가 늘어서고 어느 숫자가 어느 멤버인지는 헤더를 세어야 알 수 있습니다.
      페이지의 컬럼은 이제 테이블의 엔트리이고, 셀은 [(0, 1077, 421), (0, 1122, 196)] ×2처럼
      튜플입니다. 멤버 이름은 마크업에 남아 툴팁이 들여쓴 형태로 답하고(값을 두 번 담지 않습니다),
      누르면 셀 안에서 원소 하나가 한 줄씩 펼쳐집니다.
    • 필수 여부가 타입의 ?가 되었습니다. 별도 행은 레코드 하나에 멤버가 여럿이면 답할 수 없어
      「전부 optional」로 뭉치고 있었습니다. 기본 인덱스는 타입 뒤에 🔑입니다.
    • 값 없음이 0으로 보이던 것. 부재는 null이 아니라 「타입의 빈 값 + 없음 표시」로
      저장되는데 값만 읽고 있었습니다 — 아무도 입력하지 않은 0이 입력한 0과 같아 보이던 것으로,
      이 페이지가 막으려고 존재하는 혼동입니다. 한 페이지에서 18,040개가 그렇게 나오고 있었습니다.
      표기는 <none>이고 배열 원소마다도 같습니다.
    • 참조 값이 그 키를 가진 테이블의 행으로 갑니다. 대상이 여럿이면 어느 쪽인지 옆에 적고,
      툴팁은 선언된 대상 전부를 답합니다 — 가진 쪽은 그 행의 앞 컬럼들을, 없는 쪽은 없다는 사실을.
      시트가 선언만 하고 코어가 승격을 보류한 컬럼(레코드 멤버가 여러 대상을 지목하는 경우)도
      같습니다.
    • 시트가 적은 제약을 헤더 툴팁에. 최소·최대·허용값 개수·레코드 안 필수, 그리고 시트의 원래
      컬럼 표기(statBonus[0]["Id"])와 생성 이름. 모델에 있는데 페이지에 하나도 없었습니다.
    • 긴 값은 로 자릅니다. 대사 한 줄이 화면보다 넓어서 그 컬럼 하나가 나머지를 옆으로
      밀어내고 있었습니다. 자르는 것은 표시뿐이고(값은 페이지에 그대로 있어 필터·복사가 전체를
      봅니다), 커서를 올리면 전체가 패널로 나오고 누르면 그 자리에서 펼쳐집니다.
    • 참조 셀에 커서를 올리면 그 행이 미리 보입니다. 대상 테이블의 앞 컬럼 다섯 개까지이고,
      그 페이지가 이름을 부르는 키만 담습니다(테이블 1개 페이지 2,594 KB에 187 KB). 담는 자리는
      숨긴 요소의 JSON입니다 — 시트 값에 URL처럼 읽히는 것이 있을 수 있어 script 태그를 쓰지
      않습니다.
    • 논리값의 거짓에 표시가 생겼습니다. 거짓이 빈 칸이던 동안에는 거짓·값 없음·빈 문자열이
      같은 그림이었습니다. 참은 , 거짓은 입니다.
    • 깨져 있던 것 2건. 푸터 템플릿의 주석 안에 Scriban 구분자가 있어 주석이 조기 종료되었고,
      그 뒤 6줄이 모든 페이지의 표 아래에 출력되고 있었습니다 — 타임스탬프 바로 앞자리입니다.
      그리고 enum 페이지는 공통 푸터를 include하지 않아 닫는 태그가 없었습니다. 골든 6트리가 둘 다
      정답으로 기록하고 있었고(처음부터 그 상태였으므로 「변했는가」에 걸리지 않습니다), 게이트
      5개를 함께 넣었습니다.
  • AllowArrayGaps가 이름 기반 레이아웃에서 동작하지 않던 것. 배열 가운데 빈칸을 허용하는
    소스 항목 설정이 그 레이아웃으로 읽은 테이블에는 전달되지 않아, 켜도 아무 일이 일어나지
    않았습니다(다른 두 레이아웃은 전달하고 있었습니다). 실측으로는 전량 변환에서 그 종류의 오류가
    2,574건 보고되고 있었고, 설정을 켜도 숫자가 한 건도 줄지 않는 것이 단서였습니다.

  • Lua 지원. 코드 생성 타깃이 하나 늘었습니다("Type": "lua"). 형식은 그대로이고 리더가
    하나 늘었습니다 — LuaJIT 2.1과 Lua 5.3+ 두 모드이고, 64비트는 각각 FFI cdata와 네이티브
    정수로 무손실입니다(순수 5.1·5.2는 숫자가 double이라 지원하지 않습니다). 인코딩 14종·원소
    타입 8종·presence를 적합성 코퍼스로 두 모드에서 확인했습니다.

    • 없는 필드는 오류입니다. 로우·enum·상수의 오타 접근이 조용한 nil 대신 타입 이름과
      함께 그 자리에서 멈춥니다 — 올바른 접근에는 비용이 없는 엄격 메타테이블이고, 생성 파일의
      lua-language-server 주석이 편집 시점을 맡습니다.
    • 암호화·MAC·매니페스트 해시는 동봉된 C 소스 하나로. 순수 Lua가 감당할 수 없는 바이트
      루프라서이고, 평문만 읽는 프로젝트는 컴파일할 것이 없습니다 — 리더가 암호 경로에서만
      모듈을 찾고, 없으면 무엇을 빌드해야 하는지 말합니다. 업데이터의 HTTP는 소비자의 것을
      주입받습니다(게임 클라이언트에 두 번째 HTTP 스택을 들이지 않습니다).
    • 통합 절차는 Lua 가이드에, 설계는
      spec/targets/lua-language-support.md에 있습니다.
  • Swift 지원. 코드 생성 타깃이 하나 늘었습니다("Type": "swift"). 형식은 그대로이고 리더가
    하나 늘었습니다 — 인코딩 14종·원소 타입 8종을 적합성 코퍼스로 확인하였고, 생성물은 Swift 5와 6 두
    언어 모드에서 경고 없이
    컴파일됩니다. 로우는 class, 레코드 원소는 struct이고, 접근자는
    인스턴스입니다(Swift 6는 가변 전역을 오류로 봅니다).

    • MAC 검증만 조건부로 패키지를 씁니다. 애플 플랫폼은 OS의 CryptoKit, 그 밖에서는
      swift-crypto이고, 둘 다 없어도 리더는 컴파일되고 파일도 읽힙니다 — 안 되는 것은 검증
      하나이고 그때 무엇을 넣어야 하는지 말합니다. 직접 구현은 CPU의 SHA 확장에 닿지 못해 6배
      느립니다. 데이터 갱신기는 의존성이 없습니다(MD5는 그 파일 안에 있습니다).
    • 설치·통합 절차는 Swift 가이드에, 설계는
      spec/targets/swift-language-support.md에 있습니다.
  • 한국어 낱말을 정리했습니다. 도구가 내는 메시지, HTML 문서의 제목 한 줄, recipe 템플릿의
    주석, 그리고 문서 전체입니다. 「모양」은 자리마다 다른 낱말이 맞으므로 일괄로 바꾸지 않았습니다 —
    파일과 와이어는 형식, 검증이 보는 것은 구조, 하는 방법은 방식이고, 눈에 보이는
    생김새는 모양 그대로입니다. 뜻이 달라진 메시지는 없습니다.

    • 문서가 실제 산출과 어긋나 있던 것도 함께 고쳤습니다text·swift·lua 타깃이 목록에
      빠져 있었고, 스위트 시간이 문서마다 달랐으며, 빌드 리포트·빌드 캐시를 비롯한 최근 기능이
      기능에 없었습니다. 문서 사이트는 지워진 스크립트를 가리키는 링크 하나와
      앵커 5개 때문에 빌드가 서지 않고 있었습니다.

담긴 것

Tabbit — Game Data Authoring & Build Tool. 게임의 정적 데이터를 작성하고, 검증하고, 런타임
데이터로 빌드합니다.

  • Author. 엑셀과 구글 스프레드시트에 테이블·enum·상수셋을 적습니다. 마커 방식이 기본이고,
    다른 규칙으로 쓰인 시트는 레이아웃을 붙여 그대로 읽습니다.
  • Schema. 타입·인덱스·참조·제약·중첩 필드·옵셔널을 시트에서 읽어 스키마로 세웁니다.
  • Validate. 타입으로 나타낼 수 없는 규칙을 C# 규칙 파일로 검사하고, 문제를 어느 셀인지
    함께 한 번에 모아 보고합니다.
  • Analyze. 로드된 데이터를 HTML로 펼쳐 보고, 커밋마다 무엇이 바뀌었는지 셀 단위로 남기며,
    데이터 패치와 코드 배포 중 무엇이 나가야 하는지 판정합니다.
  • Optimize. 컬럼마다 적용 가능한 인코딩을 전부 시도해 가장 작은 것을 고릅니다 — 사전, RLE,
    델타, 비트폭 패킹, 그리고 그 조합.
  • Build. 런타임 바이너리 TCB(.tcb)와, 그것을 읽는 코드를 여러 언어로 냅니다. C#(.NET과
    유니티), TypeScript, C++, C, 언리얼, Go, Rust, Python, Java, Kotlin, Ruby, PHP, Dart.
  • 파일 대신 MySQL · PostgreSQL · MongoDB · Redis에 바로 적재할 수도 있습니다.
  • 변환이 중간에 실패해도 이전 결과가 그대로 남습니다. 파일은 스테이징을 거쳐 한꺼번에 옮기고,
    데이터베이스는 섀도 테이블을 채운 뒤 통째로 바꿉니다.
  • 데이터만 따로 패치할 수 있습니다. 생성된 업데이터가 바뀐 파일만 받아 해시로 검증합니다.