임베디드 문서
임베디드 문서 (Embedded Documentation)
D 언어로 코드를 작성하다 보면 계약(contract)이나 테스트 코드는 소스와 나란히 아주 자연스럽게 붙여 놓을 수 있는데, 정작 사용자용 문서는 얘기가 달라요. 별도 파일로 관리하다 보면 코드와 문서가 어긋나기 쉽고, 매번 두 번씩 써야 하니까요. D는 바로 이 문제를 풀기 위해 소스 코드 안에 문서를 박아 두는 '임베디드 문서(Embedded Documentation)'라는 방법을 제공해요. 이번 글에서는 문서 주석이 어떤 형태를 가지는지, Ddoc이 그 내용을 어떻게 섹션과 매크로로 가공해 최종 문서를 만들어 내는지 하나씩 살펴볼게요.
본문
D 프로그래밍 언어는 계약과 테스트 코드를 실제 코드 옆에 함께 박아 넣을 수 있게 해줘요. 그래서 이들이 서로 어긋나지 않게 유지하기가 좋죠. 그런데 문서만은 빠져 있었어요. 일반 주석은 자동으로 추출해서 매뉴얼 페이지로 가공하기에는 적합하지 않거든요. 사용자 문서를 소스 코드 안에 넣으면 문서를 두 번 작성할 필요가 없다는 점, 그리고 코드와 문서가 일관성을 유지할 가능성이 커진다는 점 같은 중요한 이점이 있어요.
이미 나와 있는 접근 방식도 몇 가지 있어요:
D가 임베디드 문서에 두는 목표는 이래요:
-
추출해서 가공한 뒤에만 보기 좋은 게 아니라, 임베디드 상태 그대로도 보기 좋아야 해요.
-
<tags>같은 어색한 형태에 거의 의존하지 않으면서, 쓰기 쉽고 자연스러워야 해요. 완성된 문서에서는 절대 볼 수 없는 형태들은 피하고 싶은 거죠. -
컴파일러가 코드를 파싱하면서 이미 아는 정보를 반복하지 않아야 해요.
-
임베디드 HTML에 의존하지 않아야 해요. 그런 건 다른 목적의 추출과 포맷을 막아 버리거든요.
-
기존 D 주석 형태에 기반하므로, D 코드에만 관심 있는 파서와 완전히 독립적이어야 해요.
-
코드와 시각적으로 다르게 보여야 해요. 코드와 혼동되면 안 되니까요.
-
원한다면 사용자가 Doxygen이나 다른 문서 추출기를 사용할 수 있어야 해요.
사양 (Specification)
임베디드 문서 주석의 형식에 대한 사양은 오직 '정보를 컴파일러에게 어떻게 제시할지'만 규정해요. 그 정보를 어떻게 활용할지, 최종 표현이 어떤 형태일지는 구현(implementation)에 맡겨져 있어요. 최종 형태가 HTML 웹 페이지일지, 매뉴얼 페이지(man page)일지, PDF 파일일지 같은 건 D 프로그래밍 언어의 사양으로 정해져 있지 않아요.
처리 단계 (Phases of Processing)
임베디드 문서 주석은 일련의 단계를 거쳐 처리돼요:
-
어휘(Lexical) — 문서 주석을 식별해서 토큰에 붙여요.
-
파싱(Parsing) — 문서 주석을 특정 선언과 연결하고 합쳐요.
-
섹션(Sections) — 각 문서 주석을 섹션들의 연속으로 나눠요.
-
특수 섹션을 처리해요.
-
특수하지 않은 섹션에 하이라이팅을 적용해요.
-
모듈의 모든 섹션을 합쳐요.
-
최종 결과를 만들기 위해 매크로 및 이스케이프 텍스트 치환을 수행해요.
어휘 분석 (Lexical)
임베디드 문서 주석은 다음 형태 중 하나예요:
-
/** ... */— 여는/다음에 별표(*) 두 개가 오는 형태 -
/++ ... +/— 여는/다음에 더하기(+) 두 개가 오는 형태 -
///— 슬래시 세 개
다음은 모두 임베디드 문서 주석이에요:
/// This is a one line documentation comment.
/** So is this. */
/++ And this. +/
/**
This is a brief documentation comment.
*/
/**
* The leading * on this line is not part of the documentation comment.
*/
/*********************************
The extra *'s immediately following the /** are not
part of the documentation comment.
*/
/++
This is a brief documentation comment.
+/
/++
+ The leading + on this line is not part of the documentation comment.
+/
/+++++++++++++++++++++++++++++++++
The extra +'s immediately following the / ++ are not
part of the documentation comment.
+/
/**************** Closing *'s are not part *****************/
주석의 여는 부분, 닫는 부분, 왼쪽 여백에 있는 추가 별표(*)와 더하기(+)는 무시되며 임베디드 문서에 포함되지 않아요. 위 형태 중 하나를 따르지 않는 주석은 문서 주석이 아니에요.
파싱 (Parsing)
각 문서 주석은 하나의 선언과 연결돼요. 문서 주석이 혼자 한 줄에 있거나 왼쪽에 공백만 있는 경우, 다음에 오는 선언을 가리켜요. 같은 선언에 적용되는 문서 주석이 여러 개면 이어 붙여져요. 선언과 연결되지 않는 문서 주석은 무시돼요. ModuleDeclaration(모듈 선언) 앞에 있는 문서 주석은 모듈 전체에 적용돼요. 문서 주석이 선언과 같은 줄의 오른쪽에 있으면 그 선언에 적용돼요.
어떤 선언의 문서 주석이 ditto라는 식별자만으로 이루어져 있다면, 같은 선언 스코프에서 바로 앞 선언의 문서 주석이 이 선언에도 적용돼요.
문서 주석이 없는 선언은 출력에 나타나지 않을 수 있어요. 출력에 반드시 나타나게 하려면 그 선언에 빈 문서 주석을 넣어 두세요.
int a; /// documentation for a; b has no documentation
int b;
/** documentation for c and d */
/** more documentation for c and d */
int c;
/** ditto */
int d;
/** documentation for e and f */ int e;
int f; /// ditto
/** documentation for g */
int g; /// more documentation for g
/// documentation for C and D
class C
{
int x; /// documentation for C.x
/** documentation for C.y and C.z */
int y;
int z; /// ditto
}
/// ditto
class D { }
섹션 (Sections)
문서 주석은 일련의 *섹션(Section)*으로 이루어져요. 한 줄의 첫 번째 공백 아닌 글자 바로 뒤에 :이 따라오는 그 이름이 바로 섹션이에요. 이 이름이 섹션 이름이 되죠. 섹션 이름은 대소문자를 구분하지 않아요.
http://나 https://로 시작하는 이름은 섹션 이름으로 인식되지 않아요.
요약 (Summary)
첫 번째 섹션은 *요약(Summary)*이며 섹션 이름이 없어요. 빈 줄이나 섹션 이름이 나오기 전까지의 첫 단락이죠. 요약은 길어도 되지만, 되도록 한 줄로 유지하는 게 좋아요. 요약 섹션은 선택 항목이에요.
설명 (Description)
그다음 이름 없는 섹션은 *설명(Description)*이에요. 요약 다음부터 섹션 이름이 나오거나 주석이 끝날 때까지의 모든 단락으로 이루어져 있어요.
설명 섹션은 선택 항목이지만, 요약 섹션 없이 설명만 따로 있을 수는 없어요.
/***********************************
* Brief summary of what
* myfunc does, forming the summary section.
*
* First paragraph of synopsis description.
*
* Second paragraph of
* synopsis description.
*/
void myfunc() { }
이름이 있는 섹션들은 요약과 설명이라는 이름 없는 섹션 다음에 와요.
표준 섹션 (Standard Sections)
일관성과 예측 가능성을 위해 몇 가지 표준 섹션이 정해져 있어요. 이 중 어떤 것도 반드시 있어야 하는 건 아니에요.
- Authors: 작성자(author)를 나열해요.
/**
* Authors: Melvin D. Nerd, [email protected]
*/
- Bugs: 알려진 버그들을 나열해요.
/**
* Bugs: Doesn't work for negative values.
*/
- Date: 현재 개정의 날짜를 명시해요. 날짜는 std.date가 파싱할 수 있는 형태여야 해요.
/**
* Date: March 14, 2003
*/
- Deprecated: 연결된 선언이 deprecated로 표시되었을 때의 이유와 취해야 할 조치를 설명해요.
/**
* Deprecated: superseded by function bar().
*/
deprecated void foo() { ... }
- Examples: 사용 예시를 적어요.
/**
* Examples:
* --------------------
* writeln("3"); // writes '3' to stdout
* --------------------
*/
- History: 개정 이력이에요.
/**
* History:
* V1 is initial version
*
* V2 added feature X
*/
- License: 저작권 있는 코드에 대한 라이선스 정보예요.
/**
* License: use freely for any purpose
*/
void bar() { ... }
- Returns: 함수의 반환값을 설명해요. 함수가 void를 반환한다면 굳이 중복해서 문서화하지 마세요.
/**
* Read the file.
* Returns: The contents of the file.
*/
void[] readFile(const(char)[] filename) { ... }
- See_Also: 관련 항목의 다른 심볼과 URL 목록이에요.
/**
* See_Also:
* foo, bar, http://www.digitalmars.com/d/phobos/index.html
*/
- Standards: 이 선언이 특정 표준을 준수한다면 그 내용을 설명해요.
/**
* Standards: Conforms to DSPEC-1234
*/
- Throws: 던져지는 예외와 어떤 상황에서 던져지는지 나열해요.
/**
* Write the file.
* Throws: WriteException on failure.
*/
void writeFile(string filename) { ... }
- Version: 선언의 현재 버전을 명시해요.
/**
* Version: 1.6a
*/
특수 섹션 (Special Sections)
일부 섹션은 특별한 의미와 문법을 가져요.
- Copyright: 저작권 고지를 담아요. 이 섹션이 모듈 선언을 문서화할 때
COPYRIGHT매크로가 섹션의 내용으로 설정돼요. 저작권 섹션이 이런 특별한 대우를 받는 건 모듈 선언의 경우뿐이에요.
/** Copyright: Public Domain */
module foo;
- Params: 함수 매개변수는 params 섹션에 나열해서 문서화할 수 있어요. 식별자 뒤에
=이 따라오는 줄마다 새로운 매개변수 설명이 시작돼요. 설명은 여러 줄에 걸칠 수 있어요.
/***********************************
* foo does this.
* Params:
* x = is for this
* and not for that
* y = is for that
*/
void foo(int x, int y)
{
}
- Macros: 매크로 섹션은 Params: 섹션과 같은 문법을 따라요. NAME=value 쌍들의 연속이죠. NAME은 매크로 이름, value는 치환될 텍스트예요.
/**
* Macros:
* FOO = now is the time for
* all good men
* BAR = bar
* MAGENTA = <font color="magenta">$0</font>
*/
하이라이팅 (Highlighting)
포함된 주석 (Embedded Comments)
문서 주석 자체도 $(DDOC_COMMENT comment text) 문법으로 주석 처리할 수 있어요. 이 주석은 중첩되지 않아요.
포함된 코드 (Embedded Code)
코드 섹션을 구분하기 위해 공백을 무시하고 하이픈(-), 백틱(`), 물결표(~)를 세 개 이상으로 시작하는 줄을 쓰면 D 코드를 포함할 수 있어요:
/++
+ Our function.
+
+ Example:
+ ---
+ import std.stdio;
+
+ void foo()
+ {
+ writeln("foo!"); /* print the string */
+ }
+ ---
+/
위 예시에서는 코드 섹션 안에서 /* ... */를 쓸 수 있도록 문서 주석을 /++ ... +/ 형태로 사용했어요.
D 코드는 자동으로 구문 하이라이팅이 적용돼요. 하이라이팅 없이 다른 언어의 코드를 넣으려면 위쪽 구분선 끝에 언어 문자열을 추가하세요:
/++
+ Some C++
+ ``` cpp
+ #include <iostream>
+
+ void foo()
+ {
+ std::cout << "foo!";
+ }
+ ```
### 인라인 코드 (Inline Code)
인라인 코드는 백틱 문자(`` ` ``) 사이에 적을 수 있어요. GitHub, Reddit, Stack Overflow 같은 사이트에서 쓰는 문법과 비슷하죠. 여는 `와 닫는 `가 같은 줄에 있어야 이 동작이 발동돼요. 백틱 안에서도 매크로는 여전히 확장된다는 점을 기억하세요. [이스케이프](#punctuation_escapes)에 관한 내용도 함께 보세요.
이 섹션들 안의 텍스트는 위에서 설명한 규칙에 따라 이스케이프된 다음 `$(DDOC_BACKQUOTED)` 매크로로 감싸져요. 기본적으로 이 매크로는 코드로 포맷된 인라인 텍스트로 표시되도록 확장돼요.
말 그대로의 백틱 문자는 한 줄에 짝이 맞지 않는 ` 하나를 쓰거나 `$(BACKTICK)` 매크로를 써서 출력할 수 있어요.
```d
/// Returns `true` if `a == b`.
void foo() {}
/// Backquoted `<html>` will be displayed to the user instead
/// of passed through as embedded HTML (see below).
void bar() {}
포함된 HTML (Embedded HTML)
문서 주석에 HTML을 포함할 수 있고, HTML 출력에 그대로 전달돼요. 다만 임베디드 문서 주석 추출기의 출력 형식이 반드시 HTML이라고 단정할 수는 없으므로, 가능하면 피하는 게 좋아요.
/**
* Example of embedded HTML:
*
* <ol>
* <li><a href="http://www.digitalmars.com">Digital Mars</a></li>
* <li><a href="http://www.classicempire.com">Empire</a></li>
* </ol>
*/
제목 (Headings)
긴 문서 섹션은 제목(heading)을 추가해 하위로 나눌 수 있어요. 제목은 # 문자 1~6개로 시작하고, 그다음 공백이 오고 제목 텍스트가 이어지는 줄이에요. #의 개수가 제목 레벨을 결정해요. 제목 끝에는 # 문자가 몇 개 붙어도 괜찮아요.
/**
* # H1
* ## H2
* ### H3
* #### H4 ###
* ##### H5 ##
* ###### H6 #
*/
링크 (Links)
문서는 다른 문서나 URL로 연결(링크)할 수 있어요. 링크 스타일은 네 가지예요:
/**
* Some links:
*
* 1. A [reference link][ref] and bare reference links: [ref] or [Object]
* 2. An [inline link](https://dlang.org)
* 3. A bare URL: https://dlang.org
* 4. An 
*
* [ref]: https://dlang.org "The D Language Website"
*/
참조 링크 (Reference Links)
참조 방식 링크는 대괄호 안에 참조 라벨을 넣어요. 그 앞에 역시 대괄호로 감싼 링크 텍스트가 옵션으로 올 수 있어요.
참조 라벨은 다른 곳에 정의된 참조와 일치해야 해요. 위 예시의 [Object]처럼 문서화하는 소스 코드 스코프 안에 있는 D 심볼일 수도 있고, 같은 문서 주석 안에 명시적으로 정의된 [ref] 같은 참조일 수도 있어요. 예시에서 1번 항목의 [ref] 두 군데는 예시 맨 아래에 있는 해당 정의의 URL과 제목 텍스트로 치환돼요. 첫 번째 링크는 reference link로, 두 번째는 ref로 읽히겠죠.
참조 정의는 대괄호 안의 라벨로 시작해서 콜론, URL, 그리고 작은따옴표·큰따옴표·괄호로 감싼 선택적인 제목이 이어져요. 참조 라벨이 D 심볼과 참조 정의 양쪽 모두와 일치한다면, 참조 정의가 사용돼요.
D 심볼로 가는 생성 링크는, 문서화하는 모듈과 루트 패키지(root package)가 같으면 상대 링크가 돼요. 그렇지 않으면 URL 앞에 $(DDOC_ROOT_pkg) 매크로가 붙는데, 여기서 pkg는 링크 대상 심볼의 루트 패키지예요. D 심볼 링크는 모듈 이름 뒤에 $(DOC_EXTENSION) 매크로가 붙어 생성돼요. 그러면 위 예시의 [Object]에 대해 생성된 URL은 다음과 같이 적은 것과 같아요:
$(DOC_ROOT_object)object$(DOC_EXTENSION)#.Object
DOC_ROOT_ 매크로는 매크로 섹션을 사용해 링크할 외부 패키지에 대해 정의할 수 있어요.
인라인 링크 (Inline Links)
인라인 방식 링크는 링크 텍스트를 대괄호로, 링크 URL을 괄호로 감싸요. 참조 링크처럼 URL 뒤에는 작은따옴표·큰따옴표·괄호로 감싼 제목 텍스트가 옵션으로 올 수 있어요:
/// [a link with title text](https://dlang.org 'Some title text')
원시 URL (Bare URLs)
원시 URL은 http://나 https://로 시작하고, 그 뒤로 문자·숫자·-_?=%&/+#~. 집합의 글자 하나 이상이 이어지며, 마침표를 적어도 하나 포함하는 문자 시퀀스예요. URL 인식은 모든 매크로 텍스트 치환보다 먼저 일어나요. URL은 $(DDOC_LINK_AUTODETECT) 매크로로 감싸지고, 그 외에는 그대로 남겨져요.
이미지 (Images)
이미지는 참조 또는 인라인 링크와 같은 형태지만, 시작 대괄호 앞에 느낌표(!)를 붙여요. 일반 링크에서 링크 텍스트가 될 부분이 이미지의 alt 텍스트로 쓰여요.
목록 (Lists)
문서에는 목록이 들어갈 수 있어요. 순서 있는 목록은 숫자 뒤에 마침표를 붙여 시작해요:
/**
* 1. First this
* 2. Then this
* 1. A sub-item
*/
순서 없는 목록은 하이픈(-), 별표(*), 더하기(+) 중 하나로 시작해요. 같은 목록의 다음 항목들도 같은 기호로 시작해야 해요:
/**
* - A list
* - With a second item
*
* + A different list
* - With a sub-item
*
* * A third list (note the double asterisks)
*/
위 예시에서 별표가 두 겹인 걸 주목하세요. 목록이 별표로 구분되는 문서 주석 안에 있기 때문에, 맨 앞 별표는 목록 항목이 아니라 문서 주석의 일부로 취급돼요. 이는 다른 줄이 별표로 시작하지 않을 때도 마찬가지예요:
/**
- A list
* Not a list because the asterisk is part of the documentation comment
*/
/++
+ + The caveat also applies to plus-delimited documentation comments
+/
목록 항목에는 새 단락, 제목, 포함된 코드, 하위 목록 항목 같은 내용을 넣을 수 있어요. 목록 기호 다음 텍스트의 들여쓰기에 맞춰 내용을 들여쓰기만 하면 돼요:
/**
* - A parent list item
*
* With a second paragraph
*
* - A sub-item
* ---
* // A code example inside the sub-item
* ---
*/
표 (Tables)
데이터는 표에 넣을 수 있어요. 표는 하나의 헤더 행, 구분 행, 그리고 0개 이상의 데이터 행으로 이루어져요. 각 행의 셀은 파이프(|) 문자로 구분돼요. 맨 앞과 맨 뒤의 |는 선택이에요. 구분 행의 셀 개수는 헤더 행의 셀 개수와 일치해야 해요:
/**
* | Item | Price |
* | ---- | ----: |
* | Wigs | $10 |
* Wheels | $13
* | Widgets | $200 |
*/
구분 행의 셀은 하이픈(-)과 선택적인 콜론(:)으로 이루어져요. 하이픈 왼쪽의 :은 왼쪽 정렬 열을, 오른쪽의 :은 오른쪽 정렬 열을(위 예시처럼요), 양쪽의 :은 가운데 정렬 열을 만들어요.
인용 (Quotes)
문서에는 각 줄 앞에 >를 붙여 인용할 내용을 넣을 수 있어요. 인용 안에는 제목, 목록, 포함된 코드 등이 올 수 있어요.
/**
* > To D, or not to D. -- Willeam NerdSpeare
*/
인용된 줄 바로 다음에 오는 텍스트 줄은 인용의 일부로 간주돼요:
/**
* > This line
* and this line are both part of the quote
*
* This line is not part of the quote.
*/
가로줄 (Horizontal Rules)
별표, 밑줄, 하이픈을 세 개 이상 포함한 줄을 추가해서 가로줄을 만들 수 있어요:
/**
* ***
* ___
*/
목록과 마찬가지로, 위 예시의 맨 앞 *는 별표로 구분되는 문서 주석의 일부이므로 제거된다는 점을 기억하세요. 이후 별표가 적어도 세 개는 필요해요.
하이픈으로 가로줄을 만들려면 하이픈 사이에 공백을 넣어야 해요. 공백이 없으면 포함된 코드 블록의 시작이나 끝으로 취급되거든요. 가로줄에는 공백이 들어갈 수 있다는 점도 참고하세요:
/**
* - - -
* _ _ _
* * * *
*/
텍스트 강조 (Text Emphasis)
별표(*)로 감싼 텍스트 조각은 강조되고, 별표 두 개(**)로 감싼 텍스트는 강하게 강조돼요:
single asterisks는 single asterisks로 렌더링돼요.
double asterisks는 double asterisks로 렌더링돼요.
말 그대로의 별표를 넣으려면 백슬래시 이스케이프를 사용하세요. \*는 *로 렌더링돼요.
Markdown과 달리, 밑줄(_)은 텍스트 강조에 지원되지 않아요. snake_case 이름과 식별자 강조의 밑줄 접두 처리를 망가뜨리기 때문이죠.
식별자 강조 (Identifier Emphasis)
문서 주석에서 함수 매개변수이거나 연결된 선언의 스코프 안에 있는 이름인 식별자는 출력에서 강조돼요. 이 강조는 이탤릭체, 볼드체, 하이퍼링크 등의 형태일 수 있어요. 어떻게 강조되는지는 그것이 무엇이냐에 따라 달라요 — 함수 매개변수인지, 타입인지, D 키워드인지 등이죠.
의도하지 않은 식별자 강조를 막으려면 식별자 앞에 밑줄(_)을 붙일 수 있어요. 이 밑줄은 출력에서 제거돼요.
동작 플래그 (Behavior Flags)
함수는 선언 아래에 표시되는 배지 형태의 *동작 플래그(behavior flags)*로 동작 보증을 알릴 수 있어요. 프로젝트 README에서 흔히 보는 빌드 상태 배지와 비슷하죠. 배지가 하나라도 있으면 Current Behaviors: 라벨로 시작돼요. 사용할 수 있는 플래그는 네 가지예요: $(NOALLOC)(No Allocations로 렌더링), $(NOGC)(No GC로), $(NOTHROW)(No Exceptions Thrown으로), $(PURE)(Is Pure로). 각 플래그는 해당될 때만 표시되고, 각자 고유한 색으로 렌더링돼요.
$(NOGC), $(NOTHROW), $(PURE) 플래그는 문서화하는 선언에 @nogc, nothrow, pure 속성이 있을 때 자동으로 방출돼요. $(NOALLOC) 플래그는 대응하는 언어 속성이 없어서 직접 작성했을 때만 표시돼요.
어떤 플래그든 문서 주석 어디에나 수동으로 작성할 수 있어요. 수동으로 작성한 플래그는 선언 제목 아래에 표시되도록 이동되고, 수동으로 썼든 속성에서 자동으로 추가됐든 중복된 플래그는 하나의 배지로 합쳐져요.
/++
Copy `n` bytes.
$(DOLLAR)(NOALLOC)
+/
void* fastCopy(void* dst, const(void)* src, size_t n) @nogc nothrow pure;
위 예시는 함수 제목 아래 Current Behaviors: 라벨 뒤에, (수동으로 작성한) No Allocations 배지와 (속성에서 자동으로 추가된) No GC, No Exceptions Thrown, Is Pure 배지를 함께 렌더링해요.
문자 엔티티 (Character Entities)
일부 문자는 문서 처리기에게 특별한 의미를 가져요. 혼란을 피하려면 해당 문자들을 대응하는 문자 엔티티로 바꾸는 게 가장 좋을 때가 있어요:
| Character | Entity |
|---|---|
| < | < |
| > | > |
| & | & |
코드 섹션 안에서는, 또는 특수 문자가 바로 뒤에 #이나 문자가 따라붙지 않을 때는 이렇게 할 필요가 없어요.
구두점 이스케이프 (Punctuation Escapes)
백슬래시 \를 써서 ASCII 구두점 기호를 이스케이프할 수 있어요. 그러면 백슬래시 없이 원래 문자를 출력해요. 단, 다음 문자들은 미리 정의된 매크로를 대신 출력해요:
| Character | Macro |
|---|---|
| ( | $(LPAREN) |
| ) | $(RPAREN) |
| , | $(COMMA) |
| $ | $(DOLLAR) |
백슬래시를 출력하려면 그냥 백슬래시 두 개를 연달아 쓰면 돼요: \\. 포함된 코드나 인라인 코드 안의 백슬래시는 구두점을 이스케이프하지 않고 그대로 출력에 포함된다는 점을 기억하세요. 구두점이 아닌 문자 앞의 백슬래시도 그대로 출력에 포함돼요. 예를 들어 C:\dmd2\bin\dmd.exe는 포함된 백슬래시를 이스케이프할 필요가 없어요.
문서화되지 않는 것 (No Documentation)
다음 구문들은 문서 주석이 있어도 문서가 생성되지 않아요:
-
불변식(Invariants)
-
포스트블릿(Postblits)
-
소멸자(Destructors)
-
정적 생성자와 정적 소멸자
-
클래스 정보, 타입 정보, 모듈 정보
매크로 (Macros)
문서 주석 처리기에는 간단한 매크로 텍스트 전처리기가 들어 있어요. 섹션 텍스트에 $(NAME)이 나타나면 해당 NAME 매크로의 치환 텍스트로 바뀌어요. 매크로는 인수를 받을 수도 있어요: $(NAME argument)처럼요.
예를 들어:
/**
Macros:
PARAM = <u>$1</u>
MATH_DOCS = <a href="https://dlang.org/phobos/std_math.html">Math Docs</a>
*/
module math;
/**
* This function returns the sum of $(PARAM a) and $(PARAM b).
* See also the $(MATH_DOCS).
*/
int sum(int a, int b) { return a + b; }
위 코드는 다음과 같은 출력을 만들 거예요:
<h1>math</h1>
<dl><dt><big><a name="sum"></a>int <u>sum</u>(int <i>a</i>, int <i>b</i>);
</big></dt>
<dd>This function returns the <u>sum</u> of <u><i>a</i></u> and <u><i>b</i></u>.
See also the <a href="https://dlang.org/phobos/std_math.html">Math Docs</a>.
</dd>
</dl>
치환 텍스트는 더 많은 매크로가 있는지 재귀적으로 검사돼요. 발견되면 차례로 확장되죠. 이미 확장된 매크로가, 인수 없이 또는 바깥 매크로와 같은 인수 텍스트로 재귀적으로 다시 만나면, 그 매크로는 아무 텍스트로도 치환되지 않아요.
-
치환 텍스트 경계를 가로지르는 매크로 호출은 확장되지 않아요.
-
매크로 이름이 정의되어 있지 않으면 치환 텍스트는
$(DDOC_UNDEFINED_MACRO(NAME))이 돼요. 기본값은 비어 있죠. -
$(NAME)이 매크로 확장 없이 출력에 존재하기를 원한다면$를 백슬래시 이스케이프할 수 있어요:\$.
매크로 인수 (Macro Arguments)
매크로를 호출할 때 식별자 끝에서 닫는 )까지의 모든 텍스트가 매크로의 인수로 전달돼요. 이 인수는 매크로 정의 안에서 $0 매개변수로 참조할 수 있죠. 치환 텍스트 안의 $0는 각 인수의 텍스트가 쉼표로 구분된 것으로 치환돼요.
인수 텍스트에 쉼표가 있으면 여러 인수를 의미해요. 매크로 정의 안에서 $1은 첫 번째 쉼표까지의 인수 텍스트, $2는 첫 번째 쉼표부터 두 번째 쉼표까지, 이렇게 $9까지를 나타내요. $+는 첫 번째 쉼표부터 닫는 )까지의 텍스트를 나타내요.
-
인수 텍스트는 중첩된 괄호,
""나''문자열,<!-- ... -->주석, 태그를 포함할 수 있어요. -
짝이 맞지 않는 괄호를 써야 한다면 백슬래시 이스케이프할 수 있어요:
\(또는\). -
인수 구분자로 쓰이지 않는 말 그대로의 쉼표는, 별도의 인수를 기대하는 매크로를 호출할 때 이스케이프할 수 있어요. 쉼표를 다루기 위해
ARGS=$0매크로를 정의하면 유용할 수 있어요. 이 둘은 동일해요:-
$(FOO one, $(ARGS two, dwa, dos), three) -
$(FOO one, two\, dwa\, dos, three)
-
매크로 정의 (Macro Definitions)
매크로 정의는 명시된 순서대로 다음 소스에서 와요:
-
미리 정의된 매크로.
-
명령 줄에 지정된
*.ddoc파일의 정의. -
Ddoc이 생성한 런타임 정의.
-
Macros:섹션의 정의.
매크로 재정의는 같은 이름의 이전 정의를 대체해요. 즉 여러 소스에서 오는 매크로 정의의 순서가 하나의 계층을 이룬다는 뜻이에요.
D_와 DDOC_로 시작하는 매크로 이름은 예약되어 있어요.
미리 정의된 매크로 (Predefined Macros)
Ddoc에는 여러 매크로가 미리 정의되어 있어요. 이들은 Ddoc이 프레젠테이션을 포맷하고 하이라이팅하는 데 필요한 최소한의 정의를 나타내죠. 정의는 간단한 HTML을 위한 것이에요.
모든 미리 정의된 매크로의 구현은 구현(implementation)에 따라 달라요. 참조 구현의 매크로 정의는 여기에서 찾을 수 있어요.
Ddoc은 HTML 코드를 생성하지 않아요. 기본 포맷팅 매크로로 포맷한 다음, (미리 정의된 형태의) 매크로를 HTML로 확장할 뿐이죠. HTML이 아닌 출력을 원한다면 이 매크로들을 재정의해야 해요.
| Name | Description |
|---|---|
| B | 인수를 볼드체로 해요 |
| I | 인수를 이탤릭체로 해요 |
| U | 인수에 밑줄을 쳐요 |
| P | 인수는 단락이에요 |
| DL | 인수는 정의 목록이에요 |
| DT | 인수는 정의 목록의 정의예요 |
| DD | 인수는 정의의 설명이에요 |
| TABLE | 인수는 표예요 |
| TR | 인수는 표의 행이에요 |
| TH | 인수는 행의 헤더 항목이에요 |
| TD | 인수는 행의 데이터 항목이에요 |
| OL | 인수는 순서 있는 목록이에요 |
| UL | 인수는 순서 없는 목록이에요 |
| LI | 인수는 목록의 항목이에요 |
| BIG | 인수는 한 글자 크기 더 크게 해요 |
| SMALL | 인수는 한 글자 크기 더 작게 해요 |
| BR | 새 줄을 시작해요 |
| LINK | 인수에 클릭 가능한 링크를 생성해요 |
| LINK2 | 클릭 가능한 링크를 생성해요, 첫 인수가 주소예요 |
| RED | 인수는 빨간색으로 설정해요 |
| BLUE | 인수는 파란색으로 설정해요 |
| GREEN | 인수는 초록색으로 설정해요 |
| YELLOW | 인수는 노란색으로 설정해요 |
| BLACK | 인수는 검은색으로 설정해요 |
| WHITE | 인수는 흰색으로 설정해요 |
| NOALLOC | 할당하지 않는 함수를 위한 동작 플래그 배지예요 |
| NOGC | @nogc 함수를 위한 동작 플래그 배지예요 |
| NOTHROW | nothrow 함수를 위한 동작 플래그 배지예요 |
| PURE | pure 함수를 위한 동작 플래그 배지예요 |
| D_CODE | 인수는 D 코드예요 |
| D_INLINECODE | 인수는 인라인 D 코드예요 |
| LF | 줄 바꿈(개행)을 삽입해요 |
| LPAREN | 왼쪽 괄호를 삽입해요 |
| RPAREN | 오른쪽 괄호를 삽입해요 |
| BACKTICK | 백틱을 삽입해요 |
| DOLLAR | 달러 기호를 삽입해요 |
| DDOC | 출력을 위한 전체 템플릿이에요 |
| ESCAPES | 치환할 문자들이에요 |
DDOC는 특별해요. 전체 생성 텍스트가 삽입되는 보일러플레이트를 지정하거든요(이는 Ddoc이 생성한 BODY 매크로로 나타나요). 예를 들어 스타일 시트를 사용하려면 DDOC를 다음과 같이 재정의하면 돼요:
DDOC = <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<META http-equiv="content-type" content="text/html; charset=utf-8">
<title>$(TITLE)</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head><body>
<h1>$(TITLE)</h1>
$(BODY)
</body></html>
ESCAPES는 특수 문자를 문자열로 대체하는 일련의 치환을 정의해요. 출력 형식이 특정 문자를 이스케이프해야 할 때 유용하죠. 예를 들어 HTML에서 &는 &로 이스케이프해야 해요. 문법은 /c/string/이에요. 여기서 c는 단일 문자거나 공백·쉼표로 구분된 여러 문자이고, string은 치환 텍스트예요.
ESCAPES = /&/AddressOf!/
/!/Exclamation/
/?/QuestionMark/
/,/Comma/
/{ }/Parens/
/<,>/Arrows/
D 코드의 하이라이팅은 다음 매크로들이 수행해요:
| Name | Description |
|---|---|
| D_COMMENT | 주석 하이라이팅 |
| D_STRING | 문자열 리터럴 하이라이팅 |
| D_KEYWORD | D 키워드 하이라이팅 |
| D_PSYMBOL | 현재 선언 이름 하이라이팅 |
| D_PARAM | 현재 함수 선언 매개변수 하이라이팅 |
하이라이팅 매크로는 DDOC_로 시작해요. 이들은 프레젠테이션의 개별 부분 포맷을 제어하죠.
| Name | Description |
|---|---|
| DDOC_CONSTRAINT | 템플릿 제약(constraint) 하이라이팅 |
| DDOC_COMMENT | 출력에 주석을 삽입해요 |
| DDOC_DECL | 선언 하이라이팅 |
| DDOC_DECL_DD | 선언의 설명 하이라이팅 |
| DDOC_DITTO | ditto 선언 하이라이팅 |
| DDOC_SECTIONS | 모든 섹션 하이라이팅 |
| DDOC_SUMMARY | 요약 섹션 하이라이팅 |
| DDOC_DESCRIPTION | 설명 섹션 하이라이팅 |
| DDOC_AUTHORS | 작성자 섹션 하이라이팅 |
| DDOC_BUGS | 버그 섹션 하이라이팅 |
| DDOC_COPYRIGHT | 저작권 섹션 하이라이팅 |
| DDOC_DATE | 날짜 섹션 하이라이팅 |
| DDOC_DEPRECATED | deprecated 섹션 하이라이팅 |
| DEPRECATED | deprecated 선언을 위한 래퍼예요 |
| DDOC_EXAMPLES | 예제 섹션 하이라이팅 |
| DDOC_HISTORY | 이력 섹션 하이라이팅 |
| DDOC_LICENSE | 라이선스 섹션 하이라이팅 |
| DDOC_OVERLOAD_SEPARATOR | 주어진 이름의 오버로드들 사이에 구분자를 삽입해요 |
| DDOC_RETURNS | 반환값 섹션 하이라이팅 |
| DDOC_SEE_ALSO | see-also 섹션 하이라이팅 |
| DDOC_STANDARDS | 표준 섹션 하이라이팅 |
| DDOC_THROWS | throws 섹션 하이라이팅 |
| DDOC_VERSION | 버전 섹션 하이라이팅 |
| DDOC_SECTION_H | 비표준 섹션의 섹션 이름 하이라이팅 |
| DDOC_SECTION | 비표준 섹션의 내용 하이라이팅 |
| DDOC_MEMBERS | 클래스, 구조체 등의 모든 멤버 기본 하이라이팅 |
| DDOC_MODULE_MEMBERS | 모듈의 모든 멤버 하이라이팅 |
| DDOC_CLASS_MEMBERS | 클래스의 모든 멤버 하이라이팅 |
| DDOC_STRUCT_MEMBERS | 구조체의 모든 멤버 하이라이팅 |
| DDOC_ENUM_MEMBERS | 열거형의 모든 멤버 하이라이팅 |
| DDOC_TEMPLATE_PARAM | 템플릿의 개별 매개변수 하이라이팅 |
| DDOC_TEMPLATE_PARAM_LIST | 템플릿의 매개변수 목록 하이라이팅 |
| DDOC_TEMPLATE_MEMBERS | 템플릿의 모든 멤버 하이라이팅 |
| DDOC_ENUM_BASETYPE | 열거형의 기반 타입 하이라이팅 |
| DDOC_PARAMS | 함수 매개변수 섹션 하이라이팅 |
| DDOC_PARAM_ROW | name=value 함수 매개변수 하이라이팅 |
| DDOC_PARAM_ID | 매개변수 이름 하이라이팅 |
| DDOC_PARAM_DESC | 매개변수 값 하이라이팅 |
| DDOC_BLANKLINE | 빈 줄을 삽입해요 |
| DDOC_ANCHOR | 특정 선언 섹션으로 하이퍼링크하는 데 쓰는 이름 있는 앵커로 확장돼요. 인수 $1은 정규화된 선언 이름으로 확장돼요 |
| DDOC_PSYMBOL | 특정 섹션이 가리키는 선언 이름 하이라이팅 |
| DDOC_PSUPER_SYMBOL | 클래스의 기반 타입 하이라이팅 |
| DDOC_KEYWORD | D 키워드 하이라이팅 |
| DDOC_PARAM | 함수 매개변수 하이라이팅 |
| DDOC_BACKQUOTED | 인라인 코드를 삽입해요 |
| DDOC_FLAGS | 선언 아래에 방출되는 동작 플래그 배지의 컨테이너예요 |
| DDOC_AUTO_PSYMBOL_SUPPRESS | 밑줄로 시작하는 자동 감지 심볼 하이라이팅 |
| DDOC_AUTO_PSYMBOL | 자동 감지 심볼 하이라이팅 |
| DDOC_AUTO_KEYWORD | 자동 감지 키워드 하이라이팅 |
| DDOC_AUTO_PARAM | 자동 감지 매개변수 하이라이팅 |
예를 들어, DDOC_SUMMARY를 이렇게 재정의할 수 있어요:
DDOC_SUMMARY = $(GREEN $0)
그러면 모든 요약 섹션이 이제 초록색으로 표시돼요.
sc.ini의 DDOCFILE에서 오는 매크로 정의 (Macro Definitions from sc.ini's DDOCFILE)
매크로 정의가 담긴 텍스트 파일을 만들어 sc.ini에 지정할 수 있어요:
DDOCFILE=myproject.ddoc
명령 줄의 .ddoc 파일에서 오는 매크로 정의 (Macro Definitions from .ddoc Files on the Command Line)
DMD 명령 줄에서 확장자 .ddoc을 가진 파일 이름은 텍스트 파일로 취급되며, 순서대로 읽혀 처리돼요.
Ddoc이 생성하는 매크로 정의 (Macro Definitions Generated by Ddoc)
| 매크로 이름 (Macro Name) | 내용 (Content) |
|---|---|
| BODY | 생성된 문서 텍스트로 설정돼요 |
| TITLE | 모듈 이름으로 설정돼요 |
| DATETIME | 현재 날짜와 시간으로 설정돼요 |
| YEAR | 현재 연도로 설정돼요 |
| COPYRIGHT | 모듈 주석의 일부인 Copyright 섹션의 내용으로 설정돼요 |
| DOCFILENAME | 생성된 출력 파일의 이름으로 설정돼요 |
| SRCFILENAME | 문서가 생성되는 소스 파일의 이름으로 설정돼요 |
Ddoc으로 단위 테스트에서 예제 만들기 (Using Ddoc to generate examples from unit tests)
Ddoc은 단위 테스트를 사용해 선언의 사용 예제를 자동으로 생성할 수 있어요. 선언 뒤에 문서화된 단위 테스트가 따라오면, 테스트의 코드가 선언의 예제 섹션에 삽입돼요. 이렇게 하면 코드에 대한 문서가 오래되어 버리는 흔한 문제를 피할 수 있죠.
문서화된 단위 테스트를 만들려면 unittest 블록 앞에 슬래시 세 개를 붙이기만 하면 돼요:
///
unittest
{
...
}
더 자세한 내용은 documented unit tests에 대한 전체 섹션을 보세요.
다른 문서에 Ddoc 사용하기 (Using Ddoc for other Documentation)
Ddoc은 주로 임베디드 주석에서 문서를 만드는 용도로 설계되었어요. 하지만 다른 일반 문서를 처리하는 데에도 쓸 수 있죠. 그렇게 하는 이유는 Ddoc의 매크로 능력과 D 코드 구문 하이라이팅 능력을 활용하기 위해서예요.
.d 소스 파일이 "Ddoc" 문자열로 시작하면, D 코드 소스 파일이 아니라 범용 문서로 취급돼요. "Ddoc" 문자열 바로 다음부터 파일 끝이나 Macros: 섹션까지가 문서를 이뤄요. --- 줄로 구분된 사이에 포함된 D 코드의 하이라이팅 외에는 그 텍스트에 자동 하이라이팅이 적용되지 않아요. 매크로 처리만 수행되죠.
D 문서의 상당 부분 자체가 이 방식으로 생성돼요. 이 페이지도 그렇죠. 그런 문서는 아래쪽에 Ddoc으로 생성되었음이 표시돼요.
보안 고려 사항 (Security considerations)
DDoc 주석에는 <script> 태그를 포함한 원시 HTML이 들어갈 수 있다는 점을 유의하세요. 신뢰할 수 없는 소스에서 생성한, 렌더링된 DDoc HTML을 게시하거나 배포할 때는 주의해야 해요. 크로스 사이트 스크립팅(cross-site scripting)을 허용할 수 있기 때문이죠.
D 문서 생성기 링크 (Links to D documentation generators)
Ddoc을 사용하는 현재의 D 문서 생성기 목록은 우리 위키 페이지에서 찾을 수 있어요.
더 알아보기 (Learn more)
이번 글은 D 언어 공식 사양의 'Embedded Documentation' 챕터를 한국어로 옮긴 거예요. 문서 주석이 코드에 박혀 어떤 섹션으로 나뉘고, 매크로와 하이라이팅을 거쳐 최종 문서가 만들어지는 흐름을 따라가 봤죠. 더 깊이 알아보고 싶다면 아래 자료를 이어서 보면 좋아요.
-
Embedded Documentation (원문) — 이번 글의 원문이에요.
-
Documented unit tests — 단위 테스트를 문서에 연결하는 방법이에요.
-
D 언어 사양 전체 보기 — D 언어 공식 사양 목차예요.
Ddoc이 실제로 만들어 내는 매크로 테마나 커스텀 출력을 다뤄 보고 싶다면, 참조 구현의 default_ddoc_theme.ddoc를 뜯어보는 것도 좋은 출발점이 될 거예요.