From 9375d5536c060eaa6c132f7533f8486abfd04074 Mon Sep 17 00:00:00 2001
From: Lan Hui <1348141770@qq.com>
Date: Wed, 14 Jul 2021 15:05:47 +0800
Subject: Upload Jin Xiongrong's work -- https://gitee.com/dragondove/storode;
 fix UnicodeDecodeError
---
 src/collision.py |  54 ++++++++
 src/storode.py   | 380 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 434 insertions(+)
 create mode 100644 src/collision.py
 create mode 100644 src/storode.py
(limited to 'src')
diff --git a/src/collision.py b/src/collision.py
new file mode 100644
index 0000000..5d87cf7
--- /dev/null
+++ b/src/collision.py
@@ -0,0 +1,54 @@
+import hashlib
+import random
+
+# collision tests for my identifier solution
+
+
+def get_identifier(s):
+    m2 = hashlib.md5()
+    m2.update(s.encode('utf-8'))
+    digest = m2.hexdigest()[-4:]
+    return digest
+
+
+def pick_random_text(lines, num):
+    length = len(text_lines)
+    s = ''
+    for i in range(num):
+        s += lines[random.randint(0, num)]
+
+    return s
+
+
+def get_text_lines_from_file(file_path):
+    lines = []
+    with open(file_path, encoding='utf8') as f:
+        for line in f.readlines():
+            if (line.strip() != ''):
+                lines.append(line)
+
+    return lines
+
+
+if __name__ == "__main__":
+    text_lines = get_text_lines_from_file('./sample/Wonderland.txt')
+    total_sample_count = 100
+    sample_size = 8
+    test_times = 100
+    collision_counts = []
+    for i in range(test_times):
+        collision_count = 0
+        digest_set = set()
+        for j in range(total_sample_count):
+            digest = get_identifier(pick_random_text(text_lines, sample_size))
+            if digest in digest_set:
+                collision_count += 1
+            else:
+                digest_set.add(digest)
+
+        collision_counts.append(collision_count)
+
+    avg_collision_count = sum(collision_counts) / test_times
+    print('average collision happened count: ' + str(avg_collision_count))
+    print('collision rate: ' +
+          str(100 * (avg_collision_count / total_sample_count)) + '%')
diff --git a/src/storode.py b/src/storode.py
new file mode 100644
index 0000000..54145b2
--- /dev/null
+++ b/src/storode.py
@@ -0,0 +1,380 @@
+import sys
+import os
+import hashlib
+
+from pathlib import Path
+from enum import Enum
+
+
+# function for calculating relative paths of path a to path b
+def calculate_relative_path(a, b):
+    file_name = b[b.rindex('/') + 1:]
+    a, b = a[:a.rindex('/')], b[:b.rindex('/')]
+    a, b = a.split('/'), b.split('/')
+
+    intersection = 0
+
+    for index in range(min(len(a), len(b))):
+        m, n = a[index], b[index]
+        if m != n:
+            intersection = index
+            break
+
+    def backward():
+        return (len(a) - intersection - 1) * '../'
+
+    def forward():
+        return '/'.join(b[intersection:])
+
+    out = backward() + forward() + '/' + file_name
+    return out
+
+
+# transfer whitespaces in string to html string
+def transfer_str_to_html(s):
+    return s.replace(
+        ' ', ' ').replace('\t', '  ').replace('\n', '
')
+
+
+class ReadState(Enum):
+    FRONT_OF_CARD = 0
+    BACK_OF_CARD = 1
+    CREATE = 2
+    UPDATE = 3
+
+
+class Requirement:
+    def __init__(self):
+        self.state = ReadState.FRONT_OF_CARD
+        self.identifier = ''
+        self.front_of_card = ''
+        self.front_lines = 0
+        self.back_lines = 0
+        self.back_of_card = ''
+        self.create_info = ''
+        self.update_info = ''
+        self.code_links = {}
+
+    def generate_identifier(self, exclusion=[]):
+        m2 = hashlib.md5()
+        m2.update((self.front_of_card + self.back_of_card).encode('utf-8'))
+        digest = m2.hexdigest()[-4:]
+        loop = 0
+        while digest in exclusion:
+            last = digest[-1]
+            if loop > 16:
+                raise Exception('Too many collision')
+            loop += 1
+            if last > 'f':
+                last = '0'
+            else:
+                digest = digest[:-1] + chr(ord(digest[-1]) + 1)
+        self.identifier = digest
+        collision.append(digest)
+
+    def text_append(self, line):
+        if self.state == ReadState.FRONT_OF_CARD:
+            if line.strip() != '':
+                self.front_lines += 1
+            self.front_of_card += line
+        elif self.state == ReadState.BACK_OF_CARD:
+            if line.strip() != '':
+                self.back_lines += 1
+            self.back_of_card += line
+        elif self.state == ReadState.CREATE:
+            self.create_info += line
+        elif self.state == ReadState.UPDATE:
+            self.update_info += line
+
+    def generate_line_number(self):
+        fronts = self.front_of_card.split('\n')
+        backs = self.back_of_card.split('\n')
+
+        def get_line_number(strings):
+            line_number = 1
+            ret = ''
+            for string in strings:
+                if string.strip() == '':
+                    ret += string + '\n'
+                else:
+                    ret += str(line_number) + ' ' + string + '\n'
+                    line_number += 1
+            return ret
+
+        self.front_of_card = get_line_number(fronts)
+        self.back_of_card = get_line_number(backs)
+
+    def process_str(self, line):
+        if line == '\n':
+            return
+        if 'back of card' in line.lower():
+            self.line_number = 1
+            self.state = ReadState.BACK_OF_CARD
+        elif 'created by' in line.lower():
+            self.state = ReadState.CREATE
+            self.text_append(line)
+        elif 'updated by' in line.lower():
+            self.state = ReadState.UPDATE
+            self.text_append(line)
+        else:
+            self.text_append(line)
+
+
+class CodeFile:
+    def __init__(self):
+        self.filename = ''
+        self.filepath = ''
+        self.lines = []
+
+    def find_next_code_signature(self, from_line):
+        length = len(self.lines)
+        index = from_line
+        while index < length:
+            line = self.lines[index].strip()
+            if line == '' or line[0] == '#':
+                index += 1
+                continue
+            if line.startswith('def ') or line.startswith('class '):
+                return index, line.split()[1][:-1]
+            # delete end comments
+            if '#' in line:
+                line = line[:line.index('#')].strip()
+            if '=' in line and '==' not in line:
+                return index, line[:line.index('=')].strip()
+            if line.startswith('if '):
+                return index, line[3:-1]
+            return index, line
+        return None, None
+
+
+if __name__ == "__main__":
+    argc = len(sys.argv)
+    if argc != 2 and argc != 3:
+        print('Usage:')
+        print('"python storode.py ./srs.txt" to get a srs file with id')
+        print('"python storode.py ./srs_id.txt ./src" to generate web pages for crossing reference')
+        exit()
+
+    need_identifier = True if argc == 2 else False
+
+    # Read requirements
+    requirements = {}
+
+    problems = []
+
+    code_files = []
+
+    # root dir for out pages
+    out_dir = './doc'
+
+    srs_file = sys.argv[1]
+    if not need_identifier:
+        src_path = sys.argv[2]
+
+    # Read requirements from file
+    with open(srs_file) as srs:
+        last_line = ''
+        requirement = None
+        lines = srs.readlines()
+        collision = []
+        for i in range(len(lines)):
+            line = lines[i]
+            if 'front of card' in line.lower():
+                if need_identifier:
+                    if requirement != None:
+                        requirement.generate_identifier(collision)
+                        requirements[requirement.identifier] = requirement
+                    requirement = Requirement()
+                else:
+                    requirement = Requirement()
+                    requirement.identifier = last_line.replace('\n', '')
+                    requirements[requirement.identifier] = requirement
+            elif requirement != None:
+                requirement.process_str(line)
+            last_line = line
+            if i == len(lines) - 1:
+                if need_identifier:
+                    if requirement != None:
+                        requirement.generate_identifier(collision)
+                        requirements[requirement.identifier] = requirement
+
+    # Write srs with id file to disk
+    if need_identifier:
+        with open(srs_file[:srs_file.rindex('.')] + '_with_id.txt', 'w') as srs:
+            for _, requirement in requirements.items():
+                requirement.generate_line_number()
+                srs.write(requirement.identifier + '\n')
+                srs.write('FRONT OF CARD\n')
+                srs.write(requirement.front_of_card)
+                srs.write('BACK OF CARD\n')
+                srs.write(requirement.back_of_card)
+                srs.write(requirement.create_info)
+                srs.write(requirement.update_info)
+                srs.write('\n\n')
+        exit(0)
+
+    src_path = Path(src_path)
+
+    requirement_out_file = out_dir + '/' + \
+        srs_file[srs_file.rindex('/') + 1:srs_file.rindex('.')] + '.html'
+
+    # Read source code
+    python_files = []
+
+    # Add python paths recursively
+    def recursion_view_path(path):
+        if path.is_dir():
+            for single_path in path.iterdir():
+                recursion_view_path(single_path)
+        else:
+            if path.match('*.py'):
+                python_files.append(path)
+
+    recursion_view_path(src_path)
+
+    for python_file in python_files:
+        code_file = CodeFile()
+        code_files.append(code_file)
+        code_file.filename = python_file.name
+        code_file.filepath = str(python_file.parent).replace('\\', '/')
+        with open(python_file, encoding='UTF-8') as file:
+            for line in file.readlines():
+                code_file.lines.append(line)
+
+    # generate pages for code
+    for code_file in code_files:
+        out_path = out_dir + '/' + code_file.filepath
+        code_out_file = out_path + '/' + code_file.filename + '.html'
+        relative_requirement_file = calculate_relative_path(
+            code_out_file, requirement_out_file)
+        relative_code_file = calculate_relative_path(
+            requirement_out_file, code_out_file)
+        if not os.path.exists(out_path):
+            os.makedirs(out_path)
+        with open(code_out_file, 'w', encoding='UTF-8') as out_file:
+            out_file.write('')
+            len_of_code = len(code_file.lines)
+            former_superlink = None
+            superlink_line = None
+            infect_lines = None
+            for i in range(len_of_code):
+                line = code_file.lines[i]
+                insertion_line = transfer_str_to_html(line)
+                if '#' in line:
+                    code = line[:line.index('#')] + '\n'
+                    comment = line[line.index('#'):]
+                    # make sure this commet is for linking requirements
+                    if '@req ' in comment:
+                        requirement_identifier = comment[comment.index(
+                            '@req') + 5:].strip()
+                        requirement_split = requirement_identifier.split()
+                        requirement_identifier = requirement_split[
+                            0]
+                        if len(requirement_split) > 1:
+                            infect_lines = requirement_split[1]
+                        else:
+                            infect_lines = None
+
+                        card_line = None
+                        is_front = True
+                        requirement_link = requirement_identifier[:]
+                        if ':' in requirement_identifier:
+                            requirement_identifier, card_line = requirement_identifier.split(
+                                ':')
+                            if card_line.lower().startswith('front'):
+                                card_line = card_line[5:]
+                            elif card_line.lower().startswith('back'):
+                                is_front = False
+                                card_line = card_line[4:]
+
+                        if i + 1 < len_of_code and requirements.__contains__(requirement_identifier):
+                            requirement = requirements[requirement_identifier]
+                            if card_line != None:
+                                card_line = int(card_line)
+                                if is_front:
+                                    if requirement.front_lines < card_line:
+                                        problems.append('WARNING: ' + '' + code_out_file + ':' + str(
+                                            i) + '' + ' requirement identifier: [' + requirement_link + '] not found')
+                                    else:
+                                        superlink_line, signature = code_file.find_next_code_signature(
+                                            i)
+                                        former_superlink = relative_requirement_file + '#' + requirement_link
+                                        requirement.code_links[signature] = relative_code_file + \
+                                            '#line' + str(superlink_line)
+                                        insertion_line = transfer_str_to_html(
+                                            code)
+                                else:
+                                    if requirement.back_lines < card_line:
+                                        problems.append('WARNING: ' + '' + code_out_file + ':' + str(
+                                            i) + '' + ' requirement identifier: [' + requirement_link + '] not found')
+                                    else:
+                                        superlink_line, signature = code_file.find_next_code_signature(
+                                            i)
+                                        former_superlink = relative_requirement_file + '#' + requirement_link
+                                        requirement.code_links[signature] = relative_code_file + \
+                                            '#line' + str(superlink_line)
+                                        insertion_line = transfer_str_to_html(
+                                            code)
+                            else:
+                                superlink_line, signature = code_file.find_next_code_signature(
+                                    i)
+                                former_superlink = relative_requirement_file + '#' + requirement_link
+                                requirement.code_links[signature] = relative_code_file + \
+                                    '#line' + str(superlink_line)
+                                insertion_line = transfer_str_to_html(code)
+                        else:
+                            problems.append('WARNING: ' + '' + code_out_file + ':' + str(
+                                i) + '' + ' requirement identifier: [' + requirement_identifier + '] not found')
+                if i == superlink_line:
+                    insertion_line = '' + insertion_line + ""
+                line_number = str(i)
+                while len(line_number) < 4:
+                    line_number = ' ' + line_number
+                insertion_line = '' + \
+                    line_number + '' + '' + \
+                    insertion_line + ''
+                out_file.write(insertion_line)
+
+    # generate the page for requirements
+    with open(requirement_out_file, 'w') as out_file:
+        out_file.write(
+            '
| Identifier | Front Card | Back Card | Date | Links | 
|---|---|---|---|---|
| ' + requirement.identifier + ' | ' + + fronts + ' | ' + + backs + ' | ' + + transfer_str_to_html(requirement.create_info) + + transfer_str_to_html(requirement.update_info) + + ' | ' + link_str + ' | 
')
+        for problem in problems:
+            out_file.write(problem + '
')
+
+        out_file.write('