본문으로 건너뛰기

Sources — 무엇을 읽을지

「Recipe 파일」로 돌아가기


읽을 곳은 두 가지이고, 여러 개를 함께 둘 수 있습니다. 전부 합쳐서 하나의 모델이 됩니다.

"Sources": {
"Xlsx": [
{ "Path": "./sheets", "FileExtensionPatterns": ".xls;.xlsx" }
],
"GoogleSheets": [
{ "ClientSecretFilename": "./client-secret.json", "SheetsId": "10NXZ..." }
]
}
어디에기본값설명
PathXlsx워크북을 찾을 폴더. 하위 폴더까지 봅니다. 이름이 #으로 시작하는 파일·폴더는 건너뜁니다.
FileExtensionPatternsXlsx.xls;.xlsx주워올 확장자. ;로 구분합니다.
ClientSecretFilenameGoogleSheetsOAuth 클라이언트 비밀 파일 경로. 커밋하지 마세요. 아래 참고.
ServiceAccountKeyFileGoogleSheets서비스 계정 키 파일 경로. 커밋하지 마세요.
ServiceAccountKeyVariableGoogleSheets서비스 계정 키가 든 환경 변수의 이름. 키가 아니라 이름입니다.
SheetsIdGoogleSheets워크북(스프레드시트 문서) URL에 들어 있는 긴 식별자.

구글 시트에 무엇으로 접속하는가

두 가지이고, 무엇을 고르는지는 누가 변환을 돌리는지에서 결정됩니다.

설정누구로 접속하나어디에 맞나
ClientSecretFilename변환을 돌리는 사람개발자의 기계. 첫 실행이 브라우저로 동의를 받고 그 계정의 프로필 아래에 토큰을 캐시하므로, 두 번째 실행부터는 대화형이 아닙니다
ServiceAccountKeyFile · ServiceAccountKeyVariable그 잡 자신빌드 서버. 대화형 단계가 없고, 문서를 사람에게 공유하듯 서비스 계정의 주소에 공유합니다

CI에 클라이언트 비밀을 쓰면 파이프라인의 문서 접근 권한이 한 사람의 계정에 종속됩니다. 그 사람이 조직을 떠나거나 권한이 회수되면 빌드가 중단되고, 그 잡이 읽는 모든 것은 그 사람으로 읽힙니다. 서비스 계정은 그것을 잡 자신의 계정으로 바꿉니다.

// 개발자의 기계
{ "ClientSecretFilename": "./secrets/googlesheets-client-secret.json", "SheetsId": "10NXZ..." }

// CI — 키는 시크릿 저장소에 두고 이름만 적습니다
{ "ServiceAccountKeyVariable": "TABBIT_SHEETS_KEY", "SheetsId": "10NXZ..." }
  • 둘을 함께 적으면 거부합니다. 서로 다른 계정이므로 하나를 말없이 고르면 그 잡이 자기가 아닌 사람으로 문서를 읽게 되고, 산출물의 어디에도 그 사실이 남지 않습니다. ServiceAccountKeyFileServiceAccountKeyVariable을 함께 적는 것도 같습니다.
  • 키 파일 자리에 클라이언트 비밀을 적으면 그 자리에서 거부합니다. 구글이 내려주는 두 JSON은 서로 바꿔 넣기 쉬운데, 그대로 API에 보내면 권한 오류로 돌아와 문서 공유 문제처럼 읽힙니다.
  • 서비스 계정에는 그 문서의 뷰어 권한만 있으면 됩니다. 키에 적힌 client_email이 공유할 주소입니다.
  • 셋 중 아무것도 적지 않은 항목은 SheetsId가 빈 것과 같게 꺼진 것으로 취급됩니다.

아래는 두 소스 모두 같습니다.

