impl preprocessor
This commit is contained in:
parent
c596a80518
commit
ca9658be6b
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,5 +2,3 @@ book
|
|||||||
.env
|
.env
|
||||||
cache
|
cache
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build
|
|
||||||
|
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
authors = ["monoid"]
|
authors = ["monoid"]
|
||||||
language = "ko"
|
language = "ko"
|
||||||
multilingual = false
|
multilingual = false
|
||||||
src = "build"
|
src = "src"
|
||||||
title = "Software Requirement Specification"
|
title = "Software Requirement Specification"
|
||||||
|
|
||||||
[preprocessor]
|
[preprocessor]
|
||||||
|
|
||||||
[preprocessor.mermaid]
|
[preprocessor.mermaid]
|
||||||
command = "mdbook-mermaid"
|
command = "mdbook-mermaid"
|
||||||
|
[preprocessor.etap]
|
||||||
|
command = "deno run -A --no-check tools/preprop.ts"
|
||||||
|
before = ["mermaid"]
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
|
|
||||||
|
21
cli.py
21
cli.py
@ -18,15 +18,6 @@ def updateIssue(issuePath: str, verbose = False):
|
|||||||
cmd = ["deno", "run","--no-check", "-A","tools/getIssue.ts", "--path",issuePath]
|
cmd = ["deno", "run","--no-check", "-A","tools/getIssue.ts", "--path",issuePath]
|
||||||
cmdExecute(cmd, verbose, "update issue:")
|
cmdExecute(cmd, verbose, "update issue:")
|
||||||
|
|
||||||
def printDocument(issuePath:str, outDir:str, watch = False, verbose = False):
|
|
||||||
if verbose:
|
|
||||||
print("build document : issuePath(", issuePath, ") to ", outDir)
|
|
||||||
cmd = ["deno", "run","--no-check" ,"-A","tools/printDocument.ts", "--issue_path", issuePath, "--outDir", outDir]
|
|
||||||
if watch:
|
|
||||||
cmd.append("--watch")
|
|
||||||
p = subprocess.run(cmd)
|
|
||||||
p.check_returncode()
|
|
||||||
|
|
||||||
def build(args):
|
def build(args):
|
||||||
parser = argparse.ArgumentParser(description='Compiling the documentation', prog="cli.py build")
|
parser = argparse.ArgumentParser(description='Compiling the documentation', prog="cli.py build")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode')
|
parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode')
|
||||||
@ -38,10 +29,10 @@ def build(args):
|
|||||||
if args.verbose:
|
if args.verbose:
|
||||||
print("build start")
|
print("build start")
|
||||||
|
|
||||||
issuePath = os.path.join(args.outDir,"issues.json")
|
|
||||||
if args.update_issues:
|
if args.update_issues:
|
||||||
|
os.makedirs("cache", exist_ok=True)
|
||||||
|
issuePath = os.path.join("cache","issues.json")
|
||||||
updateIssue(issuePath, args.verbose)
|
updateIssue(issuePath, args.verbose)
|
||||||
printDocument(issuePath, args.outDir, args.watch, args.verbose)
|
|
||||||
cmd = ["mdbook", "build"]
|
cmd = ["mdbook", "build"]
|
||||||
cmdExecute(cmd, args.verbose)
|
cmdExecute(cmd, args.verbose)
|
||||||
|
|
||||||
@ -57,11 +48,7 @@ def serve(args):
|
|||||||
if args.verbose:
|
if args.verbose:
|
||||||
print("serve start")
|
print("serve start")
|
||||||
cmd = ["mdbook", "serve", "--port", str(args.port)]
|
cmd = ["mdbook", "serve", "--port", str(args.port)]
|
||||||
p = subprocess.Popen(cmd)
|
cmdExecute(cmd, args.verbose, "serve:")
|
||||||
printDocument(issuePath, outDir, True, args.verbose)
|
|
||||||
p.wait()
|
|
||||||
if p.returncode != 0:
|
|
||||||
sys.exit(p.returncode)
|
|
||||||
|
|
||||||
def help(_args):
|
def help(_args):
|
||||||
global commandList
|
global commandList
|
||||||
@ -80,7 +67,7 @@ def issueUpdate(args):
|
|||||||
def buildPdf(args):
|
def buildPdf(args):
|
||||||
parser = argparse.ArgumentParser(description='Print to pdf', prog="cli.py buildPdf")
|
parser = argparse.ArgumentParser(description='Print to pdf', prog="cli.py buildPdf")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode')
|
parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode')
|
||||||
parser.add_argument('--outDir', default="build/doc.pdf", help='output directory')
|
parser.add_argument('--outDir', default="cache/doc.pdf", help='output directory')
|
||||||
parser.add_argument('--browser-path', help='path to the browser')
|
parser.add_argument('--browser-path', help='path to the browser')
|
||||||
args = parser.parse_args(args)
|
args = parser.parse_args(args)
|
||||||
if os.path.exists(args.outDir):
|
if os.path.exists(args.outDir):
|
||||||
|
@ -5,3 +5,4 @@
|
|||||||
- [Specific Requirement](./specific.md)
|
- [Specific Requirement](./specific.md)
|
||||||
- [Supporting information](./support.md)
|
- [Supporting information](./support.md)
|
||||||
- [Architecture](./architecture.md)
|
- [Architecture](./architecture.md)
|
||||||
|
- [Testing](./testing.md)
|
335
src/testing.md
Normal file
335
src/testing.md
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
## 유닛 테스트
|
||||||
|
|
||||||
|
유닛 테스트로 69.6%의 Line Coverage와 73.4%의 Function Coverage를 달성했다.
|
||||||
|
다음과 같은 로그가 있다.
|
||||||
|
|
||||||
|
```
|
||||||
|
running 2 tests from ./src/auth/permission.test.ts
|
||||||
|
permission.test ... ok (8ms)
|
||||||
|
permission empty ... ok (16ms)
|
||||||
|
running 4 tests from ./src/auth/session.test.ts
|
||||||
|
Session ...
|
||||||
|
set ... ok (9ms)
|
||||||
|
delete ... ok (16ms)
|
||||||
|
ok (42ms)
|
||||||
|
Login Handler ...
|
||||||
|
login with invalid format ... ok (15ms)
|
||||||
|
login with invalid password ... ok (16ms)
|
||||||
|
login ... ok (16ms)
|
||||||
|
logout with no session ... ok (16ms)
|
||||||
|
logout ... ok (16ms)
|
||||||
|
ok (96ms)
|
||||||
|
getSession ... ok (16ms)
|
||||||
|
getSession with invalid cookie ... ok (16ms)
|
||||||
|
running 1 test from ./src/auth/user.test.ts
|
||||||
|
user.createAdminUser ... ok (15ms)
|
||||||
|
running 4 tests from ./src/document/filedoc.test.ts
|
||||||
|
readDocFile ... ok (19ms)
|
||||||
|
readDocFile: not found ... ok (16ms)
|
||||||
|
readDocFile: invalid json ... ok (16ms)
|
||||||
|
saveDocFile ... ok (15ms)
|
||||||
|
running 3 tests from ./src/router/methodHandle.test.ts
|
||||||
|
methodHandle: basic methods ... ok (8ms)
|
||||||
|
methodHandle: not found ... ok (16ms)
|
||||||
|
methodHandle: options ... ok (16ms)
|
||||||
|
running 8 tests from ./src/router/route.test.ts
|
||||||
|
route: basic route ... ok (10ms)
|
||||||
|
route: double slash route ... ok (16ms)
|
||||||
|
route: double match ... ok (16ms)
|
||||||
|
route: test context ... ok (16ms)
|
||||||
|
route: test regex ... ok (16ms)
|
||||||
|
route: test not found ... ok (16ms)
|
||||||
|
route: encode_route ... ok (2ms)
|
||||||
|
route: router in router ... ok (13ms)
|
||||||
|
running 4 tests from ./src/rpc/chunk.test.ts
|
||||||
|
basic chunk operation ...
|
||||||
|
create chunk ... ok (19ms)
|
||||||
|
delete chunk ... ok (15ms)
|
||||||
|
modify chunk ... ok (15ms)
|
||||||
|
move chunk ... ok (15ms)
|
||||||
|
invalid chunk operation ... ok (17ms)
|
||||||
|
ok (98ms)
|
||||||
|
test chunk notification operation ... ok (15ms)
|
||||||
|
test chunk conflict ... ok (16ms)
|
||||||
|
test chunk conflict resolve with history ... ok (32ms)
|
||||||
|
running 2 tests from ./src/rpc/doc.test.ts
|
||||||
|
handleDocumentMethod ... ok (4ms)
|
||||||
|
handleTagMethod ...
|
||||||
|
setTag ... ok (13ms)
|
||||||
|
getTag ... ok (15ms)
|
||||||
|
conflict ... ok (15ms)
|
||||||
|
ok (61ms)
|
||||||
|
running 3 tests from ./src/rpc/share.test.ts
|
||||||
|
handleShareGetInfo ... ok (18ms)
|
||||||
|
handleShareDocMethod ... ok (15ms)
|
||||||
|
handleShareMethod with no existing share token ... ok (16ms)
|
||||||
|
running 1 test from ./src/server.test.ts
|
||||||
|
server rpc test ... ok (1s)
|
||||||
|
running 3 tests from ./src/setting.test.ts
|
||||||
|
setting: basic ... ok (35ms)
|
||||||
|
setting: default value ... ok (7ms)
|
||||||
|
setting: defered register ... ok (16ms)
|
||||||
|
test result: ok. 35 passed (15 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (2s)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 기능 테스트
|
||||||
|
|
||||||
|
### Chunk
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Test Data</th>
|
||||||
|
<th>P/F</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1</td>
|
||||||
|
<td>Focus/Unfocus</td>
|
||||||
|
<td>1. 청크를 클릭한다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2</td>
|
||||||
|
<td>remove</td>
|
||||||
|
<td>1. 청크를 삭제하는 버튼을 클릭한다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3-1</td>
|
||||||
|
<td>render - markdown</td>
|
||||||
|
<td>1. 마크다운 청크 렌더링을 확인한다.</td>
|
||||||
|
<td> # 제목 </td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3-2</td>
|
||||||
|
<td>render - latex</td>
|
||||||
|
<td>1. LaTex 청크 렌더링을 확인한다.</td>
|
||||||
|
<td> sum^n_{n=0}n = \frac{n(n+1)}2$$ </td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3-3</td>
|
||||||
|
<td>render - link</td>
|
||||||
|
<td>1. Image 청크 렌더링을 확인한다.</td>
|
||||||
|
<td>http://picsum.photos</td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>4</td>
|
||||||
|
<td>previews</td>
|
||||||
|
<td>1. Katex 청크의 미리보기를 본다.</td>
|
||||||
|
<td> sum^n_{n=0}n = \frac{n(n+1)}2$$ </td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>10</td>
|
||||||
|
<td>autocomplete</td>
|
||||||
|
<td>1. <kbd>Ctrl+Space</kbd>를 눌러 자동완성을 시도한다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>11</td>
|
||||||
|
<td>swap positions</td>
|
||||||
|
<td>1. 청크의 위치를 바꾼다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>27-1</td>
|
||||||
|
<td>edit</td>
|
||||||
|
<td>1. 청크를 수정한다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>27-2</td>
|
||||||
|
<td>edit chunk conflict</td>
|
||||||
|
<td>1. 청크를 수정모드에 들어간다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Document
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Test Data</th>
|
||||||
|
<th>P/F</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>5</td>
|
||||||
|
<td>view Chunk</td>
|
||||||
|
<td>1. 문서를 열어 청크가 렌더링되는지 본다.</td>
|
||||||
|
<td>test.syd</td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>7</td>
|
||||||
|
<td>add/delete tag</td>
|
||||||
|
<td>1. 문서에 태그를 추가한다.<br>2. 문서에 태그를 삭제한다.</td>
|
||||||
|
<td>A</td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>8</td>
|
||||||
|
<td>Drag And Drop Upload,</td>
|
||||||
|
<td>1. 텍스트를 드래그한다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### File
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Test Data</th>
|
||||||
|
<th>P/F</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>14</td>
|
||||||
|
<td>create/delete/rename file</td>
|
||||||
|
<td>1. 파일을 만든다.</td>
|
||||||
|
<td>test.txt</td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>15</td>
|
||||||
|
<td>upload/download files</td>
|
||||||
|
<td>1. 파일을 업로드한다.</td>
|
||||||
|
<td>test.txt</td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>18</td>
|
||||||
|
<td>export document</td>
|
||||||
|
<td>1. export 버튼을 누른다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Test Data</th>
|
||||||
|
<th>P/F</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>16</td>
|
||||||
|
<td>Document Search</td>
|
||||||
|
<td>1. 검색버튼을 눌러 검색을 한다.</td>
|
||||||
|
<td>chunk</td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Stash
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Test Data</th>
|
||||||
|
<th>P/F</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>17</td>
|
||||||
|
<td>render</td>
|
||||||
|
<td>1. 스태시가 그려지는지 확인한다</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>19</td>
|
||||||
|
<td>add</td>
|
||||||
|
<td>1. 청크를 추가한다</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>20</td>
|
||||||
|
<td>remove</td>
|
||||||
|
<td>1. 청크를 삭제한다</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>21</td>
|
||||||
|
<td>Drag and Drop to Document</td>
|
||||||
|
<td>1. 청크로부터 문서로 청크를 옮긴다.</td>
|
||||||
|
<td></td>
|
||||||
|
<td>P</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Management
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Test Data</th>
|
||||||
|
<th>P/F</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>22</td>
|
||||||
|
<td>Login</td>
|
||||||
|
<td>1. 비밀번호를 입력한다.</td>
|
||||||
|
<td>admin</td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>24</td>
|
||||||
|
<td>Localization</td>
|
||||||
|
<td>1. 다른언어를 지원하는지 언어를 바꿔 확인한다</td>
|
||||||
|
<td></td>
|
||||||
|
<td>F</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
86
tools/preprop.ts
Normal file
86
tools/preprop.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { readAll } from "https://deno.land/std@0.143.0/streams/mod.ts";
|
||||||
|
import * as log from "https://deno.land/std@0.143.0/log/mod.ts";
|
||||||
|
import { WriterHandler } from "https://deno.land/std@0.143.0/log/handlers.ts";
|
||||||
|
import * as Eta from "https://deno.land/x/eta@v1.12.3/mod.ts";
|
||||||
|
import { Issue } from "./githubType.ts";
|
||||||
|
|
||||||
|
class StderrHandler extends WriterHandler {
|
||||||
|
protected _writer: Deno.Writer;
|
||||||
|
#encoder: TextEncoder;
|
||||||
|
constructor(levelName: log.LevelName, options: log.HandlerOptions = {}){
|
||||||
|
super(levelName, options);
|
||||||
|
this.#encoder = new TextEncoder();
|
||||||
|
this._writer = Deno.stderr;
|
||||||
|
}
|
||||||
|
log(msg: string): void {
|
||||||
|
const encoded = this.#encoder.encode(msg);
|
||||||
|
Deno.stderr.writeSync(encoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Book {
|
||||||
|
sections: Section[];
|
||||||
|
}
|
||||||
|
interface Section {
|
||||||
|
//chapter or separtor or PartTitle
|
||||||
|
Chapter: Chapter;
|
||||||
|
}
|
||||||
|
interface Chapter {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
/** section number */
|
||||||
|
number?: number[];
|
||||||
|
sub_items: Section[];
|
||||||
|
path?: string;
|
||||||
|
source_path?: string;
|
||||||
|
parent_names: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
await log.setup({
|
||||||
|
handlers: {
|
||||||
|
console: new StderrHandler("INFO", {
|
||||||
|
formatter: "{levelName} {msg}",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
loggers: {
|
||||||
|
default: {
|
||||||
|
level: "INFO",
|
||||||
|
handlers: ["console"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
main(Deno.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIssues(){
|
||||||
|
const issue_path = "cache/issues.json";
|
||||||
|
const c = await Deno.readTextFile(issue_path);
|
||||||
|
const issues = JSON.parse(c) as Issue[];
|
||||||
|
issues.sort((a, b) => a.number - b.number);
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(args: string[]) {
|
||||||
|
if (args.length > 1) {
|
||||||
|
//log.info(`args: ${JSON.stringify(args)}`);
|
||||||
|
if (args[0] === "supports") {
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const issues = await getIssues();
|
||||||
|
|
||||||
|
log.info(`start`);
|
||||||
|
const data = await readAll(Deno.stdin);
|
||||||
|
const jsonText = new TextDecoder().decode(data);
|
||||||
|
await Deno.writeTextFile("log.json", jsonText);
|
||||||
|
const [context, book] = JSON.parse(jsonText) as [any, Book];
|
||||||
|
book.sections.forEach(x=>{
|
||||||
|
x.Chapter.content = Eta.render(x.Chapter.content, {
|
||||||
|
issues: issues
|
||||||
|
}) as string;
|
||||||
|
})
|
||||||
|
//Deno.stderr.writeSync(new TextEncoder().encode(`context: ${JSON.stringify(context)}\n`));
|
||||||
|
console.log(JSON.stringify(book));
|
||||||
|
}
|
@ -1,137 +0,0 @@
|
|||||||
|
|
||||||
import { Issue } from "./githubType.ts";
|
|
||||||
import { copy } from "https://deno.land/std@0.136.0/fs/mod.ts";
|
|
||||||
import { readAll } from "https://deno.land/std@0.135.0/streams/mod.ts"
|
|
||||||
import { parse as argParse } from "https://deno.land/std@0.135.0/flags/mod.ts";
|
|
||||||
import {
|
|
||||||
normalize, join as pathJoin, fromFileUrl, parse as parsePath
|
|
||||||
, relative
|
|
||||||
} from "https://deno.land/std@0.135.0/path/mod.ts";
|
|
||||||
import * as Eta from "https://deno.land/x/eta@v1.12.3/mod.ts";
|
|
||||||
import { createReactive } from "./reactivity.ts";
|
|
||||||
|
|
||||||
async function readContent(path?: string): Promise<string> {
|
|
||||||
let content = "[]";
|
|
||||||
if (path) {
|
|
||||||
content = await Deno.readTextFile(path);
|
|
||||||
}
|
|
||||||
else if (!Deno.isatty(Deno.stdin.rid)) {
|
|
||||||
const decoder = new TextDecoder(undefined, { ignoreBOM: true });
|
|
||||||
const buf = await readAll(Deno.stdin);
|
|
||||||
content = decoder.decode(buf);
|
|
||||||
}
|
|
||||||
else throw new Error("No input provided. path or stdin.");
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
type printDocParam = {
|
|
||||||
target: string,
|
|
||||||
data: {
|
|
||||||
issues: Issue[]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function printDoc(param: printDocParam, option?: {
|
|
||||||
outDir?: string
|
|
||||||
}) {
|
|
||||||
option = option ?? {};
|
|
||||||
const { target, data } = param;
|
|
||||||
const { outDir } = option;
|
|
||||||
|
|
||||||
let print: string = "";
|
|
||||||
print = await Eta.renderFile(target, data) as string;
|
|
||||||
if (outDir) {
|
|
||||||
const outPath = pathJoin(outDir, target);
|
|
||||||
await Deno.mkdir(pathJoin(outDir), { recursive: true });
|
|
||||||
await Deno.writeTextFile(outPath, print);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(print);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const parsedArg = argParse(Deno.args);
|
|
||||||
const { issue_path, outDirArg, w, watch } = parsedArg;
|
|
||||||
const watchMode = w || watch;
|
|
||||||
if (typeof issue_path !== "undefined" && typeof issue_path !== "string") {
|
|
||||||
console.log("Please provide a path to the json file.");
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
if (typeof outDirArg !== "undefined" && typeof outDirArg !== "string") {
|
|
||||||
console.log("Please provide a path to the output file.");
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
const outDir = (outDirArg ?? "build");
|
|
||||||
if (typeof watchMode !== "undefined" && typeof watchMode !== "boolean") {
|
|
||||||
console.log("Please provide a boolean value for w.");
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
if (watchMode && typeof issue_path === "undefined") {
|
|
||||||
console.log("Could not set watch mode without a path.");
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(import.meta.url)
|
|
||||||
url.pathname = normalize(pathJoin(url.pathname, "..", "template"));
|
|
||||||
const viewPath = fromFileUrl(url);
|
|
||||||
Eta.configure({
|
|
||||||
views: viewPath,
|
|
||||||
"view cache": false,
|
|
||||||
});
|
|
||||||
const issuesR = await createReactive(async () => {
|
|
||||||
const c = await readContent(issue_path);
|
|
||||||
const issues = JSON.parse(c) as Issue[];
|
|
||||||
issues.sort((a, b) => a.number - b.number);
|
|
||||||
return issues;
|
|
||||||
});
|
|
||||||
|
|
||||||
const targets = ["SUMMARY.md", "overall.md", "specific.md", "intro.md", "support.md", "architecture.md"];
|
|
||||||
|
|
||||||
const targetsR = await Promise.all(targets.map(async (t) => {
|
|
||||||
return await createReactive(async () => {
|
|
||||||
await printDoc({
|
|
||||||
target: t, data: {
|
|
||||||
issues: issuesR.value
|
|
||||||
}
|
|
||||||
}, { outDir: outDir });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
));
|
|
||||||
issuesR.wireTo(...targetsR);
|
|
||||||
const copyOp = await createReactive(async () => {
|
|
||||||
const files = [...Deno.readDirSync(viewPath)].map(x => x.name).filter(x => !x.endsWith(".md"));
|
|
||||||
const op = files.map(x => copy(pathJoin(viewPath, x), pathJoin(outDir, x), { overwrite: true }));
|
|
||||||
await Promise.all(op);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (watchMode) {
|
|
||||||
const watcher = Deno.watchFs([viewPath, issue_path as string]);
|
|
||||||
for await (const event of watcher) {
|
|
||||||
if (event.kind === "modify") {
|
|
||||||
Deno.stdout.write(
|
|
||||||
new TextEncoder().encode("\x1b[2J\x1b[0f"),
|
|
||||||
);
|
|
||||||
console.log(`reloading ${event.paths.join(", ")}`);
|
|
||||||
for (const path of event.paths) {
|
|
||||||
const p = parsePath(path);
|
|
||||||
if (p.dir === viewPath) {
|
|
||||||
if (p.ext === ".md") {
|
|
||||||
targetsR[targets.indexOf(p.base)].update();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
copyOp.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (p.base === "issues.json") {
|
|
||||||
await issuesR.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
|
||||||
main();
|
|
||||||
}
|
|
318
tools/printIssue.ts
Normal file
318
tools/printIssue.ts
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
type Testcase = {
|
||||||
|
id: number,
|
||||||
|
subId: number | null,
|
||||||
|
content: string,
|
||||||
|
procedure: string,
|
||||||
|
testData: string| null,
|
||||||
|
expected: string,
|
||||||
|
actual: string,
|
||||||
|
pass: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const testcase: Testcase[] = [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Focus/Unfocus",
|
||||||
|
"procedure": "1. 청크를 클릭한다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"subId": null,
|
||||||
|
"content": "remove",
|
||||||
|
"procedure": "1. 청크를 삭제하는 버튼을 클릭한다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"subId": 1,
|
||||||
|
"content": "render - markdown",
|
||||||
|
"procedure": "1. 마크다운 청크 렌더링을 확인한다.",
|
||||||
|
"testData": " # 제목 ",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"subId": 2,
|
||||||
|
"content": "render - latex",
|
||||||
|
"procedure": "1. LaTex 청크 렌더링을 확인한다.",
|
||||||
|
"testData": " sum^n_{n=0}n = \\frac{n(n+1)}2$$ ",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"subId": 3,
|
||||||
|
"content": "render - link",
|
||||||
|
"procedure": "1. Image 청크 렌더링을 확인한다.",
|
||||||
|
"testData": " http://picsum.photos/200/300 ",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"subId": null,
|
||||||
|
"content": "previews",
|
||||||
|
"procedure": "1. Katex 청크의 미리보기를 본다.",
|
||||||
|
"testData": " sum^n_{n=0}n = \\frac{n(n+1)}2$$ ",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"subId": null,
|
||||||
|
"content": "autocomplete",
|
||||||
|
"procedure": "1. 자동완성을 시험한다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"subId": null,
|
||||||
|
"content": "swap positions",
|
||||||
|
"procedure": "1. 청크의 위치를 바꾼다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"subId": 1,
|
||||||
|
"content": "edit",
|
||||||
|
"procedure": "1. 청크를 수정한다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"subId": 2,
|
||||||
|
"content": "edit chunk conflict",
|
||||||
|
"procedure": "1. 청크를 수정모드에 들어간다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"subId": null,
|
||||||
|
"content": "view Chunk",
|
||||||
|
"procedure": "1. 문서를 열어 청크가 렌더링되는지 본다.",
|
||||||
|
"testData": "test.syd",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"subId": null,
|
||||||
|
"content": "add/delete tag",
|
||||||
|
"procedure": "1. 문서에 태그를 추가한다.<br>2. 문서에 태그를 삭제한다.",
|
||||||
|
"testData": "A",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Drag And Drop Upload,",
|
||||||
|
"procedure": "1. 텍스트를 드래그한다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"subId": null,
|
||||||
|
"content": "create/delete/rename file",
|
||||||
|
"procedure": "1. 파일을 만든다.",
|
||||||
|
"testData": "test.txt",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"subId": null,
|
||||||
|
"content": "upload/download files",
|
||||||
|
"procedure": "1. 파일을 업로드한다.",
|
||||||
|
"testData": "test.txt",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"subId": null,
|
||||||
|
"content": "export document",
|
||||||
|
"procedure": "1. export 버튼을 누른다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"subId": null,
|
||||||
|
"content": "render",
|
||||||
|
"procedure": "1. 스태시가 그려지는지 확인한다",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"subId": null,
|
||||||
|
"content": "add",
|
||||||
|
"procedure": "1. 청크를 추가한다",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"subId": null,
|
||||||
|
"content": "remove",
|
||||||
|
"procedure": "1. 청크를 삭제한다",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Drag and Drop to Document",
|
||||||
|
"procedure": "1. 청크로부터 문서로 청크를 옮긴다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Login",
|
||||||
|
"procedure": "1. 비밀번호를 입력한다.",
|
||||||
|
"testData": "admin",
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Localization",
|
||||||
|
"procedure": "1. 다른언어를 지원하는지 언어를 바꿔 확인한다",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Document Search",
|
||||||
|
"procedure": "1. 검색해본다.",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 85,
|
||||||
|
"subId": null,
|
||||||
|
"content": "Permission",
|
||||||
|
"procedure": "1. 각각의 기능들에 권한없이 시도한다",
|
||||||
|
"testData": null,
|
||||||
|
"expected": "",
|
||||||
|
"actual": "",
|
||||||
|
"pass": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
import {Issue} from "./githubType.ts"
|
||||||
|
|
||||||
|
async function readContent(path?: string): Promise<string> {
|
||||||
|
let content = "[]";
|
||||||
|
if (path) {
|
||||||
|
content = await Deno.readTextFile(path);
|
||||||
|
}
|
||||||
|
else throw new Error("No input provided. path or stdin.");
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await readContent("../build/issues.json")
|
||||||
|
|
||||||
|
const issues = JSON.parse(data) as Issue[]
|
||||||
|
const table = new Map<string, Issue[]>();
|
||||||
|
issues.forEach((x)=>{
|
||||||
|
const category = x.title.split(":")[0];
|
||||||
|
if(!category) return;
|
||||||
|
let c = table.get(category)
|
||||||
|
if(!c){
|
||||||
|
c = [];
|
||||||
|
table.set(category,c);
|
||||||
|
}
|
||||||
|
c.push(x);
|
||||||
|
})
|
||||||
|
|
||||||
|
const keys = Array.from(table.keys());
|
||||||
|
|
||||||
|
|
||||||
|
keys.forEach(x=>{
|
||||||
|
console.log(`\n### ${x}\n`);
|
||||||
|
const issues = table.get(x);
|
||||||
|
console.log("<table>");
|
||||||
|
console.log("<thead>");
|
||||||
|
console.log("<tr>");
|
||||||
|
//console.log("<th>Category</th>");
|
||||||
|
console.log("<th>ID</th>");
|
||||||
|
console.log("<th>Content</th>");
|
||||||
|
console.log("<th>Procedure</th>");
|
||||||
|
console.log("<th>Test Data</th>");
|
||||||
|
console.log("<th>P/F</th>");
|
||||||
|
console.log("</tr>");
|
||||||
|
console.log("</thead>");
|
||||||
|
console.log("<tbody>");
|
||||||
|
const ts = issues!.map(x=> testcase.filter(y=>y.id==x.number)).flat() as Testcase[];
|
||||||
|
|
||||||
|
if(ts?.length == 0) return;
|
||||||
|
|
||||||
|
//console.log(`<tr><th rowspan="${ts?.length}">${x}</th>`);
|
||||||
|
|
||||||
|
ts.forEach((y,i)=>{
|
||||||
|
//if(i>0)
|
||||||
|
console.log("<tr>");
|
||||||
|
const id = y.subId ? `${y.id}-${y.subId}` : y.id;
|
||||||
|
console.log(`<td>${id}</td>`);
|
||||||
|
console.log(`<td>${y.content}</td>`);
|
||||||
|
console.log(`<td>${y.procedure}</td>`);
|
||||||
|
console.log(`<td>${y.testData ?? ""}</td>`);
|
||||||
|
console.log(`<td>${y.pass ? "P" : "F"}</td>`);
|
||||||
|
console.log("</tr>");
|
||||||
|
})
|
||||||
|
console.log("</tbody>");
|
||||||
|
console.log("</table>");
|
||||||
|
})
|
@ -1,28 +0,0 @@
|
|||||||
interface Reactive<T>{
|
|
||||||
readonly value: T;
|
|
||||||
update: () => Promise<void>;
|
|
||||||
wireTo(...r: Reactive<unknown>[]): void;
|
|
||||||
unwireTo(r: Reactive<unknown>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createReactive<T>(fn: () => Promise<T>): Promise<Reactive<T>> {
|
|
||||||
let v = await fn();
|
|
||||||
let listeners: Reactive<unknown>[] = [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
get value() : T {
|
|
||||||
return v;
|
|
||||||
},
|
|
||||||
async update(){
|
|
||||||
const ret = await fn();
|
|
||||||
v = ret;
|
|
||||||
await Promise.all(listeners.map(o => o.update()));
|
|
||||||
},
|
|
||||||
wireTo(...r: Reactive<unknown>[]) {
|
|
||||||
listeners.push(...r);
|
|
||||||
},
|
|
||||||
unwireTo(r: Reactive<unknown>) {
|
|
||||||
listeners = listeners.filter(o => o !== r);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user