impl preprocessor
This commit is contained in:
		
							parent
							
								
									c596a80518
								
							
						
					
					
						commit
						ca9658be6b
					
				
					 16 changed files with 750 additions and 187 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -2,5 +2,3 @@ book
 | 
			
		|||
.env
 | 
			
		||||
cache
 | 
			
		||||
.DS_Store
 | 
			
		||||
build
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,13 +2,15 @@
 | 
			
		|||
authors = ["monoid"]
 | 
			
		||||
language = "ko"
 | 
			
		||||
multilingual = false
 | 
			
		||||
src = "build"
 | 
			
		||||
src = "src"
 | 
			
		||||
title = "Software Requirement Specification"
 | 
			
		||||
 | 
			
		||||
[preprocessor]
 | 
			
		||||
 | 
			
		||||
[preprocessor.mermaid]
 | 
			
		||||
command = "mdbook-mermaid"
 | 
			
		||||
[preprocessor.etap]
 | 
			
		||||
command = "deno run -A --no-check tools/preprop.ts"
 | 
			
		||||
before = ["mermaid"]
 | 
			
		||||
 | 
			
		||||
[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]
 | 
			
		||||
    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):
 | 
			
		||||
    parser = argparse.ArgumentParser(description='Compiling the documentation', prog="cli.py build")
 | 
			
		||||
    parser.add_argument('-v', '--verbose', action='store_true', help='verbose mode')
 | 
			
		||||
| 
						 | 
				
			
			@ -38,10 +29,10 @@ def build(args):
 | 
			
		|||
    if args.verbose:
 | 
			
		||||
        print("build start")
 | 
			
		||||
 | 
			
		||||
    issuePath = os.path.join(args.outDir,"issues.json")
 | 
			
		||||
    if args.update_issues:
 | 
			
		||||
        os.makedirs("cache", exist_ok=True)
 | 
			
		||||
        issuePath = os.path.join("cache","issues.json")
 | 
			
		||||
        updateIssue(issuePath, args.verbose)
 | 
			
		||||
    printDocument(issuePath, args.outDir, args.watch, args.verbose)
 | 
			
		||||
    cmd = ["mdbook", "build"]
 | 
			
		||||
    cmdExecute(cmd, args.verbose)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,11 +48,7 @@ def serve(args):
 | 
			
		|||
    if args.verbose:
 | 
			
		||||
        print("serve start")
 | 
			
		||||
    cmd = ["mdbook", "serve", "--port", str(args.port)]
 | 
			
		||||
    p = subprocess.Popen(cmd)
 | 
			
		||||
    printDocument(issuePath, outDir, True, args.verbose)
 | 
			
		||||
    p.wait()
 | 
			
		||||
    if p.returncode != 0:
 | 
			
		||||
        sys.exit(p.returncode)
 | 
			
		||||
    cmdExecute(cmd, args.verbose, "serve:")
 | 
			
		||||
 | 
			
		||||
def help(_args):
 | 
			
		||||
    global commandList
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +67,7 @@ def issueUpdate(args):
 | 
			
		|||
def buildPdf(args):
 | 
			
		||||
    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('--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')
 | 
			
		||||
    args = parser.parse_args(args)
 | 
			
		||||
    if os.path.exists(args.outDir):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								log.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								log.json
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -4,4 +4,5 @@
 | 
			
		|||
- [Overall Description](./overall.md)
 | 
			
		||||
- [Specific Requirement](./specific.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…
	
	Add table
		
		Reference in a new issue