Several fixes
This commit is contained in:
parent
a2f9fdbe09
commit
adfc6c4bc4
@ -2,6 +2,8 @@ from sys import argv
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from common import replaceContent
|
||||
|
||||
placementChart = {
|
||||
'a': 'above',
|
||||
'b': 'below',
|
||||
@ -62,16 +64,17 @@ def generate_latex(inputLines):
|
||||
|
||||
return '\n'.join(output)
|
||||
|
||||
def processFileContent(raw, template):
|
||||
def processFileContent(raw):
|
||||
content = generate_latex(raw)
|
||||
return template.replace('%CONTENT', content)
|
||||
return replaceContent(content, 'FSA')
|
||||
|
||||
if __name__ == '__main__':
|
||||
filename = argv[1]
|
||||
pass
|
||||
# filename = argv[1]
|
||||
|
||||
with open(filename) as file:
|
||||
content = generate_latex(file.read())
|
||||
# with open(filename) as file:
|
||||
# content = generate_latex(file.read())
|
||||
|
||||
with open(str(Path(__file__).parent.absolute()) + '/tex_templates/FSA.tex') as template:
|
||||
with open(argv[2], 'w') as destination_file:
|
||||
destination_file.write(template.read().replace('%CONTENT', content))
|
||||
# with open(str(Path(__file__).parent.absolute()) + '/tex_templates/FSA.tex') as template:
|
||||
# with open(argv[2], 'w') as destination_file:
|
||||
# destination_file.write(template.read().replace('%CONTENT', content))
|
@ -2,7 +2,7 @@ from sys import argv
|
||||
from pathlib import Path
|
||||
from math import sin, cos, pi
|
||||
|
||||
from common import printc
|
||||
from common import printc, replaceContent
|
||||
|
||||
class Matrix:
|
||||
""" Adjacency matrix which supports 0 and 1 """
|
||||
@ -186,7 +186,7 @@ def generateNodeCoords(n):
|
||||
|
||||
return nodeCoords
|
||||
|
||||
def processFileContent(raw, template):
|
||||
def processFileContent(raw):
|
||||
lines = raw.split('\n')
|
||||
lines.pop(1)
|
||||
outputType = lines.pop(0)
|
||||
@ -198,15 +198,20 @@ def processFileContent(raw, template):
|
||||
|
||||
if outputType == 'toGraph':
|
||||
content = graph.toLaTeX()
|
||||
return template.replace('%NODES', content[0]).replace('%EDGES', content[1])
|
||||
return replaceContent(
|
||||
content,
|
||||
'Graph',
|
||||
replaceF = lambda temp, cont: temp.replace('%NODES', cont[0]).replace('%EDGES', cont[1])
|
||||
)
|
||||
else:
|
||||
content = graph.toMatrix().toLaTeX()
|
||||
return template.replace('%CONTENT', content)
|
||||
return replaceContent(content, 'Matrix')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
matrix = Matrix([[False, False, True], [True, False, True], [True, False, False]])
|
||||
print(matrix)
|
||||
print(matrix.toGraph())
|
||||
print(matrix.toGraph().isUndirected())
|
||||
pass
|
||||
# matrix = Matrix([[False, False, True], [True, False, True], [True, False, False]])
|
||||
# print(matrix)
|
||||
# print(matrix.toGraph())
|
||||
# print(matrix.toGraph().isUndirected())
|
@ -1,7 +1,7 @@
|
||||
from sys import argv
|
||||
from pathlib import Path
|
||||
|
||||
from common import printc
|
||||
from common import printc, replaceContent
|
||||
|
||||
# Increase if the diagram becomes too clobbered
|
||||
HEIGHT_SEPARATOR = 1
|
||||
@ -64,23 +64,11 @@ def latex_hasse(hasse):
|
||||
|
||||
return '\n'.join(output)
|
||||
|
||||
def processFileContent(raw, template):
|
||||
def processFileContent(raw):
|
||||
rels = getRels(raw)
|
||||
content = latex_hasse(hasse_diagram(rels))
|
||||
return template.replace("%CONTENT", content)
|
||||
return replaceContent(content, 'Hasse')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
filename = argv[1]
|
||||
|
||||
with open(filename) as file:
|
||||
rels = getRels(file.read())
|
||||
|
||||
# rels = getRels()
|
||||
# rels = divisibility_graph(30)
|
||||
|
||||
content = latex_hasse(hasse_diagram(rels))
|
||||
|
||||
with open(str(Path(__file__).parent.absolute()) + '/tex_templates/Hasse.tex') as template:
|
||||
with open(argv[2], 'w') as destination_file:
|
||||
destination_file.write(template.read().replace('%CONTENT', content))
|
||||
pass
|
||||
|
@ -1,5 +1,7 @@
|
||||
from itertools import combinations
|
||||
|
||||
from common import replaceContent
|
||||
|
||||
class Set:
|
||||
def __init__(self, elements):
|
||||
self.elements = set(elements)
|
||||
@ -27,6 +29,10 @@ class Set:
|
||||
|
||||
def __le__(self, value):
|
||||
return self < value
|
||||
|
||||
@classmethod
|
||||
def fromString(cls, string):
|
||||
return cls(string.split(' '))
|
||||
|
||||
def cardinality(self):
|
||||
return len(self)
|
||||
@ -44,6 +50,13 @@ class Set:
|
||||
column.append(str(e) + ' \\\\')
|
||||
return '\\{\n' + '\n'.join(column) + '\n\\}'
|
||||
|
||||
|
||||
def processFileContent(raw):
|
||||
s = Set.fromString(raw)
|
||||
content = s.powerset().to_vertical_latex()
|
||||
|
||||
return replaceContent(content, 'Powerset')
|
||||
|
||||
#TODO: make process input func
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,11 +1,10 @@
|
||||
from sys import argv
|
||||
from pathlib import Path
|
||||
|
||||
from common import printc, printerr
|
||||
from Graph import Graph
|
||||
from common import printc, printerr, replaceContent
|
||||
import Graph
|
||||
|
||||
# Increase if hasse diagram becomes too clobbered
|
||||
HEIGHT_SEPARATOR = 1
|
||||
|
||||
def pairToString(pair):
|
||||
if str(pair[0]).isdigit() or str(pair[1]).isdigit():
|
||||
@ -19,6 +18,18 @@ def stringToPair(string):
|
||||
else:
|
||||
return tuple(list(string))
|
||||
|
||||
def textLatex(string):
|
||||
return '\\text{' + string + '}'
|
||||
|
||||
def mathModeListLatex(elements):
|
||||
if len(elements) > 7 or len(elements) and max(len(str(e)) for e in elements) > 10:
|
||||
return '\\begin{gather*}\n' \
|
||||
+ ' \\\\\n'.join(elements) + '\n' \
|
||||
+ '\\end{gather*}'
|
||||
else:
|
||||
return f'\\[ {", ".join(elements)} \\]'
|
||||
|
||||
|
||||
|
||||
class Relation:
|
||||
def __init__(self, pairs): # pair = (str,str)
|
||||
@ -29,7 +40,7 @@ class Relation:
|
||||
|
||||
@classmethod
|
||||
def fromString(cls, string):
|
||||
relations = (stringToPair(x) for x in relations.split(' '))
|
||||
relations = (stringToPair(x) for x in string.split(' '))
|
||||
return cls(relations)
|
||||
|
||||
@classmethod
|
||||
@ -43,7 +54,7 @@ class Relation:
|
||||
return cls(pairs)
|
||||
|
||||
|
||||
def isReflexive(self):
|
||||
def isReflexive(self, verbose=False):
|
||||
elements = set(list(sum(self.pairs, ())))
|
||||
result = all((x,x) in self.pairs for x in elements)
|
||||
|
||||
@ -51,31 +62,47 @@ class Relation:
|
||||
printc('Not reflexive, missing following pairs:', color='green')
|
||||
missingPairs = [(x,x) for x in elements if (x,x) not in self.pairs]
|
||||
print(f'\\[ {", ".join(pairToString(p) for p in missingPairs)} \\]')
|
||||
if verbose:
|
||||
return (False, missingPairs)
|
||||
|
||||
if verbose:
|
||||
return (True, [(x,x) for x in elements if (x,x) in self.pairs])
|
||||
|
||||
return result
|
||||
|
||||
def isSymmetric(self):
|
||||
def isSymmetric(self, verbose=False):
|
||||
result = all((y,x) in self.pairs for x,y in self.pairs)
|
||||
|
||||
if not result:
|
||||
printc('Not symmetric, missing following pairs:', color='green')
|
||||
missingPairs = [(x,y) for x,y in self.pairs if (y,x) not in self.pairs]
|
||||
print(f'\\[ {", ".join(pairToString(p) for p in missingPairs)} \\]')
|
||||
|
||||
if verbose:
|
||||
return (False, missingPairs)
|
||||
|
||||
if verbose:
|
||||
return (True, [((x,y), (y,x)) for x,y in self.pairs if (y,x) in self.pairs and x < y])
|
||||
|
||||
return result
|
||||
|
||||
def isAntiSymmetric(self):
|
||||
def isAntiSymmetric(self, verbose=False):
|
||||
result = not any((y,x) in self.pairs and y != x for x,y in self.pairs)
|
||||
|
||||
if not result:
|
||||
printc('Not antisymmetric, following pairs are symmetric:', color='green')
|
||||
symmetricPairs = [((x,y), (y,x)) for x,y in self.pairs if (y,x) in self.pairs and y > x]
|
||||
print(f'\\[ {", ".join(f"{pairToString(p)} and {pairToString(q)}" for p,q in symmetricPairs)} \\]')
|
||||
print(f'\\[ {", ".join(f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}""" for p,q in symmetricPairs)} \\]')
|
||||
if verbose:
|
||||
return (False, symmetricPairs)
|
||||
|
||||
if verbose:
|
||||
return (True, [])
|
||||
|
||||
return result
|
||||
|
||||
def isTransitive(self):
|
||||
def isTransitive(self, verbose=False):
|
||||
result = True
|
||||
transitivePairs = []
|
||||
nonTransitivePairs = []
|
||||
|
||||
for x,y in self.pairs:
|
||||
@ -83,33 +110,110 @@ class Relation:
|
||||
if not (y != z or ((x,w) in self.pairs)):
|
||||
nonTransitivePairs.append(((x,y), (z,w)))
|
||||
result = False
|
||||
elif (y == z and x != y != w and ((x,w) in self.pairs)):
|
||||
transitivePairs.append(((x,y), (z,w)))
|
||||
|
||||
if not result:
|
||||
printc('Not transitive, following pairs are missing its transitive counterpart:', color='green')
|
||||
print(f'\\[ {", ".join(f"{pairToString(p)} and {pairToString(q)} without {pairToString((p[0], q[1]))}" for p,q in nonTransitivePairs)} \\]')
|
||||
print(f'\\[ {", ".join(f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}{textLatex(" without ")}{pairToString((p[0], q[1]))}""" for p,q in nonTransitivePairs)} \\]')
|
||||
if verbose:
|
||||
return (False, nonTransitivePairs)
|
||||
|
||||
if verbose:
|
||||
return (True, transitivePairs)
|
||||
|
||||
return result
|
||||
|
||||
def isEquivalenceRelation(self):
|
||||
def isEquivalenceRelation(self, verbose=False):
|
||||
|
||||
if verbose:
|
||||
isReflexive, reflexivePairs = self.isReflexive(verbose=True)
|
||||
isSymmetric, symmetricPairs = self.isSymmetric(verbose=True)
|
||||
isTransitive, transitivePairs = self.isTransitive(verbose=True)
|
||||
|
||||
if not isReflexive:
|
||||
return 'The relation is not an equivalence relation, because it is not reflexive.'
|
||||
+ ' The following elements should be related:\n\n'
|
||||
+ f'\\[ {", ".join(pairToString(p) for p in reflexivePairs)} \\]'
|
||||
if not isSymmetric:
|
||||
return 'The relation is not an equivalence relation, because it is not symmetric.'
|
||||
+ ' It is missing the following symmetric pairs of relations\n\n'
|
||||
+ f'\\[ {", ".join(f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}""" for p,q in symmetricPairs)} \\]'
|
||||
if not isTransitive:
|
||||
return 'The relation is not an equivalence relation, because it is not transitive.'
|
||||
+ ' The following pairs of relations are missing its transitive counterpart\n\n'
|
||||
+ f'\\[ {", ".join(f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}{textLatex(" without ")}{pairToString((p[0], q[1]))}""" for p,q in transitivePairs)} \\]'
|
||||
|
||||
|
||||
rxStr = mathModeListLatex([pairToString(p) for p in reflexivePairs])
|
||||
smStr = mathModeListLatex([f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}""" for p,q in symmetricPairs])
|
||||
trStr = mathModeListLatex([f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}{textLatex(" with ")}{pairToString((p[0], q[1]))}""" for p,q in transitivePairs])
|
||||
|
||||
return replaceContent(
|
||||
(rxStr, smStr, trStr),
|
||||
'Relations/EquivalenceProof',
|
||||
lambda temp, cont: temp.replace('%REFLEXIVE', cont[0]).replace('%SYMMETRIC', cont[1]).replace('%TRANSITIVE', cont[2])
|
||||
)
|
||||
|
||||
return self.isReflexive() and self.isSymmetric() and self.isTransitive()
|
||||
|
||||
def isPartialOrder(self):
|
||||
return self.isReflexive() and self.isAntiSymmetric() and self.isTransitive()
|
||||
|
||||
def getHassePairs(self):
|
||||
if not self.isPartialOrder():
|
||||
def isPartialOrder(self, verbose=False):
|
||||
|
||||
result = self.isReflexive() and self.isAntiSymmetric() and self.isTransitive()
|
||||
|
||||
if result:
|
||||
hasse= self.getHassePairs(checkIfPartialOrder=False)
|
||||
min_el = set(a for a,b in hasse if a not in list(zip(*hasse))[1])
|
||||
printc(f"Minimal elements: $\{{ {', '.join(str(e) for e in min_el)} \}}$ \\\\")
|
||||
|
||||
max_el = set(v for k,v in hasse if v not in (x for x,_ in hasse))
|
||||
printc(f"Maximal elements: $\{{ {', '.join(str(e) for e in max_el)} \}}$" )
|
||||
|
||||
if verbose:
|
||||
isReflexive, reflexivePairs = self.isReflexive(verbose=True)
|
||||
isAntiSymmetric, antiSymmetricPairs = self.isAntiSymmetric(verbose=True)
|
||||
isTransitive, transitivePairs = self.isTransitive(verbose=True)
|
||||
|
||||
if not isReflexive:
|
||||
return 'The relation is not a partial order, because it is not reflexive.'
|
||||
+ ' The following elements should be related:\n\n'
|
||||
+ f'\\[ {", ".join(pairToString(p) for p in reflexivePairs)} \\]'
|
||||
if not isAntiSymmetric:
|
||||
return 'The relation is not a partial order, because it is not antisymmetric.'
|
||||
+ ' The following relations are symmetric\n\n'
|
||||
+ f'\\[ {", ".join(f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}""" for p,q in antiSymmetricPairs)} \\]'
|
||||
if not isTransitive:
|
||||
return 'The relation is not a partial order, because it is not transitive.'
|
||||
+ ' The following pairs of relations are missing its transitive counterpart\n\n'
|
||||
+ f'\\[ {", ".join(f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}{textLatex(" without ")}{pairToString((p[0], q[1]))}""" for p,q in transitivePairs)} \\]'
|
||||
|
||||
rxStr = mathModeListLatex([pairToString(p) for p in reflexivePairs])
|
||||
trStr = mathModeListLatex([f"""{pairToString(p)}{textLatex(" and ")}{pairToString(q)}{textLatex(" with ")}{pairToString((p[0], q[1]))}""" for p,q in transitivePairs])
|
||||
|
||||
return replaceContent(
|
||||
(rxStr, trStr),
|
||||
'Relations/PosetProof',
|
||||
lambda temp, cont: temp.replace('%REFLEXIVE', cont[0]).replace('%TRANSITIVE', cont[1])
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def getHassePairs(self, checkIfPartialOrder=True):
|
||||
if checkIfPartialOrder and not self.isPartialOrder():
|
||||
printerr('This is not a partial order')
|
||||
return
|
||||
|
||||
nonReflexivePairs = set((x,y) for x,y in self.pairs if x != y)
|
||||
|
||||
hassePairs = set()
|
||||
for x1, y1 in self.pairs:
|
||||
for x2, y2 in self.pairs:
|
||||
for x1, y1 in nonReflexivePairs:
|
||||
for x2, y2 in nonReflexivePairs:
|
||||
if y1 == x2:
|
||||
hassePairs.add((x1, y2))
|
||||
return self.pairs - hassePairs
|
||||
return nonReflexivePairs - hassePairs
|
||||
|
||||
def latexifyHasseDiagram(self):
|
||||
hasse = self.getHassePairs()
|
||||
min_el = set(a for a,b in hasse if a not in list(zip(*hasse))[1])
|
||||
keys = set(item for tup in hasse for item in tup)
|
||||
y_pos = dict.fromkeys(keys, 0)
|
||||
|
||||
@ -127,19 +231,13 @@ class Relation:
|
||||
|
||||
for y in inv_ypos.keys():
|
||||
for i, n in enumerate(inv_ypos[y]):
|
||||
output.append(f'\\node ({n}) at ({i - len(inv_ypos[y])/2}, {y * HEIGHT_SEPARATOR}) {{${n}$}};')
|
||||
output.append(f'\\node ({n}) at ({i - len(inv_ypos[y])/2}, {y * 0.5 * (len(hasse) ** 0.4)}) {{${n}$}};')
|
||||
|
||||
output.append('')
|
||||
|
||||
for x,y in hasse:
|
||||
output.append(f'\\draw ({x}) -- ({y});')
|
||||
|
||||
|
||||
printc(f"Minimal elements: $\{{ {', '.join(str(e) for e in min_el)} \}}$ \\\\")
|
||||
|
||||
max_el = set(v for k,v in hasse if v not in (x for x,_ in hasse))
|
||||
printc(f"Maximal elements: $\{{ {', '.join(str(e) for e in max_el)} \}}$" )
|
||||
|
||||
return '\n'.join(output)
|
||||
|
||||
def latexifyGraph(self):
|
||||
@ -150,33 +248,46 @@ class Relation:
|
||||
graphType = 'directed'
|
||||
pairs = self.pairs
|
||||
|
||||
pass
|
||||
nodes = set()
|
||||
for x,y in pairs:
|
||||
nodes.add(x)
|
||||
nodes.add(y)
|
||||
|
||||
edges = "\n".join(pairToString(p) for p in pairs)
|
||||
|
||||
processingString = f'toGraph\n\n{graphType}\n\n{ " ".join(nodes) }\n\n{edges}'
|
||||
return Graph.processFileContent(processingString)
|
||||
|
||||
def processFileContent(raw, template):
|
||||
|
||||
def processFileContent(raw):
|
||||
outputType, inp = raw.split('\n\n')
|
||||
|
||||
if inp.startsWith('divisibilityPosetTo'):
|
||||
if inp.startswith('divisibilityPosetTo'):
|
||||
n = int(inp.split(' ')[1])
|
||||
relation = Relation.fromDivisibilityPosetTo(n)
|
||||
else:
|
||||
relation = Relation.fromString(inp)
|
||||
|
||||
if outputType == 'proveEquivalence':
|
||||
content = relation.isEquivalenceRelation()
|
||||
content = relation.isEquivalenceRelation(verbose=True)
|
||||
return content
|
||||
|
||||
elif outputType == 'provePoset':
|
||||
content = relation.isPartialOrder()
|
||||
content = relation.isPartialOrder(verbose=True)
|
||||
return content
|
||||
|
||||
elif outputType == 'hasseDiagram':
|
||||
content = relation.latexifyHasseDiagram()
|
||||
return replaceContent(content, 'Relations/Hasse')
|
||||
|
||||
elif outputType == 'graph':
|
||||
content = relation.latexifyGraph()
|
||||
|
||||
content = Relation.fromString(raw).latexifyHasseDiagram()
|
||||
return template.replace("%CONTENT", content)
|
||||
return content
|
||||
|
||||
if __name__ == '__main__':
|
||||
inp = 'AA BB CC DD AB AC AD BC BD CD'
|
||||
relations = [stringToPair(p) for p in inp.split(' ')]
|
||||
# print(Relation(relations).isEquivalenceRelation())
|
||||
print(Relation(relations).isPartialOrder())
|
||||
print(Relation.fromDivisibilityPosetTo(30).isPartialOrder())
|
||||
pass
|
||||
# inp = 'AA BB CC DD AB AC AD BC BD CD'
|
||||
# relations = [stringToPair(p) for p in inp.split(' ')]
|
||||
# # print(Relation(relations).isEquivalenceRelation())
|
||||
# print(Relation(relations).isPartialOrder())
|
||||
# print(Relation.fromDivisibilityPosetTo(30).isPartialOrder())
|
||||
|
@ -1,7 +1,7 @@
|
||||
from sys import argv
|
||||
from pathlib import Path
|
||||
|
||||
from common import printerr
|
||||
from common import printerr, replaceContent
|
||||
|
||||
try:
|
||||
from tabulate import tabulate
|
||||
@ -20,18 +20,21 @@ class OverloadedBool:
|
||||
self.val = val
|
||||
|
||||
def __bool__(self):
|
||||
return self.val
|
||||
val = self.val
|
||||
while(type(val) is OverloadedBool):
|
||||
val = val.val
|
||||
return val
|
||||
|
||||
def __str__(self):
|
||||
return str(self.val)
|
||||
|
||||
def __add__(self, bool2):
|
||||
""" Implies """
|
||||
return (not self.val or bool2)
|
||||
return OverloadedBool(not self.val or bool2)
|
||||
|
||||
def __sub__(self, bool2):
|
||||
""" Iff """
|
||||
return (not self.val or bool2) and (not bool2 or self.val)
|
||||
return OverloadedBool((not self.val or bool2) and (not bool2 or self.val))
|
||||
|
||||
def flattenImpliesIff(exps):
|
||||
return [exp.replace('implies', '+').replace('iff', '-').replace('E', '') for exp in exps]
|
||||
@ -51,7 +54,7 @@ def generateTruthTable(exps):
|
||||
def calculateGridLine():
|
||||
line = []
|
||||
for exp in exps:
|
||||
exec(f'line.append(({exp}).__bool__())')
|
||||
exec(f'line.append(({exp}))')
|
||||
return line
|
||||
|
||||
truthtable.append(calculateGridLine())
|
||||
@ -95,30 +98,14 @@ def printTruthtable(exps, truthtable):
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def processFileContent(raw, template):
|
||||
def processFileContent(raw):
|
||||
exps = parseExpressionList(raw)
|
||||
truthtable = generateTruthTable(exps)
|
||||
|
||||
printTruthtable(exps, generateTruthTable(exps))
|
||||
|
||||
content = latexify(exps, truthtable)
|
||||
return template.replace('%CONTENT', content)
|
||||
|
||||
|
||||
return replaceContent(content, 'Truthtable')
|
||||
|
||||
if __name__ == '__main__':
|
||||
filename = argv[1]
|
||||
|
||||
with open(filename) as file:
|
||||
exps = parseExpressionList(file.read())
|
||||
truthtable = generateTruthTable(exps)
|
||||
|
||||
from tabulate import tabulate
|
||||
printTruthtable(exps, generateTruthTable(exps))
|
||||
|
||||
content = latexify(exps, truthtable)
|
||||
|
||||
with open(str(Path(__file__).parent.absolute()) + '/tex_templates/Truthtable.tex') as template:
|
||||
with open(argv[2], 'w') as destination_file:
|
||||
destination_file.write(template.read().replace('%CONTENT', content))
|
||||
pass
|
@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
|
||||
clear = '\033[0m'
|
||||
colors = {
|
||||
@ -13,4 +14,8 @@ def printc(text, color='blue'):
|
||||
print(f'{colors[color]}{text}{clear}')
|
||||
|
||||
def printerr(text):
|
||||
print(f'\033[31;5m[ERROR] {text}{clear}')
|
||||
print(f'\033[31;5m[ERROR] {text}{clear}')
|
||||
|
||||
def replaceContent(content, template, replaceF = lambda temp, cont: temp.replace("%CONTENT", cont)):
|
||||
with open(str(Path(__file__).parent.absolute()) + f'/tex_templates/{template}.tex') as template:
|
||||
return replaceF(template.read(), content)
|
@ -15,22 +15,21 @@ def fetchContentType(content):
|
||||
|
||||
def processContent(content):
|
||||
contentType, content = fetchContentType(content)
|
||||
with open(str(Path(__file__).parent.absolute()) + f'/tex_templates/{contentType}.tex') as template:
|
||||
if contentType == 'Hasse':
|
||||
result = Hasse.processFileContent(content, template.read())
|
||||
elif contentType == 'FSA':
|
||||
result = FSA.processFileContent(content, template.read())
|
||||
elif contentType == 'Graph':
|
||||
result = Graph.processFileContent('toGraph\n\n' + content, template.read())
|
||||
elif contentType == 'Matrix':
|
||||
result = Graph.processFileContent('toMatrix\n\n' + content, template.read())
|
||||
elif contentType == 'Relations':
|
||||
result = Relations.processFileContent(content, template.read())
|
||||
elif contentType == 'Truthtable':
|
||||
result = Truthtable.processFileContent(content, template.read())
|
||||
else:
|
||||
printerr('DIDN\'T RECOGNIZE FILE TYPE')
|
||||
exit(1)
|
||||
|
||||
if contentType == 'FSA':
|
||||
result = FSA.processFileContent(content)
|
||||
elif contentType == 'Graph':
|
||||
result = Graph.processFileContent('toGraph\n\n' + content)
|
||||
elif contentType == 'Matrix':
|
||||
result = Graph.processFileContent('toMatrix\n\n' + content)
|
||||
elif contentType == 'Relations':
|
||||
result = Relations.processFileContent(content)
|
||||
elif contentType == 'Truthtable':
|
||||
result = Truthtable.processFileContent(content)
|
||||
else:
|
||||
printerr('DIDN\'T RECOGNIZE FILE TYPE')
|
||||
exit(1)
|
||||
|
||||
return result
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
0
exam_template/python/tex_templates/Powerset.tex
Normal file
0
exam_template/python/tex_templates/Powerset.tex
Normal file
8
exam_template/python/tex_templates/Python.tex
Normal file
8
exam_template/python/tex_templates/Python.tex
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
\codeFile{%CODEFILE}{python}
|
||||
|
||||
Output:
|
||||
|
||||
\begin{verbatim}
|
||||
%OUTPUT
|
||||
\end{verbatim}
|
@ -0,0 +1,22 @@
|
||||
|
||||
In order for this relation to be an equivalence equation, it has to be reflexive, symmetric and transitive.
|
||||
|
||||
\textbf{Reflexive:}
|
||||
|
||||
All elements are related to themself
|
||||
|
||||
%REFLEXIVE
|
||||
|
||||
\textbf{Symmetric:}
|
||||
|
||||
All relations has its symmetric counterpart
|
||||
|
||||
%SYMMETRIC
|
||||
|
||||
\textbf{Transitive:}
|
||||
|
||||
All pair of relations where $xRy$ and $yRz$ has its transitive counterpart
|
||||
|
||||
%TRANSITIVE
|
||||
|
||||
Hence the relation is an equivalence relation
|
22
exam_template/python/tex_templates/Relations/PosetProof.tex
Normal file
22
exam_template/python/tex_templates/Relations/PosetProof.tex
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
In order for this relation to be a partial order, it has to be reflexive, antisymmetric and transitive.
|
||||
|
||||
\textbf{Reflexive:}
|
||||
|
||||
All elements are related to themself
|
||||
|
||||
%REFLEXIVE
|
||||
|
||||
\textbf{Antisymmetric:}
|
||||
|
||||
No relation have a symmetric counterpart \\
|
||||
|
||||
(Listing the ones that don't have a symmetric counterpart would just be listing the whole set) \\
|
||||
|
||||
\textbf{Transitive:}
|
||||
|
||||
All pair of relations where $xRy$ and $yRz$ has its transitive counterpart
|
||||
|
||||
%TRANSITIVE
|
||||
|
||||
Hence the relation is a partial order
|
33
exam_template_graphics/graphics/directedGraph.tex
Normal file
33
exam_template_graphics/graphics/directedGraph.tex
Normal file
@ -0,0 +1,33 @@
|
||||
\newcommand{\arrow}[2]{\path [-{Latex[scale=1]}] (#1) edge (#2);}
|
||||
|
||||
\begin{tikzpicture}
|
||||
|
||||
\begin{scope}[every node/.style={shape=circle, fill=white, draw, inner sep=2pt}]
|
||||
\node (A) at (0,4.0) {$A$};
|
||||
\node (B) at (-2.82843,2.82843) {$B$};
|
||||
\node (C) at (-4.0,0.0) {$C$};
|
||||
\node (D) at (-2.82843,-2.82843) {$D$};
|
||||
\node (E) at (-0.0,-4.0) {$E$};
|
||||
\node (F) at (2.82843,-2.82843) {$F$};
|
||||
\node (G) at (4.0,-0.0) {$G$};
|
||||
\node (H) at (2.82843,2.82843) {$H$};
|
||||
\end{scope}
|
||||
|
||||
\begin{scope}[every draw/.style={}]
|
||||
\draw [-{Latex[scale=1]}] (A) to (B);
|
||||
\draw [-{Latex[scale=1]}] (A) to (C);
|
||||
\draw [-{Latex[scale=1]}] (C) to (D);
|
||||
\draw [-{Latex[scale=1]}] (D) to (E);
|
||||
\draw [-{Latex[scale=1]}, bend left=8] (H) to (A);
|
||||
\draw [-{Latex[scale=1]}] (F) to (D);
|
||||
\draw [-{Latex[scale=1]}] (C) to (B);
|
||||
\draw [-{Latex[scale=1]}] (F) to (G);
|
||||
\draw [-{Latex[scale=1]}] (D) to (E);
|
||||
\draw [-{Latex[scale=1]}] (D) to (G);
|
||||
\draw [-{Latex[scale=1]}, bend left=8] (A) to (H);
|
||||
\draw [-{Latex[scale=1]}] (F) to (B);
|
||||
\draw [-{Latex[scale=1]}, bend left=8] (H) to (C);
|
||||
\draw [-{Latex[scale=1]}, bend left=8] (C) to (H);
|
||||
\end{scope}
|
||||
|
||||
\end{tikzpicture}
|
20
exam_template_graphics/graphics/equivalenceDiagram.tex
Normal file
20
exam_template_graphics/graphics/equivalenceDiagram.tex
Normal file
@ -0,0 +1,20 @@
|
||||
\newcommand{\arrow}[2]{\path [-{Latex[scale=1]}] (#1) edge (#2);}
|
||||
|
||||
\begin{tikzpicture}
|
||||
|
||||
\begin{scope}[every node/.style={shape=circle, fill=white, draw, inner sep=2pt}]
|
||||
\node (B) at (0,2.5) {$B$};
|
||||
\node (D) at (-2.37764,0.77254) {$D$};
|
||||
\node (C) at (-1.46946,-2.02254) {$C$};
|
||||
\node (A) at (1.46946,-2.02254) {$A$};
|
||||
\node (E) at (2.37764,0.77254) {$E$};
|
||||
\end{scope}
|
||||
|
||||
\begin{scope}[every draw/.style={}]
|
||||
\draw (A) -- (C);
|
||||
\draw (A) -- (E);
|
||||
\draw (B) -- (D);
|
||||
\draw (C) -- (E);
|
||||
\end{scope}
|
||||
|
||||
\end{tikzpicture}
|
@ -1,16 +1,16 @@
|
||||
\begin{tikzpicture}
|
||||
\tikzset{every node/.style={shape=circle,draw,inner sep=2pt}}
|
||||
|
||||
\node (a) at (-0.5, 0) {$a$};
|
||||
\node (b) at (-1.0, 1) {$b$};
|
||||
\node (e) at (0.0, 1) {$e$};
|
||||
\node (c) at (-1.0, 2) {$c$};
|
||||
\node (d) at (0.0, 2) {$d$};
|
||||
\node (a) at (-0.5, 0.0) {$a$};
|
||||
\node (e) at (-1.0, 0.9518269693579393) {$e$};
|
||||
\node (b) at (0.0, 0.9518269693579393) {$b$};
|
||||
\node (d) at (-1.0, 1.9036539387158786) {$d$};
|
||||
\node (c) at (0.0, 1.9036539387158786) {$c$};
|
||||
|
||||
\draw (e) -- (d);
|
||||
\draw (a) -- (e);
|
||||
\draw (b) -- (c);
|
||||
\draw (a) -- (b);
|
||||
\draw (e) -- (c);
|
||||
\draw (e) -- (d);
|
||||
\draw (b) -- (c);
|
||||
\draw (a) -- (e);
|
||||
\draw (a) -- (b);
|
||||
|
||||
\end{tikzpicture}
|
@ -0,0 +1,49 @@
|
||||
\begin{tikzpicture}
|
||||
\tikzset{every node/.style={shape=circle,draw,inner sep=2pt}}
|
||||
|
||||
\node (1) at (-0.5, 0.0) {$1$};
|
||||
\node (2) at (-4.0, 1.7826024579660036) {$2$};
|
||||
\node (3) at (-3.0, 1.7826024579660036) {$3$};
|
||||
\node (5) at (-2.0, 1.7826024579660036) {$5$};
|
||||
\node (7) at (-1.0, 1.7826024579660036) {$7$};
|
||||
\node (11) at (0.0, 1.7826024579660036) {$11$};
|
||||
\node (13) at (1.0, 1.7826024579660036) {$13$};
|
||||
\node (17) at (2.0, 1.7826024579660036) {$17$};
|
||||
\node (19) at (3.0, 1.7826024579660036) {$19$};
|
||||
\node (4) at (-3.0, 3.565204915932007) {$4$};
|
||||
\node (6) at (-2.0, 3.565204915932007) {$6$};
|
||||
\node (9) at (-1.0, 3.565204915932007) {$9$};
|
||||
\node (10) at (0.0, 3.565204915932007) {$10$};
|
||||
\node (14) at (1.0, 3.565204915932007) {$14$};
|
||||
\node (15) at (2.0, 3.565204915932007) {$15$};
|
||||
\node (8) at (-1.5, 5.347807373898011) {$8$};
|
||||
\node (12) at (-0.5, 5.347807373898011) {$12$};
|
||||
\node (18) at (0.5, 5.347807373898011) {$18$};
|
||||
\node (16) at (-0.5, 7.130409831864014) {$16$};
|
||||
|
||||
\draw (6) -- (12);
|
||||
\draw (6) -- (18);
|
||||
\draw (4) -- (12);
|
||||
\draw (5) -- (10);
|
||||
\draw (1) -- (3);
|
||||
\draw (2) -- (14);
|
||||
\draw (3) -- (9);
|
||||
\draw (4) -- (8);
|
||||
\draw (3) -- (6);
|
||||
\draw (3) -- (15);
|
||||
\draw (5) -- (15);
|
||||
\draw (2) -- (4);
|
||||
\draw (1) -- (2);
|
||||
\draw (1) -- (5);
|
||||
\draw (1) -- (11);
|
||||
\draw (2) -- (10);
|
||||
\draw (1) -- (17);
|
||||
\draw (8) -- (16);
|
||||
\draw (1) -- (7);
|
||||
\draw (1) -- (13);
|
||||
\draw (2) -- (6);
|
||||
\draw (9) -- (18);
|
||||
\draw (1) -- (19);
|
||||
\draw (7) -- (14);
|
||||
|
||||
\end{tikzpicture}
|
42
exam_template_graphics/graphics/proveEquivalence.tex
Normal file
42
exam_template_graphics/graphics/proveEquivalence.tex
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
In order for this relation to be an equivalence equation, it has to be reflexive, symmetric and transitive.
|
||||
|
||||
\textbf{Reflexive:}
|
||||
|
||||
All elements are related to themself
|
||||
|
||||
\[ AA, DD, CC, EE, BB \]
|
||||
|
||||
\textbf{Symmetric:}
|
||||
|
||||
All relations has its symmetric counterpart
|
||||
|
||||
\begin{gather*}
|
||||
AE\text{ and }EA \\
|
||||
AC\text{ and }CA \\
|
||||
BD\text{ and }DB \\
|
||||
CE\text{ and }EC
|
||||
\end{gather*}
|
||||
|
||||
\textbf{Transitive:}
|
||||
|
||||
All pair of relations where $xRy$ and $yRz$ has its transitive counterpart
|
||||
|
||||
\begin{gather*}
|
||||
DB\text{ and }BD\text{ with }DD \\
|
||||
AE\text{ and }EA\text{ with }AA \\
|
||||
AE\text{ and }EC\text{ with }AC \\
|
||||
AC\text{ and }CE\text{ with }AE \\
|
||||
AC\text{ and }CA\text{ with }AA \\
|
||||
BD\text{ and }DB\text{ with }BB \\
|
||||
CE\text{ and }EA\text{ with }CA \\
|
||||
CE\text{ and }EC\text{ with }CC \\
|
||||
EA\text{ and }AE\text{ with }EE \\
|
||||
EA\text{ and }AC\text{ with }EC \\
|
||||
EC\text{ and }CE\text{ with }EE \\
|
||||
EC\text{ and }CA\text{ with }EA \\
|
||||
CA\text{ and }AE\text{ with }CE \\
|
||||
CA\text{ and }AC\text{ with }CC
|
||||
\end{gather*}
|
||||
|
||||
Hence the relation is an equivalence relation
|
27
exam_template_graphics/graphics/provePoset.tex
Normal file
27
exam_template_graphics/graphics/provePoset.tex
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
In order for this relation to be a partial order, it has to be reflexive, antisymmetric and transitive.
|
||||
|
||||
\textbf{Reflexive:}
|
||||
|
||||
All elements are related to themself
|
||||
|
||||
\[ AA, BB, CC, DD \]
|
||||
|
||||
\textbf{Antisymmetric:}
|
||||
|
||||
No relation have a symmetric counterpart \\
|
||||
|
||||
(Listing the ones that don't have a symmetric counterpart would just be listing the whole set) \\
|
||||
|
||||
\textbf{Transitive:}
|
||||
|
||||
All pair of relations where $xRy$ and $yRz$ has its transitive counterpart
|
||||
|
||||
\begin{gather*}
|
||||
AB\text{ and }BD\text{ with }AD \\
|
||||
AB\text{ and }BC\text{ with }AC \\
|
||||
BC\text{ and }CD\text{ with }BD \\
|
||||
AC\text{ and }CD\text{ with }AD
|
||||
\end{gather*}
|
||||
|
||||
Hence the relation is a partial order
|
19
exam_template_graphics/graphics/src/directedGraph.txt
Normal file
19
exam_template_graphics/graphics/src/directedGraph.txt
Normal file
@ -0,0 +1,19 @@
|
||||
# Graph
|
||||
directed
|
||||
|
||||
A B C D E F G H
|
||||
|
||||
AB
|
||||
AC
|
||||
CD
|
||||
DE
|
||||
HA
|
||||
FD
|
||||
CB
|
||||
FG
|
||||
DE
|
||||
DG
|
||||
AH
|
||||
FB
|
||||
HC
|
||||
CH
|
@ -1,4 +1,4 @@
|
||||
# Relations
|
||||
hasseDiagram
|
||||
|
||||
ab ac ad ae bc ed ec
|
||||
aa bb cc dd ee ab ac ad ae bc ed ec
|
@ -0,0 +1,4 @@
|
||||
# Relations
|
||||
hasseDiagram
|
||||
|
||||
divisibilityPosetTo 20
|
3
exam_template_graphics/graphics/src/powerset.txt
Normal file
3
exam_template_graphics/graphics/src/powerset.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# Powerset
|
||||
|
||||
A B C D (E F) (A (B C))
|
2
exam_template_graphics/graphics/src/python.txt
Normal file
2
exam_template_graphics/graphics/src/python.txt
Normal file
@ -0,0 +1,2 @@
|
||||
# python
|
||||
|
@ -1,2 +1,2 @@
|
||||
# Truthtable
|
||||
p, q, s, p and q, p implies q, E p iff q, p iff (q and s)
|
||||
p, q, s, p and q, E p iff q, p iff (q and s), E not ((not p and q) or (not p and not q)) or (p and q)
|
@ -14,4 +14,6 @@ FG
|
||||
DE
|
||||
DG
|
||||
AH
|
||||
FB
|
||||
FB
|
||||
HC
|
||||
CH
|
@ -1,14 +1,14 @@
|
||||
|
||||
\begin{truthtable}
|
||||
{c|c|c|c|c|e|c}
|
||||
{$p$ & $q$ & $s$ & $p \wedge q$ & $p \Rightarrow q$ & $ p \Leftrightarrow q$ & $p \Leftrightarrow (q \wedge s)$}
|
||||
{c|c|c|c|e|c|e}
|
||||
{$p$ & $q$ & $s$ & $p \wedge q$ & $ p \Leftrightarrow q$ & $p \Leftrightarrow (q \wedge s)$ & $ \neg ((\neg p \wedge q) \vee (\neg p \wedge \neg q)) \vee (p \wedge q)$}
|
||||
\T & \T & \T & \T & \T & \T & \T \\
|
||||
\T & \T & \F & \T & \T & \T & \F \\
|
||||
\T & \F & \T & \F & \F & \F & \F \\
|
||||
\T & \F & \F & \F & \F & \F & \F \\
|
||||
\F & \T & \T & \F & \T & \F & \F \\
|
||||
\F & \T & \F & \F & \T & \F & \T \\
|
||||
\F & \F & \T & \F & \T & \T & \T \\
|
||||
\F & \F & \F & \F & \T & \T & \T \\
|
||||
\T & \T & \F & \T & \T & \F & \T \\
|
||||
\T & \F & \T & \F & \F & \F & \T \\
|
||||
\T & \F & \F & \F & \F & \F & \T \\
|
||||
\F & \T & \T & \F & \F & \F & \F \\
|
||||
\F & \T & \F & \F & \F & \T & \F \\
|
||||
\F & \F & \T & \F & \T & \T & \F \\
|
||||
\F & \F & \F & \F & \T & \T & \F \\
|
||||
\end{truthtable}
|
||||
|
@ -20,6 +20,7 @@
|
||||
\draw (B) -- (C);
|
||||
\draw (B) -- (F);
|
||||
\draw (C) -- (D);
|
||||
\draw (C) -- (H);
|
||||
\draw (D) -- (E);
|
||||
\draw (D) -- (F);
|
||||
\draw (D) -- (G);
|
||||
|
@ -16,10 +16,21 @@
|
||||
\renewcommand{\theenumiii}{\alph{enumiii})}
|
||||
|
||||
\usepackage{verbatim}
|
||||
\usepackage{listings}
|
||||
|
||||
\newcommand{\listFile}[1]{
|
||||
\lstinputlisting
|
||||
[ frame=single,
|
||||
basicstyle=\small,
|
||||
breaklines
|
||||
]
|
||||
{graphics/src/#1.txt}
|
||||
}
|
||||
|
||||
\newcommand{\verbatimDiagram}[1]{
|
||||
\section{#1}
|
||||
\verbatiminput{graphics/src/#1.txt}
|
||||
\subsection{#1}
|
||||
|
||||
\listFile{#1}
|
||||
|
||||
\includeDiagram{graphics/#1.tex}
|
||||
|
||||
@ -27,9 +38,9 @@
|
||||
}
|
||||
|
||||
\newcommand{\verbatimInput}[1]{
|
||||
\section{#1}
|
||||
\subsection{#1}
|
||||
|
||||
\verbatiminput{graphics/src/#1.txt}
|
||||
\listFile{#1}
|
||||
|
||||
\input{graphics/#1.tex}
|
||||
|
||||
@ -43,20 +54,42 @@
|
||||
|
||||
\tableofcontents
|
||||
|
||||
\verbatimDiagram{hasse}
|
||||
\newpage{}
|
||||
|
||||
\verbatimDiagram{automata}
|
||||
\section{Propositional Logic}
|
||||
|
||||
\verbatimInput{truthtable}
|
||||
\verbatimInput{truthtable}
|
||||
|
||||
\verbatimDiagram{undirectedGraph}
|
||||
\section{Sets}
|
||||
|
||||
\verbatimDiagram{complete6}
|
||||
\section{Relations}
|
||||
|
||||
\verbatimDiagram{adjacency}
|
||||
\verbatimInput{proveEquivalence}
|
||||
|
||||
\verbatimInput{undirectedGraphToMatrix}
|
||||
\verbatimInput{provePoset}
|
||||
|
||||
\verbatimDiagram{directedFromMatrix}
|
||||
\verbatimDiagram{equivalenceDiagram}
|
||||
|
||||
\verbatimDiagram{hasse}
|
||||
|
||||
\verbatimDiagram{hasseDiagramByDivisibility}
|
||||
|
||||
\section{Graph theory}
|
||||
|
||||
\verbatimDiagram{undirectedGraph}
|
||||
|
||||
\verbatimDiagram{directedGraph}
|
||||
|
||||
\verbatimDiagram{complete6}
|
||||
|
||||
\verbatimDiagram{adjacency}
|
||||
|
||||
\verbatimInput{undirectedGraphToMatrix}
|
||||
|
||||
\verbatimDiagram{directedFromMatrix}
|
||||
|
||||
\section{Finite state automata}
|
||||
|
||||
\verbatimDiagram{automata}
|
||||
|
||||
\end{document}
|
@ -1,9 +1,19 @@
|
||||
\babel@toc {english}{}
|
||||
\contentsline {section}{\numberline {1}hasse}{1}{section.1}%
|
||||
\contentsline {section}{\numberline {2}automata}{2}{section.2}%
|
||||
\contentsline {section}{\numberline {3}truthtable}{3}{section.3}%
|
||||
\contentsline {section}{\numberline {4}undirectedGraph}{4}{section.4}%
|
||||
\contentsline {section}{\numberline {5}complete6}{5}{section.5}%
|
||||
\contentsline {section}{\numberline {6}adjacency}{6}{section.6}%
|
||||
\contentsline {section}{\numberline {7}undirectedGraphToMatrix}{7}{section.7}%
|
||||
\contentsline {section}{\numberline {8}directedFromMatrix}{8}{section.8}%
|
||||
\contentsline {section}{\numberline {1}Propositional Logic}{2}{section.1}%
|
||||
\contentsline {subsection}{\numberline {1.1}truthtable}{2}{subsection.1.1}%
|
||||
\contentsline {section}{\numberline {2}Sets}{3}{section.2}%
|
||||
\contentsline {section}{\numberline {3}Relations}{3}{section.3}%
|
||||
\contentsline {subsection}{\numberline {3.1}proveEquivalence}{3}{subsection.3.1}%
|
||||
\contentsline {subsection}{\numberline {3.2}provePoset}{4}{subsection.3.2}%
|
||||
\contentsline {subsection}{\numberline {3.3}equivalenceDiagram}{5}{subsection.3.3}%
|
||||
\contentsline {subsection}{\numberline {3.4}hasse}{6}{subsection.3.4}%
|
||||
\contentsline {subsection}{\numberline {3.5}hasseDiagramByDivisibility}{7}{subsection.3.5}%
|
||||
\contentsline {section}{\numberline {4}Graph theory}{8}{section.4}%
|
||||
\contentsline {subsection}{\numberline {4.1}undirectedGraph}{8}{subsection.4.1}%
|
||||
\contentsline {subsection}{\numberline {4.2}directedGraph}{9}{subsection.4.2}%
|
||||
\contentsline {subsection}{\numberline {4.3}complete6}{10}{subsection.4.3}%
|
||||
\contentsline {subsection}{\numberline {4.4}adjacency}{11}{subsection.4.4}%
|
||||
\contentsline {subsection}{\numberline {4.5}undirectedGraphToMatrix}{12}{subsection.4.5}%
|
||||
\contentsline {subsection}{\numberline {4.6}directedFromMatrix}{13}{subsection.4.6}%
|
||||
\contentsline {section}{\numberline {5}Finite state automata}{14}{section.5}%
|
||||
\contentsline {subsection}{\numberline {5.1}automata}{14}{subsection.5.1}%
|
||||
|
Loading…
Reference in New Issue
Block a user