기본값설명
Layout"tabbit"시트를 읽는 방식. 아래 참고.
IncludeWorkbooks[](전부)읽을 워크북. 배열 또는 ;로 이은 문자열. * ? 와일드카드.
ExcludeWorkbooks[]제외할 워크북. IncludeWorkbooks 다음에 적용됩니다.
IncludeSheets[](전부)읽을 시트 이름. [워크북]시트로 워크북을 지정할 수 있습니다.
ExcludeSheets[]제외할 시트. IncludeSheets 다음에 적용됩니다.
DefaultDelimiter(전체 설정)이 항목의 셀 안 값 구분자. 적으면 recipe 전체 설정보다 우선합니다.
TimeZone(전체 설정)이 항목 시트의 시간대. 적으면 recipe 전체 설정보다 우선합니다. 참고.
OnDuplicateIndex"error"인덱스 값이 겹칠 때. 겹치는 것을 허용하는 레이아웃에서만 동작합니다.
OnFormulaError"error"#REF! 같은 수식 오류 셀을 만났을 때. 아래 참고.
OnBlankCell"error"숫자·날짜처럼 빈 칸을 읽을 방법이 없는 컬럼의 빈 칸. 아래 참고.
TrimTrailingArrayElementsfalse배열에서 값이 없는 뒤쪽 원소를 버립니다. 아래 참고.
TableRowSets""한 테이블이 행을 여러 벌 가질 때, 그것을 이름으로 어떻게 구분하는지. 아래 참고.
LayoutOptions{}그 레이아웃만 아는 설정. 아래 참고.

Layout — 시트를 읽는 방식

셀 격자를 어떻게 해석할지를 고르는 설정입니다. 어디서 읽어오는지(엑셀이냐 구글시트냐)와는 무관하므로, 두 소스 모두에서 쓸 수 있습니다.

무엇인가
tabbit기본값. :table Item 같은 선언 셀로 엔티티를 선언하고, 그 아래 행 키가 헤더를 정합니다. 한 시트에 여러 개를 아무 데나 놓을 수 있습니다.
sheet-per-table선언 셀 없이 시트 탭 하나가 곧 테이블 하나이고 머리 3줄이 헤더인 형태. 다른 규칙으로 작성된 시트를 그대로 읽기 위한 것입니다.

소스 항목마다 따로 지정하므로 한 번에 섞어 읽을 수 있습니다. 한쪽 워크북의 테이블이 다른 쪽에서 선언한 enum을 타입으로 써도 됩니다.

"Xlsx": [
{ "Path": "./sheets", "Layout": "tabbit" },
{ "Path": "./other-sheets", "Layout": "sheet-per-table" }
]

sheet-per-table이 시트를 어떻게 읽는지와 실제로 적용한 기록은 sprout 샘플에 있습니다.

읽을 워크북과 시트 골라내기

아무것도 적지 않으면 Path 아래의 워크북 전부, 그 안의 시트 전부입니다. 골라내는 것은 두 단계이고, 워크북이 먼저입니다 — 제외된 워크북은 열지도 않습니다.

무엇을 고르나
IncludeWorkbooks / ExcludeWorkbooks워크북 자체
IncludeSheets / ExcludeSheets시트. [워크북]시트로 특정 워크북에만 적용할 수 있습니다
{
"Path": "./xls",

// 폴더에 입력이 아닌 파일이 섞여 있을 때. 보통 쓰게 되는 것은 제외 쪽입니다 —
// 입력인 워크북을 전부 적는 것보다 짧고, 하나 늘어날 때마다 손볼 필요가 없습니다.
"ExcludeWorkbooks": ["백업/*", "*_참고용*"],

// 모든 워크북에 적용
"ExcludeSheets": [
"*_메모",

// 그 워크북에만 적용. 양쪽 다 글롭이므로 한 줄로 여러 워크북을 덮을 수 있습니다.
"[테이블.xlsb]Define",
"[테이블.xlsb]List*Shape",
"[*.xlsb]RewardPath"
]
}

목록이 길면 배열이, 하나면 문자열이 읽기 좋습니다. * ?는 파일 글롭과 같고 대소문자는 구분하지 않습니다.

