diff --git a/docs/complie_samples/ca.py b/docs/complie_samples/ca.py new file mode 100644 index 0000000..7ee1ea4 --- /dev/null +++ b/docs/complie_samples/ca.py @@ -0,0 +1,35 @@ +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-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) + 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 new file mode 100644 index 0000000..d3a8f47 --- /dev/null +++ b/docs/complie_samples/cga.py @@ -0,0 +1,126 @@ +import logging +from typing import List, Dict + +from util import dict2ins, link_node + + +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): + 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 + + 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 + + def __str__(self) -> str: + return str(self.__data) + + +@link_node +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 diff --git a/docs/complie_samples/design.md b/docs/complie_samples/design.md new file mode 100644 index 0000000..d7f0b01 --- /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[hunt] + _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/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 new file mode 100644 index 0000000..42726e3 --- /dev/null +++ b/docs/complie_samples/parser.py @@ -0,0 +1,106 @@ +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: List[TokenNode] = [] + self.__type = type + + 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(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: + return True + + if self.__type != other._CodeBlock__type: + return False + if self.code != other.code: + 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': '```'}, + {'text_line': ''} + ] + + def __init__(self): + super().__init__(CODE_TYPE.INFO) + + def __str__(self): + return str(self.tokens) + + @property + def tokens(self): + tokens = [] + tokens.extend(self.__header) + for node in self._block: + tokens.append({'text_line': node.val}) + tokens.extend(self.__footer) + return tokens + + +def hunt(nodes: List[TokenNode]) -> List[CodeBlock]: + aim = False + block = [] + for node in nodes: + cur = node.key + if cur == 'code_start': + aim = True + block.append(CodeBlock(node.val[3:])) + if aim: + block[-1].append(node) + if cur == 'code_end': + aim = False + return block + + +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 new file mode 100644 index 0000000..8e565e1 --- /dev/null +++ b/docs/complie_samples/test/test_ca.py @@ -0,0 +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(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 new file mode 100644 index 0000000..7aa88a9 --- /dev/null +++ b/docs/complie_samples/test/test_cga.py @@ -0,0 +1,109 @@ +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) 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_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 new file mode 100644 index 0000000..9ff2867 --- /dev/null +++ b/docs/complie_samples/test/test_parser.py @@ -0,0 +1,176 @@ +from unittest import TestCase + +import ca +import cga +import parser +from discern import lex +from util import write2file +import logging + + +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': '```'}, + {'text_line': 'just a comment'} + ] + + @classmethod + def setUpClass(cls) -> None: + logging.basicConfig(level=logging.INFO) + + def test_hunt(self): + node_list = cga.make_token_list(self.md) + prey = parser.hunt(node_list) + block = parser.CodeBlock(parser.CODE_TYPE.C) + 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_tokens(self): + + token_list = cga.make_token_list(self.md) + 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': '```'}, + {'text_line': ' return 0'}, + {'text_line': '}'}, + {'code_end': '```'}, + {'text_line': 'just a comment'}, + ] + expected_token_list = cga.make_token_list(expected_tokens) + + self.assertNotEqual(expected_token_list[0], token_list[0]) + + 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]) + + +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): + 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_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': '```'}, + {'text_line': ''}, + ] + + 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 new file mode 100644 index 0000000..993f108 --- /dev/null +++ b/docs/complie_samples/test/test_util.py @@ -0,0 +1,33 @@ +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, '')) + + 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 new file mode 100644 index 0000000..d01985e --- /dev/null +++ b/docs/complie_samples/util.py @@ -0,0 +1,58 @@ +import logging +import subprocess +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_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') + + +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 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 @@ -