From 3089828aff8ae56e75d1eb3c9514bcf4de53b3dd Mon Sep 17 00:00:00 2001 From: Devecor Date: Sun, 28 Feb 2021 09:18:17 +0800 Subject: [PATCH 1/4] Revert "Merge pull request #1 from dashingsoft/master" From ff9be4949c7a8731e3f4c76f05a2fd255c5974c6 Mon Sep 17 00:00:00 2001 From: Devecor Date: Tue, 23 Mar 2021 19:26:07 +0800 Subject: [PATCH 2/4] Add marker and terminal for compile errors --- package.json | 4 ++- src/components/CodeManager.vue | 54 ++++++++++++++++++++++++++++++---- vue.config.js | 5 +++- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 0751309..1ada4c0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "gettext.js": "^0.9.0", "pixi.js": "^5.2.1", "vue": "^2.6.11", - "xhr2": "^0.2.0" + "xhr2": "^0.2.0", + "xterm": "^4.11.0", + "xterm-addon-fit": "^0.5.0" }, "devDependencies": { "@vue/cli-plugin-babel": "^4.2.0", diff --git a/src/components/CodeManager.vue b/src/components/CodeManager.vue index 1975f88..10d16ec 100644 --- a/src/components/CodeManager.vue +++ b/src/components/CodeManager.vue @@ -149,9 +149,14 @@ -
- -
+ + +
+ +
+
+
+
@@ -159,11 +164,16 @@ import { DIRTY, COMPILED, FAILURE, TIMEOUT, BUILDING } from '../definition.js' import connector from '../connector.js' import { _t } from '../plugins/gettext.js' +import { Range } from 'ace-builds' +import { Terminal } from 'xterm' +import { FitAddon } from 'xterm-addon-fit' +import 'xterm/css/xterm.css' export default { name: 'CodeManager', props: { courseId: Number, + terminal: Terminal, }, computed: { title: function () { @@ -186,6 +196,7 @@ export default { tempCourseworks: [], loadingCourse: false, cachedData: { 'pk_0': [] }, + termStyle: {height: '0px'}, } }, mounted() { @@ -448,7 +459,24 @@ export default { } } ) ) - this.$refs.editor.execCommand( 'goToNextError' ) + for( var index in ranges ) { + const _start = ranges[index].locations[0].caret + const _end = ranges[index].locations[0].finish + var _range = new Range(_start.line - 1, _start.column - 1, _end.line - 1, _end.column) + buf.session.addMarker(_range, "cb-gcc-error", "text", false) + + if (!this.terminal) { + this.terminal = new Terminal() + var fitAddon = new FitAddon(); + this.terminal.loadAddon(fitAddon); + } + + this.termStyle = {height: '200px'} + + this.terminal.open(document.getElementById('terminal')) + this.terminal.writeln('line ' + _start.line + ': ' + ranges[index].message) + fitAddon.fit() + } }, // @@ -597,7 +625,7 @@ export default { diff --git a/vue.config.js b/vue.config.js index 0a60341..d41360e 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,3 +1,6 @@ module.exports = { - publicPath: "" + publicPath: "", + configureWebpack: { + devtool: 'source-map' + } } From 2cf42736375076ed1b323ed23a2435a3dba5b6d9 Mon Sep 17 00:00:00 2001 From: Devecor Date: Sat, 12 Jun 2021 12:26:50 +0800 Subject: [PATCH 3/4] initialize doc generator --- docs/complie_samples/ca.py | 23 +++ docs/complie_samples/cga.py | 170 ++++++++++++++++++++ docs/complie_samples/design.md | 86 ++++++++++ docs/complie_samples/discern.py | 104 ++++++++++++ docs/complie_samples/parser.py | 79 +++++++++ docs/complie_samples/test/test_ca.py | 14 ++ docs/complie_samples/test/test_cga.py | 186 ++++++++++++++++++++++ docs/complie_samples/test/test_discern.py | 80 ++++++++++ docs/complie_samples/test/test_parser.py | 77 +++++++++ docs/complie_samples/test/test_util.py | 20 +++ docs/complie_samples/util.py | 50 ++++++ 11 files changed, 889 insertions(+) create mode 100644 docs/complie_samples/ca.py create mode 100644 docs/complie_samples/cga.py create mode 100644 docs/complie_samples/design.md create mode 100644 docs/complie_samples/discern.py create mode 100644 docs/complie_samples/parser.py create mode 100644 docs/complie_samples/test/test_ca.py create mode 100644 docs/complie_samples/test/test_cga.py create mode 100644 docs/complie_samples/test/test_discern.py create mode 100644 docs/complie_samples/test/test_parser.py create mode 100644 docs/complie_samples/test/test_util.py create mode 100644 docs/complie_samples/util.py diff --git a/docs/complie_samples/ca.py b/docs/complie_samples/ca.py new file mode 100644 index 0000000..ddb843b --- /dev/null +++ b/docs/complie_samples/ca.py @@ -0,0 +1,23 @@ +from subprocess import Popen, PIPE, STDOUT, TimeoutExpired + +import util + + +def gcc(code: str) -> str: + filename = util.write2file(code, '.c') + + cmd = ['gcc', '-g', + '-fdiagnostics-format=json', + '-fdiagnostics-parseable-fixits', + '-Werror=implicit-function-declaration', + filename, + '-o', filename + '.out', + ] + proc = Popen(" ".join(cmd), stdout=PIPE, stderr=STDOUT, shell=True) + try: + outs, errs = proc.communicate(timeout=30) + except TimeoutExpired: + proc.kill() + proc.communicate() + outs = 'The task is killed because of timeout' + return outs diff --git a/docs/complie_samples/cga.py b/docs/complie_samples/cga.py new file mode 100644 index 0000000..4e84318 --- /dev/null +++ b/docs/complie_samples/cga.py @@ -0,0 +1,170 @@ +from typing import List, Dict, Type +from util import dict2ins, link_node +import sys + + +class LexSta: + START = 1 + PROCESSING = START << 1 + END = START << 2 + + +class StaGraph(object): + + def __init__(self): + self.__stat__ = LexSta.START + + def evolve(self, sta): + next = self.__stat__ << 1 & 7 + + if not next: + next = LexSta.START + + if sta ^ next: + return False + else: + self.__stat__ = next + return True + + +class Buffer(object): + + def __init__(self, line: str): + assert line[-1] == '\n' + self.__buf_ = line + self.__nxt_ = 0 + self.__cur_ = 0 + + def next(self): + val = self.__buf_[self.__nxt_] + self.__cur_ = self.__nxt_ + self.__nxt_ += 1 + return val + + def current(self): + return self.__buf_[self.__cur_] + + def seek(self, offset): + return self.__buf_[self.__cur_ + offset] + + def seeks(self, offset): + if offset > 0: + return self.__buf_[self.__cur_ + 1: self.__cur_ + 1 + offset] + elif offset < 0: + return self.__buf_[self.__cur_ + offset: self.__cur_] + else: + return self.current() + + @property + def buf_str(self) -> str: + return self.__buf_ + + @buf_str.setter + def buf_str(self, value): + self.__buf_ = value + + +class TokenNode(object): + + def __init__(self, data: dict): + self.__data = data + self.__next = None + + def get(self, key: str): + return self.__data.get(key) + + @property + def next(self): + return self.__next + + @next.setter + def next(self, node): + self.__next = node + + @property + def key(self): + return list(self.__data.keys())[0] + + @property + def val(self): + return self.get(self.key) + + def __eq__(self, other): + if not other: + return False + + if self is other: + return True + + if self.key != other.key: + return False + + if self.val != other.val: + return False + + if self.next != other.next: + return False + + return True + + +@link_node +def make_token_list(tokens: List[Dict[str, str]]) -> List[Type[TokenNode]]: + nodelist = [] + for token in tokens: + d = {'data': token} + nodelist.append(dict2ins(d, TokenNode)) + return nodelist + + +if __name__ == '__main__': + i_file_path = sys.argv[0] + stub_data = '''# h1 text + ## h2 text + + ```c + int main() { + printf(a); + return 0; + } + ``` + + ## errorinfo + ``` + t1.c: In function ‘main’: + t1.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] + 2 | printf(a); + | ^~~~~~ + t1.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ + t1.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ + +++ |+#include + 1 | int main() { + t1.c:2:12: error: ‘a’ undeclared (first use in this function) + 2 | printf(a); + | ^ + t1.c:2:12: note: each undeclared identifier is reported only once for each function it appears in + ``` + + ## h2 text2 + + ```c + int main() { + printf("hello world"); + } + ``` + ## errorinfo + ``` + t2.c: In function ‘main’: + t2.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] + 2 | printf("hello world"); + | ^~~~~~ + t2.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ + t2.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ + +++ |+#include + 1 | int main() { + ``` + ''' + + with open(i_file_path, 'w', encoding='utf-8') as f: + f.write(stub_data) + f.close() diff --git a/docs/complie_samples/design.md b/docs/complie_samples/design.md new file mode 100644 index 0000000..3c1242d --- /dev/null +++ b/docs/complie_samples/design.md @@ -0,0 +1,86 @@ +# The technical architecture of cga + +## flow + +```mermaid +flowchart TB + + subgraph codeblock + _linked_list_node_4[code_start] + _linked_list_node_4 --> _linked_list_node_5[text_line] + _linked_list_node_5 --> _linked_list_node_6[code_end] + end + + subgraph tokenlist + _linked_list_header[h1] --> _linked_list_node_0[text_line] + _linked_list_node_0 --> _linked_list_node_1[text_line] + _linked_list_node_1 --> _linked_list_node_2[h2] + _linked_list_node_2 --> _linked_list_node_3[text_line] + _linked_list_node_3 --> codeblock + codeblock x-. insert .-x _linked_list_node_7[h2] + _linked_list_node_7 --> _linked_list_node_8[text_line] + end + + subgraph lexer + _lexer[lex] + end + + subgraph parser + _blocks_hunter[hunter] + _insert[insert] + end + + subgraph gener + _generate[todoc] + end + + subgraph cc-accessor + _gcca[ca] -- code --> _gcc[gcc] + _gcc --error info --> _gcca + + _gcca -- code --> _clang[clang] + _clang -- error info --> _gcca + + _gcca -- code --> _msvc[msvc] + _msvc -- error info --> _gcca + end + + subgraph errorblock + _error_info_header[h2] --> _error_info_node_1[text_line] + _error_info_node_1 --> _error_info_node_2[code_start] + _error_info_node_2 --> _error_info_node_3[text_line] + _error_info_node_3 --> _error_info_node_4[text_line] + _error_info_node_4 --> _error_info_node_5[code_end] + end + + + codeblock -- insert --> errorblock + errorblock -- insert --> _linked_list_node_7 + + codeblock ==> _blocks_hunter + _blocks_hunter == code ==> cc-accessor ==> errorblock + lexer ==> tokenlist + + tokenlist ==> gener + _generate ==> _md_file[markdown] + +``` + +``` ++ Public +- Private +# Protected +~ Package/Internal +``` + +```json +[ + {"h2": "##"}, + {"text_line": "this is h2"}, + {"code_start": "```c"}, + {"text_line": "int main() {"}, + {"text_line": " return 0;"}, + {"text_line": "}"}, + {"code_end": "```"} +] +``` diff --git a/docs/complie_samples/discern.py b/docs/complie_samples/discern.py new file mode 100644 index 0000000..d0f455b --- /dev/null +++ b/docs/complie_samples/discern.py @@ -0,0 +1,104 @@ +import re + +from cga import Buffer + +TITLE_LEVEL = ['h1', 'h2', 'h3', 'h4', 'h5'] + + +def ll_title(buf: Buffer): + regex = '^#+ ' + group = re.search(regex, buf.buf_str) + if group: + match = group.group(0) + level = TITLE_LEVEL[len(match) - 2] + sign = match[:-1] + return { + 'matched': True, + 'token': level, + 'sign': sign, + 'remain': buf.buf_str[len(match):] + } + return { + 'matched': False, + 'remain': buf.buf_str + } + + +def ll_text_line(buf: Buffer): + regex = '.+\n\Z' + group = re.search(regex, buf.buf_str) + if group: + match = group.group(0) + sign = match[:-1] + return { + 'matched': True, + 'token': 'text_line', + 'sign': sign, + 'remain': '' + } + return { + 'matched': False, + 'remain': buf.buf_str + } + + +def ll_code_start(buf: Buffer): + regex = '^```((c|C)(()|(\+{2})))\n\Z' + group = re.search(regex, buf.buf_str) + if group: + match = group.group(0) + sign = match[:-1] + return { + 'matched': True, + 'token': 'code_start', + 'sign': sign, + 'remain': '' + } + return { + 'matched': False, + 'remain': buf.buf_str + } + + +def ll_code_end(buf: Buffer): + regex = '^```\n' + group = re.search(regex, buf.buf_str) + if group: + match = group.group(0) + sign = match[:-1] + return { + 'matched': True, + 'token': 'code_end', + 'sign': sign, + 'remain': '' + } + return { + 'matched': False, + 'remain': buf.buf_str + } + + +PRIORITY_QUEUE = [ll_title, ll_code_start, ll_code_end, ll_text_line] + + +def lexer(buf: Buffer): + tokens = [] + for f in PRIORITY_QUEUE: + ll = f(buf) + buf.buf_str = ll.get('remain') + if ll.get('matched'): + tokens.append({ll.get('token'): ll.get('sign')}) + return tokens + + +def lex(file: str): + res = [] + with open(file, mode='r', encoding='utf-8') as f: + while True: + line = f.readline() + if not line: + break + buffer = Buffer(line) + res.extend(lexer(buffer)) + f.close() + return res diff --git a/docs/complie_samples/parser.py b/docs/complie_samples/parser.py new file mode 100644 index 0000000..b1d1e5b --- /dev/null +++ b/docs/complie_samples/parser.py @@ -0,0 +1,79 @@ +from typing import Dict, List, Type + +from cga import TokenNode, make_token_list + + +class CODE_TYPE(object): + C = 'c' + C_PLUS = 'c++' + INFO = '' + + +class CodeBlock(object): + + def __init__(self, type: CODE_TYPE) -> None: + self._block = [] + self.__type = type + + def append(self, line: str): + assert not line.endswith('\n') + self._block.append(line) + + @property + def code(self): + return '\n'.join(self._block) + '\n' + + def __eq__(self, other): + if self is other: + return True + + if self.__type != other._CodeBlock__type: + return False + if self.code != other.code: + return False + return True + + +class InfoBlock(CodeBlock): + __header = [ + {'h2': '##'}, + {'text_line': 'error info'}, + {'code_start': '```'} + ] + __footer = [{'code_end': '```'}] + + def __init__(self): + super().__init__(CODE_TYPE.INFO) + + @property + def tokens(self): + tokens = [] + for line in self._block: + tokens.append({'text_line': line}) + res = [] + res.extend(self.__header) + res.extend(tokens) + res.extend(self.__footer) + return res + + +def hunt(tokens: List[Dict]) -> List[CodeBlock]: + aim = False + block = [] + for token in tokens: + cur = list(token.keys())[0] + if cur == 'code_start': + aim = True + block.append(CodeBlock(token.get('code_start')[3:])) + continue + elif cur == 'code_end': + aim = False + continue + if aim: + block[-1].append(token.get('text_line')) + return block + + +def insert(node: Type[TokenNode], tokens: List[Dict[str, str]]) -> None: + nodelist = make_token_list(tokens) + node.next, nodelist[-1].next = nodelist[0], node.next diff --git a/docs/complie_samples/test/test_ca.py b/docs/complie_samples/test/test_ca.py new file mode 100644 index 0000000..9029404 --- /dev/null +++ b/docs/complie_samples/test/test_ca.py @@ -0,0 +1,14 @@ +from unittest import TestCase +import parser +import ca + + +class CATest(TestCase): + + def test_gcc(self): + block = parser.CodeBlock(parser.CODE_TYPE.C) + block.append('int main() {') + block.append(' return 0') + block.append('}') + error_info = ca.gcc(block.code) + self.assertIsNotNone(error_info) diff --git a/docs/complie_samples/test/test_cga.py b/docs/complie_samples/test/test_cga.py new file mode 100644 index 0000000..5a4d358 --- /dev/null +++ b/docs/complie_samples/test/test_cga.py @@ -0,0 +1,186 @@ +from unittest import TestCase +import cga +import util + + +class BufferTest(TestCase): + + def test_next(self): + _input = '# test\n' + buffer = cga.Buffer(_input) + for i in _input: + self.assertEqual(i, buffer.next()) + + def test_current(self): + _input = '# test\n' + buffer = cga.Buffer(_input) + for _ in _input: + expect = buffer.next() + self.assertEqual(expect, buffer.current()) + expect = 'ff' + self.assertNotEqual(expect, buffer.current()) + + def test_seek(self): + _input = '012345\n' + buffer = cga.Buffer(_input) + for i in range(3): + buffer.next() + self.assertEqual('2', buffer.seek(0)) + self.assertEqual('0', buffer.seek(-2)) + self.assertEqual('\n', buffer.seek(4)) + self.assertEqual('4', buffer.seek(2)) + self.assertEqual(buffer.current(), buffer.seek(0)) + self.assertEqual(buffer.seek(1), buffer.next()) + self.assertEqual(buffer.next(), buffer.seek(0)) + + def test_seeks(self): + _input = '012345\n' + buffer = cga.Buffer(_input) + for i in range(3): + buffer.next() + + self.assertEqual('01', buffer.seeks(-2)) + self.assertEqual('2', buffer.seeks(0)) + self.assertEqual('34', buffer.seeks(2)) + self.assertEqual('345\n', buffer.seeks(4)) + + +class StaGraphTest(TestCase): + + def test_evolve(self): + stat = cga.StaGraph() + self.assertTrue(stat.evolve(cga.LexSta.PROCESSING)) + self.assertTrue(stat.evolve(cga.LexSta.END)) + self.assertTrue(stat.evolve(cga.LexSta.START)) + self.assertTrue(stat.evolve(cga.LexSta.PROCESSING)) + self.assertTrue(stat.evolve(cga.LexSta.END)) + + self.assertFalse(stat.evolve(cga.LexSta.PROCESSING)) + + self.assertTrue(stat.evolve(cga.LexSta.START)) + self.assertFalse(stat.evolve(cga.LexSta.END)) + + self.assertTrue(stat.evolve(cga.LexSta.PROCESSING)) + self.assertFalse(stat.evolve(cga.LexSta.PROCESSING)) + + +class TokenNodeTest(TestCase): + + def test_next(self): + headnode = cga.TokenNode({'h1': 'the h1 title'}) + nextnode = cga.TokenNode({'text_line': 'here is text'}) + headnode.next = nextnode + + self.assertIs(headnode.next, nextnode) + + def test_get(self): + node = cga.TokenNode({'h1': 'the h1 title'}) + self.assertEqual('the h1 title', node.get('h1')) + + def test_key(self): + node = cga.TokenNode({'h1': 'the h1 title'}) + self.assertEqual('h1', node.key) + + def test_val(self): + node = cga.TokenNode({'h1': 'the h1 title'}) + self.assertEqual('the h1 title', node.val) + + +class TestMakeTokenList(TestCase): + + def test_make_token_list(self): + tokens = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'code_start': '```c'}, + {'text_line': 'int main() {'}, + {'text_line': ' return 0;'}, + {'text_line': '}'}, + {'code_end': '```'} + ] + + expected = [cga.TokenNode(i) for i in tokens] + + for i in range(len(expected) - 1): + expected[i].next = expected[i + 1] + + nodelist = cga.make_token_list(tokens) + + self.assertListEqual(expected, nodelist) + + +# class TestMain(TestCase): +# def test_main(self): +# md = '''# h1 text +# ## h2 text +# +# ```c +# int main() { +# printf(a); +# return 0; +# } +# ``` +# +# ## h2 text2 +# +# ```c +# int main() { +# printf("hello world"); +# } +# ``` +# ''' +# +# expected = '''# h1 text +# ## h2 text +# +# ```c +# int main() { +# printf(a); +# return 0; +# } +# ``` +# +# ## errorinfo +# ``` +# t1.c: In function ‘main’: +# t1.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] +# 2 | printf(a); +# | ^~~~~~ +# t1.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ +# t1.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ +# +++ |+#include +# 1 | int main() { +# t1.c:2:12: error: ‘a’ undeclared (first use in this function) +# 2 | printf(a); +# | ^ +# t1.c:2:12: note: each undeclared identifier is reported only once for each function it appears in +# ``` +# +# ## h2 text2 +# +# ```c +# int main() { +# printf("hello world"); +# } +# ``` +# ## errorinfo +# ``` +# t2.c: In function ‘main’: +# t2.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] +# 2 | printf("hello world"); +# | ^~~~~~ +# t2.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ +# t2.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ +# +++ |+#include +# 1 | int main() { +# ``` +# ''' +# +# file_path = util.write2file(md) +# +# cmd = f'python3 ../cga.py {file_path}' +# util.execute_cmd(cmd) +# with open(file_path, mode='r', encoding='utf-8') as f: +# res = f.read() +# f.close() +# self.assertEqual(expected, res) diff --git a/docs/complie_samples/test/test_discern.py b/docs/complie_samples/test/test_discern.py new file mode 100644 index 0000000..17b8459 --- /dev/null +++ b/docs/complie_samples/test/test_discern.py @@ -0,0 +1,80 @@ +from unittest import TestCase + +import cga +import discern as ds +from util import write2file + + +class Test(TestCase): + def test_ll_title_normal(self): + buffer = cga.Buffer('## this is h2\n') + act = ds.ll_title(buffer) + self.assertEqual(4, len(act)) + expect = { + 'matched': True, + 'token': 'h2', + 'sign': '##', + 'remain': 'this is h2\n' + } + self.assertDictEqual(expect, act) + + def test_ll_title_exception(self): + buffer = cga.Buffer(' ## this is h2\n') + act = ds.ll_title(buffer) + expect = { + 'matched': False, + 'remain': ' ## this is h2\n' + } + self.assertEqual(2, len(act)) + self.assertDictEqual(expect, act) + + def test_ll_text_line(self): + buffer = cga.Buffer('## this is h2\n') + act = ds.ll_text_line(buffer) + expect = { + 'matched': True, + 'token': 'text_line', + 'sign': '## this is h2', + 'remain': '' + } + self.assertDictEqual(expect, act) + + def test_lexer(self): + buffer = cga.Buffer('# this is h1\n') + act = ds.lexer(buffer) + expect = [{'h1': '#'}, {'text_line': 'this is h1'}] + self.assertListEqual(expect, act) + + def test_lex_with_headline(self): + md = '# this is h1\n' + file_path = write2file(md) + act = ds.lex(file_path) + expect = [{'h1': '#'}, {'text_line': 'this is h1'}] + self.assertListEqual(expect, act) + + def test_lex_with_text(self): + md = '## this is h2\n hello world!\nhappy tdd\n' + file_path = write2file(md) + act = ds.lex(file_path) + expect = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'text_line': ' hello world!'}, + {'text_line': 'happy tdd'} + ] + self.assertListEqual(expect, act) + + def test_lex_with_code_block(self): + md = '## this is h2\n```c\nint main() {\n return 0;\n}\n```\n' + file_path = write2file(md) + act = ds.lex(file_path) + expect = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'code_start': '```c'}, + {'text_line': 'int main() {'}, + {'text_line': ' return 0;'}, + {'text_line': '}'}, + {'code_end': '```'} + ] + self.assertListEqual(expect, act) diff --git a/docs/complie_samples/test/test_parser.py b/docs/complie_samples/test/test_parser.py new file mode 100644 index 0000000..4ce000d --- /dev/null +++ b/docs/complie_samples/test/test_parser.py @@ -0,0 +1,77 @@ +from unittest import TestCase + +import cga +import parser + + +class TestParser(TestCase): + + md = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'code_start': '```c'}, + {'text_line': 'int main() {'}, + {'text_line': ' return 0'}, + {'text_line': '}'}, + {'code_end': '```'} + ] + + def test_hunt(self): + prey = parser.hunt(self.md) + block = parser.CodeBlock(parser.CODE_TYPE.C) + block.append('int main() {') + block.append(' return 0') + block.append('}') + expect = [block] + for f, s in zip(expect, prey): + self.assertEqual(f, s) + + def test_insert(self): + + token_list = cga.make_token_list(self.md) + tokens = [ + {'h2': 'error info'}, + {'code_start': '```'}, + {'text_line': 'this is error info'}, + {'code_end': '```'} + ] + + expected_token_list = cga.make_token_list(self.md) + nodelist = cga.make_token_list(tokens) + footer = expected_token_list[3].next + expected_token_list[3].next = nodelist[0] + nodelist[-1].next = footer + + self.assertNotEqual(expected_token_list[0], token_list[0]) + + parser.insert(token_list[3], tokens) + + self.assertEqual(expected_token_list[0], token_list[0]) + self.assertEqual(expected_token_list, token_list) + + +class TestCodeBlock(TestCase): + + def test_code(self): + codeblock = parser.CodeBlock(parser.CODE_TYPE.C) + codeblock.append('line1') + codeblock.append('line2') + self.assertEqual('line1\nline2\n', codeblock.code) + + +class TestInfoBlock(TestCase): + + def test_tokens(self): + infoblock = parser.InfoBlock() + infoblock.append('line1') + infoblock.append('line2') + tokens = [ + {'h2': '##'}, + {'text_line': 'error info'}, + {'code_start': '```'}, + {'text_line': 'line1'}, + {'text_line': 'line2'}, + {'code_end': '```'} + ] + + self.assertListEqual(tokens, infoblock.tokens) diff --git a/docs/complie_samples/test/test_util.py b/docs/complie_samples/test/test_util.py new file mode 100644 index 0000000..534ef3d --- /dev/null +++ b/docs/complie_samples/test/test_util.py @@ -0,0 +1,20 @@ +from unittest import TestCase +import util + + +class TestUtil(TestCase): + + def test_dict2ins(self): + + class T(object): + def __init__(self, arg, flag=False): + self.__arg = arg + self.__flag = flag + + d = {'arg': 'arg_value', 'flag': True} + + ins = T(d.get('arg'), flag=True) + + self.assertEqual(ins._T__arg, util.dict2ins(d, T)._T__arg) + self.assertEqual(ins._T__flag, util.dict2ins(d, T)._T__flag) + self.assertRaises(ValueError, util.dict2ins, *(d, '')) diff --git a/docs/complie_samples/util.py b/docs/complie_samples/util.py new file mode 100644 index 0000000..03bfcea --- /dev/null +++ b/docs/complie_samples/util.py @@ -0,0 +1,50 @@ +import logging +from time import time +import os +from typing import TypeVar +import inspect + +logging.basicConfig(level=logging.INFO) + + +def write2file(text: str, ext='.md') -> str: + t_dir = 'build' + target = str(int(time())) + ext + if not os.path.exists(t_dir): + os.mkdir(t_dir) + file_path = t_dir + '/' + target + with open(file_path, mode='w', encoding='utf-8') as tf: + tf.write(text) + tf.close() + return file_path + + +def execute_cmd(cmd: str): + logging.info(cmd) + os.system(cmd) + + +T = TypeVar('AnyClass') + + +def dict2ins(d: dict, cls: T) -> T: + if inspect.isclass(cls): + constructor = getattr(cls, '__init__') + params = inspect.signature(constructor).parameters + values = [] + for name, param in params.items(): + if d.get(name): + values.append(d.get(name)) + + return cls(*values) + else: + raise ValueError('the argument "cls" must be a class') + + +def link_node(func): + def link(tokens): + nodelist = func(tokens) + for i in range(len(nodelist) - 1): + nodelist[i].next = nodelist[i + 1] + return nodelist + return link From 6538d246a46f9bd84081b1aad46a25563a78c9e7 Mon Sep 17 00:00:00 2001 From: Devecor Date: Sat, 3 Jul 2021 15:54:13 +0800 Subject: [PATCH 4/4] complete main logic of doc generator --- docs/complie_samples/ca.py | 14 ++- docs/complie_samples/cga.py | 68 ++-------- docs/complie_samples/design.md | 2 +- docs/complie_samples/gener.py | 25 ++++ docs/complie_samples/main.py | 67 ++++++++++ docs/complie_samples/parser.py | 81 ++++++++---- docs/complie_samples/test/__init__.py | 0 docs/complie_samples/test/test.md | 19 +++ docs/complie_samples/test/test_ca.py | 16 ++- docs/complie_samples/test/test_cga.py | 77 ------------ docs/complie_samples/test/test_gener.py | 34 +++++ docs/complie_samples/test/test_main.py | 21 ++++ docs/complie_samples/test/test_parser.py | 151 +++++++++++++++++++---- docs/complie_samples/test/test_util.py | 15 ++- docs/complie_samples/util.py | 10 +- 15 files changed, 407 insertions(+), 193 deletions(-) create mode 100644 docs/complie_samples/gener.py create mode 100644 docs/complie_samples/main.py create mode 100644 docs/complie_samples/test/__init__.py create mode 100644 docs/complie_samples/test/test.md create mode 100644 docs/complie_samples/test/test_gener.py create mode 100644 docs/complie_samples/test/test_main.py diff --git a/docs/complie_samples/ca.py b/docs/complie_samples/ca.py index ddb843b..7ee1ea4 100644 --- a/docs/complie_samples/ca.py +++ b/docs/complie_samples/ca.py @@ -1,13 +1,15 @@ from subprocess import Popen, PIPE, STDOUT, TimeoutExpired import util +from cga import TokenNode +from parser import InfoBlock, CodeBlock def gcc(code: str) -> str: filename = util.write2file(code, '.c') cmd = ['gcc', '-g', - '-fdiagnostics-format=json', + # '-fdiagnostics-format=json', '-fdiagnostics-parseable-fixits', '-Werror=implicit-function-declaration', filename, @@ -16,8 +18,18 @@ def gcc(code: str) -> str: proc = Popen(" ".join(cmd), stdout=PIPE, stderr=STDOUT, shell=True) try: outs, errs = proc.communicate(timeout=30) + assert isinstance(outs, bytes) + outs = outs.decode('utf-8') except TimeoutExpired: proc.kill() proc.communicate() outs = 'The task is killed because of timeout' return outs + + +def build_block(error_info: str, block_type=InfoBlock) -> CodeBlock: + assert isinstance(error_info, str) + block = block_type() + for line in error_info.split('\n'): + block.append(TokenNode({'text_line': line})) + return block diff --git a/docs/complie_samples/cga.py b/docs/complie_samples/cga.py index 4e84318..d3a8f47 100644 --- a/docs/complie_samples/cga.py +++ b/docs/complie_samples/cga.py @@ -1,6 +1,7 @@ -from typing import List, Dict, Type +import logging +from typing import List, Dict + from util import dict2ins, link_node -import sys class LexSta: @@ -90,6 +91,11 @@ def val(self): return self.get(self.key) def __eq__(self, other): + logging.info('self key:' + self.key) + logging.info('other key:' + other.key) + logging.info('self val:' + self.val) + logging.info('other val:' + other.val) + if not other: return False @@ -107,64 +113,14 @@ def __eq__(self, other): return True + def __str__(self) -> str: + return str(self.__data) + @link_node -def make_token_list(tokens: List[Dict[str, str]]) -> List[Type[TokenNode]]: +def make_token_list(tokens: List[Dict[str, str]]) -> List[TokenNode]: nodelist = [] for token in tokens: d = {'data': token} nodelist.append(dict2ins(d, TokenNode)) return nodelist - - -if __name__ == '__main__': - i_file_path = sys.argv[0] - stub_data = '''# h1 text - ## h2 text - - ```c - int main() { - printf(a); - return 0; - } - ``` - - ## errorinfo - ``` - t1.c: In function ‘main’: - t1.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] - 2 | printf(a); - | ^~~~~~ - t1.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ - t1.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ - +++ |+#include - 1 | int main() { - t1.c:2:12: error: ‘a’ undeclared (first use in this function) - 2 | printf(a); - | ^ - t1.c:2:12: note: each undeclared identifier is reported only once for each function it appears in - ``` - - ## h2 text2 - - ```c - int main() { - printf("hello world"); - } - ``` - ## errorinfo - ``` - t2.c: In function ‘main’: - t2.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] - 2 | printf("hello world"); - | ^~~~~~ - t2.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ - t2.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ - +++ |+#include - 1 | int main() { - ``` - ''' - - with open(i_file_path, 'w', encoding='utf-8') as f: - f.write(stub_data) - f.close() diff --git a/docs/complie_samples/design.md b/docs/complie_samples/design.md index 3c1242d..d7f0b01 100644 --- a/docs/complie_samples/design.md +++ b/docs/complie_samples/design.md @@ -26,7 +26,7 @@ flowchart TB end subgraph parser - _blocks_hunter[hunter] + _blocks_hunter[hunt] _insert[insert] end diff --git a/docs/complie_samples/gener.py b/docs/complie_samples/gener.py new file mode 100644 index 0000000..b05fd39 --- /dev/null +++ b/docs/complie_samples/gener.py @@ -0,0 +1,25 @@ +from typing import List + +import discern +from cga import TokenNode + + +def convert_to_inlines(nodelist: List[TokenNode]) -> List[str]: + inlines = [] + node = nodelist[0] + while node: + if node.key in discern.TITLE_LEVEL: + inlines.append(node.val + ' ' + node.next.val) + node = node.next.next + continue + inlines.append(node.val) + node = node.next + return inlines + + +def write_to_md(nodelist: List[TokenNode], target: str) -> None: + inlines = convert_to_inlines(nodelist) + with open(target, mode='w', encoding='utf-8') as f: + for line in inlines: + f.write(line + '\n') + f.close() diff --git a/docs/complie_samples/main.py b/docs/complie_samples/main.py new file mode 100644 index 0000000..8c0037a --- /dev/null +++ b/docs/complie_samples/main.py @@ -0,0 +1,67 @@ +import logging +import sys +import os +import argparse +from typing import Tuple + +import ca +import parser +from cga import make_token_list +import discern as ds +import gener + + +def parse_args(): + argparser = argparse.ArgumentParser( + description="write your c/c++ program by markdown, then cga builds them.") + argparser.add_argument("-v", "--version", action="store_const", + const=True, default=False, help="查看当前版本") + argparser.add_argument("markdown", help="input file(it must be markdown file)") + argparser.add_argument("-o", "--output", default='.', help="output path") + + if len(sys.argv) <= 1: + return argparser.parse_args(["-h"]) + return argparser.parse_args(sys.argv[1:]) + + +def execute_cga(i_file_path: str, o_file_path: str) -> None: + tokens = ds.lex(i_file_path) + node_list = make_token_list(tokens) + all_code_block = parser.hunt(node_list) + + for code_block in all_code_block: + info_block = ca.build_block(ca.gcc(code_block.code)) + parser.insert(code_block.end, info_block) + + gener.write_to_md(node_list, o_file_path) + + +def preprocess(i_file_path: str, o_file_path: str) -> Tuple[str, str]: + if not os.path.exists(i_file_path): + logging.error('no such file: ' + i_file_path) + return + if o_file_path and o_file_path != '.': + if os.path.exists(o_file_path): + logging.error('file has been existed: ' + o_file_path) + return + else: + _dir = os.path.dirname(o_file_path) + if not os.path.exists(_dir): + logging.error('no such directory: ' + _dir) + else: + i_file_name = i_file_path.split('/')[-1] + o_file_name = i_file_name.split('.')[0] + '.cc.' + i_file_name.split('.')[-1] + o_file_path = os.path.dirname(i_file_path) + '/' + o_file_name + return i_file_path, o_file_path + + +def main(): + args = parse_args() + io_path = preprocess(args.markdown, args.output) + if io_path: + f_in, f_out = io_path + execute_cga(f_in, f_out) + + +if __name__ == '__main__': + main() diff --git a/docs/complie_samples/parser.py b/docs/complie_samples/parser.py index b1d1e5b..42726e3 100644 --- a/docs/complie_samples/parser.py +++ b/docs/complie_samples/parser.py @@ -10,18 +10,35 @@ class CODE_TYPE(object): class CodeBlock(object): - + def __init__(self, type: CODE_TYPE) -> None: - self._block = [] + self._block: List[TokenNode] = [] self.__type = type - - def append(self, line: str): - assert not line.endswith('\n') - self._block.append(line) + + def append(self, node: TokenNode): + if len(self._block): + self.end.next = node + self._block.append(node) + + def token_keys(self): + for i in self._block: + yield i.key + + def token_values(self): + for i in self._block: + yield i.val @property def code(self): - return '\n'.join(self._block) + '\n' + return '\n'.join(list(self.token_values())[1:-1]) + '\n' + + @property + def head(self): + return self._block[0] + + @property + def end(self): + return self._block[-1] def __eq__(self, other): if self is other: @@ -33,47 +50,57 @@ def __eq__(self, other): return False return True + def __str__(self) -> str: + return self.code + class InfoBlock(CodeBlock): __header = [ + {'text_line': ''}, {'h2': '##'}, {'text_line': 'error info'}, {'code_start': '```'} ] - __footer = [{'code_end': '```'}] + __footer = [ + {'code_end': '```'}, + {'text_line': ''} + ] def __init__(self): super().__init__(CODE_TYPE.INFO) + def __str__(self): + return str(self.tokens) + @property def tokens(self): tokens = [] - for line in self._block: - tokens.append({'text_line': line}) - res = [] - res.extend(self.__header) - res.extend(tokens) - res.extend(self.__footer) - return res + tokens.extend(self.__header) + for node in self._block: + tokens.append({'text_line': node.val}) + tokens.extend(self.__footer) + return tokens -def hunt(tokens: List[Dict]) -> List[CodeBlock]: +def hunt(nodes: List[TokenNode]) -> List[CodeBlock]: aim = False block = [] - for token in tokens: - cur = list(token.keys())[0] + for node in nodes: + cur = node.key if cur == 'code_start': aim = True - block.append(CodeBlock(token.get('code_start')[3:])) - continue - elif cur == 'code_end': - aim = False - continue + block.append(CodeBlock(node.val[3:])) if aim: - block[-1].append(token.get('text_line')) + block[-1].append(node) + if cur == 'code_end': + aim = False return block -def insert(node: Type[TokenNode], tokens: List[Dict[str, str]]) -> None: - nodelist = make_token_list(tokens) - node.next, nodelist[-1].next = nodelist[0], node.next +def insert_tokens(node: TokenNode, tokens: List[Dict[str, str]]) -> None: + nodes_for_inserting = make_token_list(tokens) + node.next, nodes_for_inserting[-1].next = nodes_for_inserting[0], node.next + + +def insert(node: TokenNode, block: InfoBlock) -> None: + insert_tokens(node, block.tokens) diff --git a/docs/complie_samples/test/__init__.py b/docs/complie_samples/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/complie_samples/test/test.md b/docs/complie_samples/test/test.md new file mode 100644 index 0000000..630aeca --- /dev/null +++ b/docs/complie_samples/test/test.md @@ -0,0 +1,19 @@ +# this is a generator + +## code block 1 + +```C +int main() { + printf("hello world%s", s); + return 0; +} +``` + +## code block 2 + +```C +int main() { + int i = 0; + i++; +} +``` diff --git a/docs/complie_samples/test/test_ca.py b/docs/complie_samples/test/test_ca.py index 9029404..8e565e1 100644 --- a/docs/complie_samples/test/test_ca.py +++ b/docs/complie_samples/test/test_ca.py @@ -1,14 +1,24 @@ from unittest import TestCase import parser import ca +from cga import TokenNode class CATest(TestCase): def test_gcc(self): block = parser.CodeBlock(parser.CODE_TYPE.C) - block.append('int main() {') - block.append(' return 0') - block.append('}') + block.append(TokenNode({'text_line': 'int main() {'})) + block.append(TokenNode({'text_line': ' return 0'})) + block.append(TokenNode({'text_line': '}'})) error_info = ca.gcc(block.code) self.assertIsNotNone(error_info) + + def test_build_block(self): + + error_info = 'line1\nline2\n\n' + info_block = parser.InfoBlock() + for line in error_info.split('\n'): + info_block.append(TokenNode({'text_line': line})) + + self.assertEqual(info_block, ca.build_block(error_info)) diff --git a/docs/complie_samples/test/test_cga.py b/docs/complie_samples/test/test_cga.py index 5a4d358..7aa88a9 100644 --- a/docs/complie_samples/test/test_cga.py +++ b/docs/complie_samples/test/test_cga.py @@ -107,80 +107,3 @@ def test_make_token_list(self): nodelist = cga.make_token_list(tokens) self.assertListEqual(expected, nodelist) - - -# class TestMain(TestCase): -# def test_main(self): -# md = '''# h1 text -# ## h2 text -# -# ```c -# int main() { -# printf(a); -# return 0; -# } -# ``` -# -# ## h2 text2 -# -# ```c -# int main() { -# printf("hello world"); -# } -# ``` -# ''' -# -# expected = '''# h1 text -# ## h2 text -# -# ```c -# int main() { -# printf(a); -# return 0; -# } -# ``` -# -# ## errorinfo -# ``` -# t1.c: In function ‘main’: -# t1.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] -# 2 | printf(a); -# | ^~~~~~ -# t1.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ -# t1.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ -# +++ |+#include -# 1 | int main() { -# t1.c:2:12: error: ‘a’ undeclared (first use in this function) -# 2 | printf(a); -# | ^ -# t1.c:2:12: note: each undeclared identifier is reported only once for each function it appears in -# ``` -# -# ## h2 text2 -# -# ```c -# int main() { -# printf("hello world"); -# } -# ``` -# ## errorinfo -# ``` -# t2.c: In function ‘main’: -# t2.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] -# 2 | printf("hello world"); -# | ^~~~~~ -# t2.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ -# t2.c:1:1: note: include ‘’ or provide a declaration of ‘printf’ -# +++ |+#include -# 1 | int main() { -# ``` -# ''' -# -# file_path = util.write2file(md) -# -# cmd = f'python3 ../cga.py {file_path}' -# util.execute_cmd(cmd) -# with open(file_path, mode='r', encoding='utf-8') as f: -# res = f.read() -# f.close() -# self.assertEqual(expected, res) diff --git a/docs/complie_samples/test/test_gener.py b/docs/complie_samples/test/test_gener.py new file mode 100644 index 0000000..5258d9c --- /dev/null +++ b/docs/complie_samples/test/test_gener.py @@ -0,0 +1,34 @@ +from unittest import TestCase + +import cga +import gener +import util + + +class TestGener(TestCase): + md = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'code_start': '```c'}, + {'text_line': 'int main() {'}, + {'text_line': ' return 0'}, + {'text_line': '}'}, + {'code_end': '```'} + ] + + def test_write_to_md_without_inserted(self): + node_list = cga.make_token_list(self.md) + target = util.write2file('') + gener.write_to_md(node_list, target) + with open(target, mode='r', encoding='utf-8') as f: + res = f.read() + f.close() + expected = [ + '## this is h2', + '```c', + 'int main() {', + ' return 0', + '}', + '```' + ] + self.assertEqual('\n'.join(expected) + '\n', res) diff --git a/docs/complie_samples/test/test_main.py b/docs/complie_samples/test/test_main.py new file mode 100644 index 0000000..690e825 --- /dev/null +++ b/docs/complie_samples/test/test_main.py @@ -0,0 +1,21 @@ +from unittest import TestCase + +import util +import os + + +class TestMain(TestCase): + def test_main_without_args(self): + cmd = 'python3 ../main.py'.split(' ') + res = util.execute_in_shell(cmd) + self.assertTrue('[-h] [-v] [-o OUTPUT] markdown' in res) + + def test_main_with_only_input_file(self): + cmd = 'python3 ../main.py ./test.md'.split(' ') + res = util.execute_in_shell(cmd) + self.assertTrue(os.path.exists('test.cc.md')) + + def test_main_with_output_file(self): + cmd = 'python3 ../main.py ./test.md -o ./build/test-output.md'.split(' ') + res = util.execute_in_shell(cmd) + self.assertTrue(os.path.exists('./build/test-output.md')) diff --git a/docs/complie_samples/test/test_parser.py b/docs/complie_samples/test/test_parser.py index 4ce000d..9ff2867 100644 --- a/docs/complie_samples/test/test_parser.py +++ b/docs/complie_samples/test/test_parser.py @@ -1,7 +1,11 @@ from unittest import TestCase +import ca import cga import parser +from discern import lex +from util import write2file +import logging class TestParser(TestCase): @@ -13,65 +17,160 @@ class TestParser(TestCase): {'text_line': 'int main() {'}, {'text_line': ' return 0'}, {'text_line': '}'}, - {'code_end': '```'} + {'code_end': '```'}, + {'text_line': 'just a comment'} ] + @classmethod + def setUpClass(cls) -> None: + logging.basicConfig(level=logging.INFO) + def test_hunt(self): - prey = parser.hunt(self.md) + node_list = cga.make_token_list(self.md) + prey = parser.hunt(node_list) block = parser.CodeBlock(parser.CODE_TYPE.C) - block.append('int main() {') - block.append(' return 0') - block.append('}') + block.append(cga.TokenNode({'code_start': '```c'})) + block.append(cga.TokenNode({'text_line': 'int main() {'})) + block.append(cga.TokenNode({'text_line': ' return 0'})) + block.append(cga.TokenNode({'text_line': '}'})) + block.append(cga.TokenNode({'code_end': '```'})) expect = [block] for f, s in zip(expect, prey): self.assertEqual(f, s) - def test_insert(self): + def test_insert_tokens(self): token_list = cga.make_token_list(self.md) - tokens = [ + expected_tokens = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'code_start': '```c'}, + {'text_line': 'int main() {'}, {'h2': 'error info'}, {'code_start': '```'}, {'text_line': 'this is error info'}, - {'code_end': '```'} + {'code_end': '```'}, + {'text_line': ' return 0'}, + {'text_line': '}'}, + {'code_end': '```'}, + {'text_line': 'just a comment'}, ] - - expected_token_list = cga.make_token_list(self.md) - nodelist = cga.make_token_list(tokens) - footer = expected_token_list[3].next - expected_token_list[3].next = nodelist[0] - nodelist[-1].next = footer + expected_token_list = cga.make_token_list(expected_tokens) self.assertNotEqual(expected_token_list[0], token_list[0]) - parser.insert(token_list[3], tokens) + tokens_for_inserting = [ + {'h2': 'error info'}, + {'code_start': '```'}, + {'text_line': 'this is error info'}, + {'code_end': '```'}, + ] + parser.insert_tokens(token_list[3], tokens_for_inserting) + + self.assertEqual(expected_token_list[0], token_list[0]) + node = token_list[3] + for _ in tokens_for_inserting: + self.assertIsNotNone(node.next) + node = node.next + self.assertIsNone(token_list[-1].next) + + def test_insert(self): + expected_md = [ + {'h2': '##'}, + {'text_line': 'this is h2'}, + {'code_start': '```c'}, + {'text_line': 'int main() {'}, + {'text_line': ' return 0'}, + {'text_line': '}'}, + {'code_end': '```'}, + {'text_line': ''}, + {'h2': '##'}, + {'text_line': 'error info'}, + {'code_start': '```'}, + {'text_line': 'this is error info'}, + {'code_end': '```'}, + {'text_line': ''}, + {'text_line': 'just a comment'}, + ] + token_list = cga.make_token_list(self.md) + expected_token_list = cga.make_token_list(expected_md) + + # self.assertNotEqual(expected_token_list[0], token_list[0]) + + info_block = ca.build_block('this is error info') + parser.insert(token_list[6], info_block) + self.assertEqual(expected_token_list[6], token_list[6]) self.assertEqual(expected_token_list[0], token_list[0]) - self.assertEqual(expected_token_list, token_list) class TestCodeBlock(TestCase): + md = '''# h1 text +## h2 text + +```c +int main() { + printf(a); + return 0; +} +``` + +## h2 text2 + +```c +int main() { + printf("hello world"); +} +``` +''' def test_code(self): - codeblock = parser.CodeBlock(parser.CODE_TYPE.C) - codeblock.append('line1') - codeblock.append('line2') - self.assertEqual('line1\nline2\n', codeblock.code) + md = [ + '```c', + 'int main() {', + ' printf("hello!");', + 'return 0;', + '}', + '```' + ] + path = write2file('\n'.join(md) + '\n') + tokens = lex(path) + node_list = cga.make_token_list(tokens) + + code_block = parser.CodeBlock(parser.CODE_TYPE.C) + + for i in node_list: + code_block.append(i) + + self.assertEqual('int main() {\n printf("hello!");\nreturn 0;\n}\n', code_block.code) + + def test_head_and_end(self): + path = write2file(self.md) + tokens = lex(path) + node_list = cga.make_token_list(tokens) + code_block = parser.CodeBlock(parser.CODE_TYPE.C) + for i in node_list: + code_block.append(i) + self.assertEqual(node_list[0], code_block.head) + self.assertEqual(node_list[-1], code_block.end) class TestInfoBlock(TestCase): - def test_tokens(self): - infoblock = parser.InfoBlock() - infoblock.append('line1') - infoblock.append('line2') + def test_tokens_and_str(self): + info_block = parser.InfoBlock() + info_block.append(cga.TokenNode({'text_line': 'line1'})) + info_block.append(cga.TokenNode({'text_line': 'line2'})) tokens = [ + {'text_line': ''}, {'h2': '##'}, {'text_line': 'error info'}, {'code_start': '```'}, {'text_line': 'line1'}, {'text_line': 'line2'}, - {'code_end': '```'} + {'code_end': '```'}, + {'text_line': ''}, ] - self.assertListEqual(tokens, infoblock.tokens) + self.assertEqual(str(tokens), str(info_block)) + self.assertListEqual(tokens, info_block.tokens) diff --git a/docs/complie_samples/test/test_util.py b/docs/complie_samples/test/test_util.py index 534ef3d..993f108 100644 --- a/docs/complie_samples/test/test_util.py +++ b/docs/complie_samples/test/test_util.py @@ -5,7 +5,6 @@ class TestUtil(TestCase): def test_dict2ins(self): - class T(object): def __init__(self, arg, flag=False): self.__arg = arg @@ -18,3 +17,17 @@ def __init__(self, arg, flag=False): self.assertEqual(ins._T__arg, util.dict2ins(d, T)._T__arg) self.assertEqual(ins._T__flag, util.dict2ins(d, T)._T__flag) self.assertRaises(ValueError, util.dict2ins, *(d, '')) + + def test_execute_in_shell_with_ascii(self): + cmd = 'echo hello!' + res = util.execute_in_shell(cmd.split(' ')) + self.assertEqual('hello!\n', res) + + def test_execute_in_shell_with_non_ascii(self): + cmd = 'echo 你好!' + res = util.execute_in_shell(cmd.split(' ')) + self.assertEqual('你好!\n', res) + + def test_execute_in_shell_with_wrong(self): + cmd = 'ech hello!' + self.assertRaises(FileNotFoundError, util.execute_in_shell, cmd.split(' ')) diff --git a/docs/complie_samples/util.py b/docs/complie_samples/util.py index 03bfcea..d01985e 100644 --- a/docs/complie_samples/util.py +++ b/docs/complie_samples/util.py @@ -1,4 +1,5 @@ import logging +import subprocess from time import time import os from typing import TypeVar @@ -19,11 +20,18 @@ def write2file(text: str, ext='.md') -> str: return file_path -def execute_cmd(cmd: str): +def execute_in_sys(cmd: str) -> None: logging.info(cmd) os.system(cmd) +def execute_in_shell(cmd: list) -> str: + logging.info(cmd) + res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + logging.info(res.stdout) + return res.stdout + + T = TypeVar('AnyClass')