워크북 이름은 세 가지로 적을 수 있습니다Path 기준 상대 경로(shared/Items.xlsx), 파일명(Items.xlsx), 확장자를 뗀 이름(Items). 3개 다 같은 워크북을 가리키므로 백업/*은 폴더 하나를, *.xlsb는 형식 하나를 지정합니다. 경로로 적으면 그 경로만이고, 파일명으로 적으면 하위 폴더에 있는 같은 이름도 걸립니다.

시트 이름에 워크북을 붙일 수 있는 이유는 시트 이름이 워크북마다 겹치기 때문입니다. 한쪽의 Define은 테이블이고 다른 쪽의 Define은 작업용 탭인 상황을 시트 이름만으로는 구분할 수 없고, 구분하지 못하면 둘 다 빠집니다. 대괄호를 쓰는 것은 시트 이름에 !이나 .은 들어갈 수 있지만 엑셀이 [ ]는 금지하기 때문입니다 — 이름의 일부로 오해될 여지가 없습니다.

IncludeWorkbooks·IncludeSheets에 적었는데 없는 것은 오류입니다 — 적어놓고 표시 없이 빠지면 산출물에서 테이블 하나가 사라진 걸 아무도 모르기 때문입니다. 오류 메시지는 실제로 있는 목록을 같이 보여주고, 워크북을 지정한 패턴이 아무것도 못 맞혔을 때는 각 시트가 어느 워크북에 있었는지와 이 항목이 건너뛴 워크북까지 적습니다 — 시트를 못 찾은 이유가 그 워크북을 제외해 둔 것일 때가 많기 때문입니다.

GoogleSheets 소스는 문서 하나를 가리키므로 워크북 목록은 문서 제목과 맞춰봅니다. [제목]시트도 같게 동작합니다.

OnDuplicateIndex — 인덱스가 겹칠 때

무엇을 하나
error기본값. 겹친 값을 전부 모아 보고하고 멈춥니다. 인덱스가 존재하는 이유 자체입니다.
keep-first먼저 나온 행을 남기고 뒤를 버립니다.
keep-last나중 행이 앞을 덮어씁니다.

뒤의 둘은 원본을 고칠 수 없는 동안 나머지를 변환하기 위한 것이고, 인덱스가 겹치는 것을 허용하는 레이아웃에서만 동작합니다. 버린 행은 전부 로그에 남습니다.

TableRowSets — 한 테이블에 데이터 여러 벌

어떤 시트들은 한 테이블의 컬럼을 두 번 이상 채웁니다. 스키마는 같고 행만 다른 것이고, 빌드마다 그중 한 벌을 싣습니다. 그것을 테이블 이름 뒤에 꼬리표를 붙여 구분합니다.

Item ← 테이블
Item_alt ← 같은 테이블의 다른 한 벌

꼬리표가 어떻게 생겼는지는 그 시트들의 관례이므로 여기 정규식으로 적습니다.

"TableRowSets": "^(?<table>.+?)(?<set>_alt)$"
그룹무엇
table어느 테이블의 벌인가
set파일 이름에 붙을 꼬리표. 구분자까지 잡힌 그대로 붙습니다

적지 않으면(기본) 아무 이름도 벌로 읽히지 않습니다.

두 테이블이 되지 않습니다. 벌은 그 테이블에 행을 보태는 것이라 생성되는 타입이 하나이고, 데이터 파일만 Item.tcbItem_alt.tcb 둘로 나갑니다. 참조는 같은 꼬리표의 벌을 먼저 보고 없으면 원본을 봅니다.

규칙어긋나면
테이블이 벌의 컬럼을 전부 선언해야 합니다오류. 반대(테이블이 더 넓은 것)는 괜찮고, 벌에 없는 칸은 「값 없음」이 됩니다
가리키는 테이블이 있어야 합니다오류. 벌은 테이블을 선언하지 않습니다
소스마다 다른 표기를 적으면오류. 테이블 이름은 실행 전체가 공유합니다

자세한 것은 테이블의 행 벌에 있습니다.

OnFormulaError — 수식이 오류일 때

무엇을 하나
error기본값. 그 셀들을 검증 보고로 냅니다 — 첫 하나가 아니라 전부입니다.
empty빈 값으로 읽고, 컬럼당 한 번 경고합니다 (건수와 첫 셀).

어느 쪽이든 보고되는 것은 모델이 값으로 든 셀뿐입니다. 이름 붙은 사각형 안이어도 컬럼 이름이 없어 필드가 되지 않은 셀 — 조회용 수식, 작업용 컬럼 — 은 보고되지 않습니다. 한 프로젝트의 전량 변환에서 그것이 10,263건이었고, 그 규모가 기본값을 쓸 수 없게 만들고 있었습니다 (설계).

empty우리가 관리하지 않는 워크북을 위한 것입니다. 읽는 컬럼의 깨진 수식 하나 때문에 그 파일의 테이블 전부를 거부하지 않기 위한 것이고, 우리 워크북이라면 기본값이 맞습니다 — 수식 오류는 고칠 수 있는 문제입니다.

OnBlankCell — 빈 칸을 읽을 수 없는 컬럼에서

무엇을 하나
error기본값. 셀 위치와 함께 멈춥니다.
empty타입의 빈 값으로 읽고, 컬럼당 한 번 경고합니다.

string·bool·배열 컬럼에는 해당하지 않습니다. 그 셋의 빈 칸은 예전부터 ""·false·빈 배열이라는 이고, 이 설정이 정하는 것은 빈 칸을 읽을 방법이 없는 타입 — 숫자·날짜·기간· uuid·enum·참조 — 의 빈 칸입니다.

기본이 엄격한 이유는 그 빈 칸이 대개 적다 만 셀이기 때문입니다. 0으로 읽으면 작성자가 적은 0과 구별되지 않는 값이 데이터에 들어갑니다. emptyOnFormulaError와 같은 자리의 완화입니다 — 우리가 관리하지 않는 워크북에서 미완성 셀 하나 때문에 워크북 전체를 거부하지 않기 위한 것이고, 몇 개를 그렇게 읽었는지가 실행 기록에 남습니다.

없음과는 다른 질문입니다. 값이 없는 행은 -라고 적고, 그것은 컬럼 타입이 ?로 끝날 때만 허용됩니다. 이 설정이 무엇이든 필수 컬럼의 -는 오류이고, empty로 읽힌 빈 칸은 값이 있는 셀입니다. 규칙 전체는 시트 작성빈 칸과 없음에 있습니다.

TrimTrailingArrayElements — 배열의 빈 꼬리 자르기

Slot1.*·Slot2.*·Slot3.*에서 셋째를 비운 로우가 길이 2를 냅니다. 기본은 입니다.

레코드 배열과 스칼라 배열 둘 다입니다. Tag1·Tag2·Tag3도 같은 규칙으로 잘립니다 — 자르기는 「원소가 어디서 끝나는가」에 대한 답이고, 그 질문에 원소의 생김새는 상관이 없습니다.

이 키는 TrimTrailingRecordElements였습니다. 레코드 배열에만 걸리던 때의 이름이고, 릴리즈된 적이 없어 이름을 맞췄습니다.

고정 길이는 채우지 않은 로우를 빈 값으로 메우고, 그 메움은 값과 구별되지 않습니다 — {Id:0, Count:0}이 「0개를 주는 슬롯」인지 「슬롯이 없음」인지 알 수 없습니다. 자르면 데이터로 구별됩니다.

가운데는 지우지 않습니다. 뒤에서만 잘라야 인덱스 k가 언제나 Slot{k+1}입니다. 「값이 없다」는 타입에 ?가 붙은 컬럼에 -를 적은 셀이고, 0을 적은 셀도 빈 칸도 값입니다. 규칙 전체는 시트 작성에 있습니다.

배열의 컬럼들이 필수 여부를 다르게 적어도 됩니다. 첫 원소의 표시가 배열 전체의 표시이고, 뒤 컬럼의 표시는 보지 않습니다 — 타입을 첫 원소에서 가져오는 것과 같습니다. 설계

LayoutOptions — 레이아웃 전용 설정

어떤 레이아웃만 아는 설정을 자유 키/값으로 넘깁니다. 코어는 키를 모르고 검증하지 않습니다 — 레이아웃이 읽고, 인식하지 못하는 키는 레이아웃이 그 이름과 함께 보고합니다.

{
"Path": "./other-sheets",
"Layout": "some-layout",
"LayoutOptions": { "NumberType": "narrow" }
}

모든 레이아웃에 해당하는 설정은 위의 정식 키로 들어갑니다. 특정 프로젝트의 설정이 이름째로 recipe 스키마에 들어가면 그 레이아웃을 지울 때 스키마와 문서에 흔적이 남기 때문입니다 — 설계 원칙.