@@ -0,0 +1,373 @@ | |||
#!/usr/bin/env python3 | |||
import os | |||
import sys | |||
from argparse import ArgumentParser | |||
parser = ArgumentParser(prog='generate v1') | |||
parser.add_argument('module', type=str, | |||
help='the name of the module to generate') | |||
parser.add_argument('-i', '--input', type=str, | |||
help='The path and name of the input file') | |||
parser.add_argument('-o', '--output', type=str, | |||
help='The path of the output files') | |||
args = parser.parse_args() | |||
DIR = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
MODULE = args.module | |||
def isNotKeyword(str): | |||
keywords = [ | |||
"core", | |||
"ex", | |||
"recent", | |||
"service", | |||
"set", | |||
"toggle", | |||
"toggleNil", | |||
"vm", | |||
"$vm", | |||
"world", | |||
] | |||
return str not in keywords | |||
class CoreAction: | |||
def __init__(self, func, sink): | |||
self.func = func | |||
self.sink = sink | |||
def code(self): | |||
return f""" | |||
p.ctrl.m | |||
.compactMap {{ {self.func}($0) }} | |||
.receive(on: DispatchQueue.main) | |||
.sink {{ [weak core = p.core] v in {self.sink} }} | |||
.store(in: &p.core.subscriptions) | |||
""".replace("\n\n", "\n") | |||
class CorePipe: | |||
def __init__(self, name, props): | |||
self.name = name | |||
self.props = props | |||
self.resetMethod() | |||
self.resetDbg() | |||
self.resetSrc() | |||
self.resetExName() | |||
self.resetSteps() | |||
def code(self): | |||
return f""" | |||
p.ctrl.{self.method}( | |||
dbg: "{self.dbg}", | |||
sub: &p.core.subscriptions, | |||
{self.src}.eraseToAnyPublisher(), | |||
{self.steps} | |||
) | |||
""".replace("\n\n", "\n") | |||
def resetDbg(self): | |||
capitals = [l for l in self.name if l.isupper()] | |||
# Нет заглавных. | |||
if len(capitals) == 0: | |||
self.dbg = self.name | |||
return | |||
# Есть заглавные. | |||
firstCap = self.name.find(capitals[0]) | |||
self.dbg = self.name[:firstCap] + "".join(capitals) | |||
def resetExName(self): | |||
firstLetter = self.name[0].capitalize() | |||
self.exName = f"""ex{firstLetter}{self.name[1:]}""" | |||
def resetMethod(self): | |||
if "toggle" in self.props: | |||
self.method = "pipe" | |||
else: | |||
self.method = "pipeValue" | |||
def resetSrc(self): | |||
if "core" in self.props: | |||
self.src = "p.core." + self.name | |||
elif "world" in self.props: | |||
self.src = "p.world." + self.name | |||
elif "vm" in self.props: | |||
self.src = "p.core.vm." + self.name | |||
elif "$vm" in self.props: | |||
self.src = "p.core.vm.$" + self.name | |||
else: | |||
# Если это что-то неизвестное заранее, то ищем строку, | |||
# отличную от известных ключевых слов. | |||
self.src = next(filter(isNotKeyword, self.props)) | |||
def resetSteps(self): | |||
if "recent" and "ex" in self.props: | |||
self.steps = f""" | |||
{{ | |||
$0.{self.name}.value = $1 | |||
$0.{self.name}.isRecent = true | |||
}}, | |||
{{ | |||
$0.{self.name}.isRecent = false | |||
$0.{self.exName} = $1 | |||
}} | |||
""" | |||
elif "recent" in self.props: | |||
self.steps = f""" | |||
{{ | |||
$0.{self.name}.value = $1 | |||
$0.{self.name}.isRecent = true | |||
}}, | |||
{{ m, _ in m.{self.name}.isRecent = false }} | |||
""" | |||
elif "set" in self.props: | |||
self.steps = f""" | |||
{{ $0.{self.name} = $1 }} | |||
""" | |||
elif "toggle" in self.props: | |||
self.steps = f""" | |||
{{ $0.{self.name} = true }}, | |||
{{ $0.{self.name} = false }} | |||
""" | |||
elif "toggleNil" in self.props: | |||
self.steps = f""" | |||
{{ $0.{self.name} = $1 }}, | |||
{{ m, _ in m.{self.name} = nil }} | |||
""" | |||
class ServiceAction: | |||
def __init__(self, func, sink): | |||
self.func = func | |||
self.sink = sink | |||
def code(self): | |||
return f""" | |||
sp.ctrl.m | |||
.compactMap {{ {self.func}($0) }} | |||
.receive(on: DispatchQueue.main) | |||
.sink {{ v in {self.sink} }} | |||
.store(in: &sp.service.subscriptions) | |||
""".replace("\n\n", "\n") | |||
class ServicePipe: | |||
def __init__(self, name, props): | |||
self.name = name | |||
self.props = props | |||
self.resetMethod() | |||
self.resetDbg() | |||
self.resetSrc() | |||
self.resetExName() | |||
self.resetSteps() | |||
def code(self): | |||
return f""" | |||
sp.ctrl.{self.method}( | |||
dbg: "{self.dbg}", | |||
{self.src}.eraseToAnyPublisher(), | |||
{self.steps} | |||
) | |||
""".replace("\n\n", "\n") | |||
def resetDbg(self): | |||
capitals = [l for l in self.name if l.isupper()] | |||
# Нет заглавных. | |||
if len(capitals) == 0: | |||
self.dbg = self.name | |||
return | |||
# Есть заглавные. | |||
firstCap = self.name.find(capitals[0]) | |||
self.dbg = self.name[:firstCap] + "".join(capitals) | |||
def resetExName(self): | |||
firstLetter = self.name[0].capitalize() | |||
self.exName = f"""ex{firstLetter}{self.name[1:]}""" | |||
def resetMethod(self): | |||
if "toggle" in self.props: | |||
self.method = "pipe" | |||
else: | |||
self.method = "pipeValue" | |||
def resetSrc(self): | |||
if "service" in self.props: | |||
self.src = "sp.service." + self.name | |||
elif "world" in self.props: | |||
self.src = "sp.world." + self.name | |||
else: | |||
# Если это и не сервис, и не мир, то | |||
# ищем строку, отличную от известных ключевых слов. | |||
self.src = next(filter(isNotKeyword, self.props)) | |||
def resetSteps(self): | |||
if "recent" and "ex" in self.props: | |||
self.steps = f""" | |||
{{ | |||
$0.{self.name}.value = $1 | |||
$0.{self.name}.isRecent = true | |||
}}, | |||
{{ | |||
$0.{self.name}.isRecent = false | |||
$0.{self.exName} = $1 | |||
}} | |||
""" | |||
elif "recent" in self.props: | |||
self.steps = f""" | |||
{{ | |||
$0.{self.name}.value = $1 | |||
$0.{self.name}.isRecent = true | |||
}}, | |||
{{ m, _ in m.{self.name}.isRecent = false }} | |||
""" | |||
elif "set" in self.props: | |||
self.steps = f""" | |||
{{ $0.{self.name} = $1 }} | |||
""" | |||
elif "toggle" in self.props: | |||
self.steps = f""" | |||
{{ $0.{self.name} = true }}, | |||
{{ $0.{self.name} = false }} | |||
""" | |||
elif "toggleNil" in self.props: | |||
self.steps = f""" | |||
{{ $0.{self.name} = $1 }}, | |||
{{ m, _ in m.{self.name} = nil }} | |||
""" | |||
class State: | |||
isCoreAction = False | |||
isCoreController = False | |||
isServiceAction = False | |||
isServiceController = False | |||
def generatePlatform(coreActions, corePipes, serviceActions, servicePipes): | |||
corePlatform = "" | |||
if len(coreActions) > 0 or len(corePipes) > 0: | |||
corePlatform = f""" | |||
static func setupPlatform(_ p: CoreParameters) {{ | |||
{coreActions} | |||
{corePipes} | |||
}} | |||
""" | |||
return f""" | |||
// ВНИМАНИЕ: Сгенерировано автоматом, не менять руками! | |||
import Foundation | |||
extension {MODULE} {{ | |||
enum SectionGenerated {{ | |||
{corePlatform} | |||
static func setupPlatform(_ sp: ServiceParameters) {{ | |||
{serviceActions} | |||
{servicePipes} | |||
}} | |||
}} | |||
}} | |||
""" | |||
def generateCoreAction(ln): | |||
parts = ln.split(": ") | |||
if len(parts) != 2: | |||
return None | |||
func = parts[0].lstrip() | |||
sink = parts[1].rstrip() | |||
action = CoreAction(func, sink) | |||
return action.code() | |||
def generateCorePipe(ln): | |||
parts = ln.split(": [") | |||
if len(parts) != 2: | |||
return None | |||
name = parts[0].lstrip() | |||
props = parts[1][:-1].split(", ") | |||
pipe = CorePipe(name, props) | |||
return pipe.code() | |||
def generateServiceAction(ln): | |||
parts = ln.split(": ") | |||
if len(parts) != 2: | |||
return None | |||
func = parts[0].lstrip() | |||
sink = parts[1].rstrip() | |||
action = ServiceAction(func, sink) | |||
return action.code() | |||
def generateServicePipe(ln): | |||
parts = ln.split(": [") | |||
if len(parts) != 2: | |||
return None | |||
name = parts[0].lstrip() | |||
props = parts[1][:-1].split(", ") | |||
pipe = ServicePipe(name, props) | |||
return pipe.code() | |||
def readFile(fileName): | |||
lines = [] | |||
with open(fileName) as file: | |||
for line in file: | |||
lines.append(line.rstrip()) | |||
return lines | |||
def resetState(ln): | |||
if ln.startswith("ca:"): | |||
state.isCoreAction = True | |||
state.isCoreController = False | |||
state.isServiceAction = False | |||
state.isServiceController = False | |||
elif ln.startswith("cp:"): | |||
state.isCoreAction = False | |||
state.isCoreController = True | |||
state.isServiceAction = False | |||
state.isServiceController = False | |||
elif ln.startswith("sa:"): | |||
state.isCoreAction = False | |||
state.isCoreController = False | |||
state.isServiceAction = True | |||
state.isServiceController = False | |||
elif ln.startswith("sp:"): | |||
state.isCoreAction = False | |||
state.isCoreController = False | |||
state.isServiceAction = False | |||
state.isServiceController = True | |||
return state | |||
def saveFile(fileName, data): | |||
with open(fileName, "w") as file: | |||
file.write(data) | |||
def validateVersion(ln): | |||
if ln.startswith("version: 1") == False: | |||
print("ERROR: Invalid version") | |||
sys.exit(1) | |||
# Main | |||
state = State() | |||
print(f"Generating platform for module '{MODULE}'...") | |||
fileIn = args.input or f"{DIR}/../../../Modules/{MODULE}/{MODULE}.yml" | |||
fileOut = args.output or f"{DIR}/../../../Modules/{MODULE}/src" | |||
fileOut = os.path.join(fileOut, f'{MODULE}.SectionGenerated.swift') | |||
lines = readFile(fileIn) | |||
validateVersion(lines[0]) | |||
coreActions = "" | |||
corePipes = "" | |||
serviceActions = "" | |||
servicePipes = "" | |||
for ln in lines: | |||
st = resetState(ln) | |||
if st.isCoreAction: | |||
action = generateCoreAction(ln) | |||
if action: | |||
coreActions += action | |||
if st.isCoreController: | |||
pipe = generateCorePipe(ln) | |||
if pipe: | |||
corePipes += pipe | |||
if st.isServiceAction: | |||
action = generateServiceAction(ln) | |||
if action: | |||
serviceActions += action | |||
if st.isServiceController: | |||
pipe = generateServicePipe(ln) | |||
if pipe: | |||
servicePipes += pipe | |||
result = generatePlatform(coreActions, corePipes, serviceActions, servicePipes) | |||
saveFile(fileOut, result) |
@@ -0,0 +1,55 @@ | |||
#!/usr/bin/env python3 | |||
import os | |||
import sys | |||
from argparse import ArgumentParser | |||
from generation.generateStructure import * | |||
from generation.shortenName import * | |||
from generation.Result import * | |||
from parsing.parseLines import * | |||
from parsing.Structure import * | |||
DIR = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
# Импорт из общей для всех генераторов директории. | |||
sys.path.append(f"{DIR}/../common") | |||
from modulePaths import * | |||
from readFile import * | |||
parser = ArgumentParser(prog='generate v2') | |||
parser.add_argument('module', type=str, | |||
help='the name of the module to generate') | |||
parser.add_argument('-i', '--input', type=str, | |||
help='The path and name of the input file') | |||
parser.add_argument('-o', '--output', type=str, | |||
help='The path of the output files') | |||
parser.add_argument('-s', '--source', type=str, | |||
help='The path of the source files') | |||
args = parser.parse_args() | |||
(PATH, MODULE) = modulePaths(args.module) | |||
print(f"Generating platform for module '{PATH}'...") | |||
FILE_IN = args.input or f"{DIR}/../../../Modules/{PATH}/{MODULE}.yml" | |||
DIR_OUT = args.output or f"{DIR}/../../../Modules/{PATH}/src/" | |||
FILE_OUT = os.path.join(DIR_OUT, f"{MODULE}.Generated.swift") | |||
FILE_OUT_V1 = os.path.join(DIR_OUT, f"{MODULE}.SectionGenerated.swift") | |||
MODULE_SRC = args.source or f"{DIR}/../../../Modules/{PATH}/src" | |||
# Удаляем первую версию генерированного файла при его наличии. | |||
if os.path.isfile(FILE_OUT_V1): | |||
os.remove(FILE_OUT_V1) | |||
# Читаем файл и разбираем его на ключи-значения. | |||
lines = readFile(FILE_IN) | |||
structure = Structure() | |||
parseLines(lines, structure) | |||
# Генерируем код. | |||
result = Result(DIR, PATH, MODULE, readFile, shortenName, MODULE_SRC, structure) | |||
generateStructure(result) | |||
# Сохраняем файл. | |||
with open(FILE_OUT, "w") as file: | |||
file.write(result.file) |
@@ -0,0 +1,29 @@ | |||
class Result: | |||
def __init__(self, dir, path, module, readFile, shortenName, src, structure): | |||
self.dir = dir | |||
self.path = path | |||
self.module = module | |||
self.readFile = readFile | |||
self.shortenName = shortenName | |||
self.src = src | |||
self.structure = structure | |||
self.contextFields = "" | |||
self.core = "" | |||
self.coreSectionGenerated = "" | |||
self.coreSectionGeneratedActions = "" | |||
self.coreSectionGeneratedPipes = "" | |||
self.coreSectionsDestroy = "" | |||
self.coreSectionsSetup = "" | |||
self.coreVM = "" | |||
self.coreWindow = "" | |||
self.file = "" | |||
self.imports = "" | |||
self.modelFields = "" | |||
self.serviceCore = "" | |||
self.serviceSectionGenerated = "" | |||
self.serviceSectionGeneratedActions = "" | |||
self.serviceSectionGeneratedPipes = "" | |||
self.serviceSections = "" | |||
self.worldFields = "" | |||
self.worldParameters = "" |
@@ -0,0 +1,10 @@ | |||
from generation.isPipeRecent import * | |||
def fieldFormat(fmtPlain, fmtRecent, name, structure): | |||
fmt = fmtPlain | |||
if ( | |||
isPipeRecent(name, structure.core) or | |||
isPipeRecent(name, structure.service) | |||
): | |||
fmt = fmtRecent | |||
return fmt |
@@ -0,0 +1,18 @@ | |||
from generation.fieldFormat import * | |||
def generateContextFields(c): | |||
fileName = f"{c.dir}/templates/context-field" | |||
lines = c.readFile(fileName) | |||
fmtPlain = lines[0] | |||
fmtRecent = lines[1] | |||
fields = [] | |||
for key in c.structure.model.fields: | |||
values = c.structure.model.fields[key] | |||
fmt = fieldFormat(fmtPlain, fmtRecent, key, c.structure) | |||
ln = fmt \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", values[0]) | |||
fields.append(ln) | |||
c.contextFields = "\n".join(fields) |
@@ -0,0 +1,7 @@ | |||
def generateCore(c): | |||
fileName = f"{c.dir}/templates/core" | |||
lines = c.readFile(fileName) | |||
c.core = "\n".join(lines) | |||
fileName = f"{c.dir}/templates/service-core" | |||
c.serviceCore = c.readFile(fileName)[0] |
@@ -0,0 +1,4 @@ | |||
def generateCoreSectionGenerated(c): | |||
fileName = f"{c.dir}/templates/core-section-generated" | |||
lines = c.readFile(fileName) | |||
c.coreSectionGenerated = "\n".join(lines) |
@@ -0,0 +1,37 @@ | |||
from generation.sectionGeneratedActionShouldLoad import * | |||
def generateCoreSectionGeneratedActions(c): | |||
base = f"{c.dir}/templates/core-section-generated-action" | |||
fmtCommon = c.readFile(base) | |||
fmtInstant = c.readFile(f"{base}-instant") | |||
for key in c.structure.core.actions: | |||
value = c.structure.core.actions[key] | |||
# Шаблонные действия. | |||
if value == "": | |||
# shouldLoad*. | |||
shouldLoad = "shouldLoad" | |||
if key.startswith(shouldLoad): | |||
c.coreSectionGeneratedActions += sectionGeneratedActionShouldLoad(key, "core", c) | |||
continue | |||
continue | |||
output = "" | |||
action = key | |||
template = fmtCommon | |||
# Действие без receive(on:) | |||
if action.startswith("🚀"): | |||
action = action[1:] | |||
template = fmtInstant | |||
for fmt in template: | |||
ln = fmt \ | |||
.replace("%SHOULD%", action) \ | |||
.replace("%SINK%", value) | |||
output += ln + "\n" | |||
c.coreSectionGeneratedActions += output |
@@ -0,0 +1,19 @@ | |||
from generation.sectionFunctions import * | |||
from generation.sectionNames import * | |||
def generateCoreSectionsDestroy(c): | |||
fileName = f"{c.dir}/templates/core-section" | |||
fmt = c.readFile(fileName)[0] | |||
items = [] | |||
sections = sectionNames(c) | |||
for name in sections: | |||
# Пропускаем секции, не относящиеся к удалению ядра. | |||
funcs = sectionFunctions(name, c) | |||
if "destroyCore" not in funcs: | |||
continue | |||
ln = fmt.replace("%NAME%", name) | |||
items.append(ln) | |||
c.coreSectionsDestroy = "\n".join(items) |
@@ -0,0 +1,27 @@ | |||
from generation.hasSectionGenerated import * | |||
from generation.sectionFunctions import * | |||
from generation.sectionNames import * | |||
def generateCoreSectionsSetup(c): | |||
fileName = f"{c.dir}/templates/core-section" | |||
lines = c.readFile(fileName) | |||
fmtCore = lines[1] | |||
fmtPlatform = lines[2] | |||
items = [] | |||
sections = sectionNames(c) | |||
for name in sections: | |||
# Пропускаем секции, не относящиеся к установке ядра. | |||
funcs = sectionFunctions(name, c) | |||
if "setupCore" not in funcs: | |||
continue | |||
ln = fmtCore.replace("%NAME%", name) | |||
items.append(ln) | |||
# Генерированная секция. | |||
# Должна быть добавлена последней. | |||
if hasSectionGenerated(c.structure.core): | |||
items.append(fmtPlatform) | |||
c.coreSectionsSetup = "\n".join(items) |
@@ -0,0 +1,11 @@ | |||
from generation.sectionNames import * | |||
def generateCoreWindow(c): | |||
fileName = f"{c.dir}/templates/core-window" | |||
lines = c.readFile(fileName) | |||
fmtV = lines[0] | |||
fmtW = lines[1] | |||
sections = sectionNames(c) | |||
if "UI" in sections: | |||
c.coreVM = fmtV | |||
c.coreWindow = fmtW |
@@ -0,0 +1,28 @@ | |||
def generateFile(c): | |||
fileName = f"{c.dir}/templates/file" | |||
fmt = c.readFile(fileName) | |||
for ln in fmt: | |||
newLine = ln \ | |||
.replace("%CONTEXT_FIELDS%", c.contextFields) \ | |||
.replace("%CORE%", c.core) \ | |||
.replace("%CORE_SECTION_GENERATED%", c.coreSectionGenerated) \ | |||
.replace("%CORE_SECTION_GENERATED_ACTIONS%", c.coreSectionGeneratedActions) \ | |||
.replace("%CORE_SECTION_GENERATED_PIPES%", c.coreSectionGeneratedPipes) \ | |||
.replace("%CORE_SECTIONS_DESTROY%", c.coreSectionsDestroy) \ | |||
.replace("%CORE_SECTIONS_SETUP%", c.coreSectionsSetup) \ | |||
.replace("%CORE_VM%", c.coreVM) \ | |||
.replace("%CORE_WINDOW%", c.coreWindow) \ | |||
.replace("%IMPORTS%", c.imports) \ | |||
.replace("%MODEL_FIELDS%", c.modelFields) \ | |||
.replace("%SERVICE_CORE%", c.serviceCore) \ | |||
.replace("%SERVICE_SECTION_GENERATED%", c.serviceSectionGenerated) \ | |||
.replace("%SERVICE_SECTION_GENERATED_ACTIONS%", c.serviceSectionGeneratedActions) \ | |||
.replace("%SERVICE_SECTION_GENERATED_PIPES%", c.serviceSectionGeneratedPipes) \ | |||
.replace("%SERVICE_SECTIONS%", c.serviceSections) \ | |||
.replace("%WORLD_CONSTRUCTOR%", c.worldConstructor) \ | |||
.replace("%WORLD_FIELDS%", c.worldFields) \ | |||
.replace("%WORLD_PARAMETERS%", c.worldParameters) \ | |||
.replace("%MODULE%", c.module) \ | |||
.replace("%MODULE_SHORT%", c.shortenName(c.module)) # Замены %MODULE*% должны быть в конце. | |||
c.file += newLine + "\n" |
@@ -0,0 +1,16 @@ | |||
def generateImports(c): | |||
fileName = f"{c.src}/../{c.module}.podspec" | |||
# Для сборного модуля используем путь к корневому podspec. | |||
if c.path != c.module: | |||
parent = c.path.split("/")[0] | |||
fileName = fileName.replace(f"{c.module}/src/../{c.module}", parent) | |||
lines = c.readFile(fileName) | |||
items = ["Combine", "Foundation", "UIKit"] | |||
prefix = "s.dependency '" | |||
for ln in lines: | |||
if ln.startswith(prefix): | |||
name = ln[len(prefix):-1] | |||
items.append(name) | |||
c.imports = "import " + "\nimport ".join(sorted(items)) |
@@ -0,0 +1,19 @@ | |||
from generation.fieldFormat import * | |||
def generateModelFields(c): | |||
fileName = f"{c.dir}/templates/model-field" | |||
lines = c.readFile(fileName) | |||
fmtPlain = lines[0] | |||
fmtRecent = lines[1] | |||
fields = [] | |||
for key in c.structure.model.fields: | |||
values = c.structure.model.fields[key] | |||
fmt = fieldFormat(fmtPlain, fmtRecent, key, c.structure) | |||
ln = fmt \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", values[0]) \ | |||
.replace("%DEFAULT%", values[1]) | |||
fields.append(ln) | |||
c.modelFields = "\n".join(fields) |
@@ -0,0 +1,4 @@ | |||
def generateServiceSectionGenerated(c): | |||
fileName = f"{c.dir}/templates/service-section-generated" | |||
lines = c.readFile(fileName) | |||
c.serviceSectionGenerated = "\n".join(lines) |
@@ -0,0 +1,72 @@ | |||
from generation.sectionGeneratedActionShouldLoad import * | |||
def generateServiceSectionGeneratedActions(c): | |||
base = f"{c.dir}/templates/service-section-generated-action" | |||
fmtCommon = c.readFile(base) | |||
fmtInstant = c.readFile(f"{base}-instant") | |||
fmtRelay = c.readFile(f"{base}-relay") | |||
fmtInstantDelay = c.readFile(f"{base}-instant-delay") | |||
fmtInstantRelay = c.readFile(f"{base}-instant-relay") | |||
fmtInstantModel = c.readFile(f"{base}-instant-model") | |||
fmtInstantShouldResetCore = c.readFile(f"{base}-instant-shouldResetCore") | |||
fmtModel = c.readFile(f"{base}-model") | |||
fmtShouldResetCore = c.readFile(f"{base}-shouldResetCore") | |||
canRelay = "🚀model" in c.structure.service.actions | |||
if canRelay: | |||
c.serviceSectionGeneratedActions += "\n".join(fmtRelay + ['']) | |||
for key in c.structure.service.actions: | |||
value = c.structure.service.actions[key] | |||
# Шаблонные действия. | |||
if value == "": | |||
# instant model. | |||
if key == "🚀model": | |||
c.serviceSectionGeneratedActions += "\n".join(fmtInstantModel) + "\n" | |||
continue | |||
# model. | |||
if key == "model": | |||
c.serviceSectionGeneratedActions += "\n".join(fmtModel) + "\n" | |||
continue | |||
# shouldLoad*. | |||
shouldLoad = "shouldLoad" | |||
if key.startswith(shouldLoad): | |||
c.serviceSectionGeneratedActions += sectionGeneratedActionShouldLoad(key, "service", c) | |||
continue | |||
# instant shouldResetCore. | |||
if key == "🚀shouldResetCore": | |||
c.serviceSectionGeneratedActions += "\n".join(fmtInstantShouldResetCore) + "\n" | |||
continue | |||
# shouldResetCore. | |||
if key == "shouldResetCore": | |||
c.serviceSectionGeneratedActions += "\n".join(fmtShouldResetCore) + "\n" | |||
continue | |||
continue | |||
output = "" | |||
action = key | |||
template = fmtCommon | |||
# Действие без receive(on:) | |||
if action.startswith("🚀"): | |||
action = action[1:] | |||
template = fmtInstantRelay if canRelay else fmtInstant | |||
# Действие c .delay(on:) | |||
if action.startswith("⏳"): | |||
action = action[1:] | |||
template = fmtInstantDelay | |||
for fmt in template: | |||
ln = fmt \ | |||
.replace("%SHOULD%", action) \ | |||
.replace("%SINK%", value) | |||
output += ln + "\n" | |||
c.serviceSectionGeneratedActions += output |
@@ -0,0 +1,27 @@ | |||
from generation.hasSectionGenerated import * | |||
from generation.sectionFunctions import * | |||
from generation.sectionNames import * | |||
def generateServiceSections(c): | |||
fileName = f"{c.dir}/templates/service-section" | |||
lines = c.readFile(fileName) | |||
fmtService = lines[0] | |||
fmtPlatform = lines[1] | |||
items = [] | |||
sections = sectionNames(c) | |||
for name in sections: | |||
# Пропускаем секции, не относящиеся к сервису. | |||
funcs = sectionFunctions(name, c) | |||
if "setupService" not in funcs: | |||
continue | |||
ln = fmtService.replace("%NAME%", name) | |||
items.append(ln) | |||
# Генерированная секция. | |||
# Должна быть добавлена последней. | |||
if hasSectionGenerated(c.structure.service): | |||
items.append(fmtPlatform) | |||
c.serviceSections= "\n".join(items) |
@@ -0,0 +1,43 @@ | |||
from generation.generateContextFields import * | |||
from generation.generateCore import * | |||
from generation.generateCoreSectionGenerated import * | |||
from generation.generateCoreSectionGeneratedActions import * | |||
from generation.generateCoreSectionsDestroy import * | |||
from generation.generateCoreSectionsSetup import * | |||
from generation.generateCoreWindow import * | |||
from generation.generateFile import * | |||
from generation.generateImports import * | |||
from generation.generateModelFields import * | |||
from generation.generateServiceSectionGenerated import * | |||
from generation.generateServiceSectionGeneratedActions import * | |||
from generation.generateServiceSections import * | |||
from generation.generateWorldConstructor import * | |||
from generation.generateWorldFields import * | |||
from generation.generateWorldParameters import * | |||
from generation.hasSectionGenerated import * | |||
from generation.sectionGeneratedPipes import * | |||
def generateStructure(c): | |||
generateContextFields(c) | |||
generateImports(c) | |||
generateModelFields(c) | |||
# Генерируем ядро лишь при наличии инструкций в YML. | |||
if hasSectionGenerated(c.structure.core): | |||
generateCore(c) | |||
generateCoreSectionsDestroy(c) | |||
generateCoreSectionsSetup(c) | |||
generateCoreWindow(c) | |||
generateCoreSectionGenerated(c) | |||
generateCoreSectionGeneratedActions(c) | |||
c.coreSectionGeneratedPipes = sectionGeneratedPipes(c.structure.core, "&core.subscriptions", c) | |||
generateServiceSections(c) | |||
# Генерируем секцию сервиса лишь при наличии инструкций в YML. | |||
if hasSectionGenerated(c.structure.service): | |||
generateServiceSectionGenerated(c) | |||
generateServiceSectionGeneratedActions(c) | |||
c.serviceSectionGeneratedPipes = sectionGeneratedPipes(c.structure.service, "nil", c) | |||
generateWorldConstructor(c) | |||
generateWorldFields(c) | |||
generateWorldParameters(c) | |||
# Файл обязательно генерировать последним: зависит от остальных. | |||
generateFile(c) |
@@ -0,0 +1,18 @@ | |||
def generateWorldConstructor(c): | |||
fileName = f"{c.dir}/templates/world-constructor" | |||
fmt = c.readFile(fileName)[0] | |||
items = [] | |||
for key in c.structure.world.fields: | |||
values = c.structure.world.fields[key] | |||
if ( | |||
"init" in values or | |||
key == "model" or | |||
key == "net" | |||
): | |||
ln = fmt.replace("%NAME%", key) | |||
items.append(ln) | |||
c.worldConstructor = "\n".join(items) |
@@ -0,0 +1,77 @@ | |||
from generation.worldFieldTypeInit import * | |||
from generation.worldFieldTypePS import * | |||
def generateWorldFields(c): | |||
fileName = f"{c.dir}/templates/world-field" | |||
lines = c.readFile(fileName) | |||
fmtInitType = lines[0] | |||
fmtCVS = lines[1] | |||
fmtInit = lines[2] | |||
fmtModel = lines[3] | |||
fmtNet = lines[4] | |||
fmtPS = lines[5] | |||
fmtVar = lines[6] | |||
fields = [] | |||
for key in c.structure.world.fields: | |||
values = c.structure.world.fields[key] | |||
# [TYPE, DEFAULT, cvs] -> CurrentValueSubject | |||
if "cvs" in values: | |||
type = values[0] | |||
default = values[1] | |||
ln = fmtCVS \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", type) \ | |||
.replace("%DEFAULT%", default) | |||
fields.append(ln) | |||
# [escape, init], [TYPE, escape, init] -> let TYPE | |||
elif "escape" in values and "init" in values: | |||
type = worldFieldTypeInit(key, c.structure) | |||
fmt = fmtInit | |||
if len(values) == 3: | |||
fmt = fmtInitType | |||
ln = fmt \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", type) | |||
fields.append(ln) | |||
# [init], [TYPE, init] -> let TYPE | |||
elif "init" in values: | |||
type = worldFieldTypeInit(key, c.structure) | |||
fmt = fmtInit | |||
if len(values) == 2: | |||
fmt = fmtInitType | |||
ln = fmt \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", type) | |||
fields.append(ln) | |||
# model -> PassthroughSubject<Model> | |||
elif key == "model": | |||
fields.append(fmtModel) | |||
# net -> Net.Publisher | |||
elif key == "net": | |||
fields.append(fmtNet) | |||
# [ps], [TYPE, ps] -> PassthroughSubject | |||
elif "ps" in values: | |||
type = worldFieldTypePS(key, c.structure) | |||
ln = fmtPS \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", type) | |||
fields.append(ln) | |||
# [TYPE, DEFAULT, var] -> var TYPE | |||
elif "var" in values: | |||
type = values[0] | |||
default = values[1] | |||
ln = fmtVar \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", type) \ | |||
.replace("%DEFAULT%", default) | |||
fields.append(ln) | |||
c.worldFields = "\n".join(fields) |
@@ -0,0 +1,35 @@ | |||
from generation.worldFieldTypeInit import * | |||
def generateWorldParameters(c): | |||
fileName = f"{c.dir}/templates/world-parameter" | |||
lines = c.readFile(fileName) | |||
fmtInitType = lines[0] | |||
fmtEscInitType = lines[1] | |||
fmtInit = lines[2] | |||
fmtModel = lines[3] | |||
fmtNet = lines[4] | |||
params = [] | |||
for key in c.structure.world.fields: | |||
values = c.structure.world.fields[key] | |||
if "init" in values: | |||
type = worldFieldTypeInit(key, c.structure) | |||
fmt = fmtInit | |||
if "escape" in values: | |||
fmt = fmtEscInitType | |||
elif len(values) > 1: | |||
fmt = fmtInitType | |||
ln = fmt \ | |||
.replace("%NAME%", key) \ | |||
.replace("%TYPE%", type) | |||
params.append(ln) | |||
elif key == "model": | |||
params.append(fmtModel) | |||
elif key == "net": | |||
params.append(fmtNet) | |||
c.worldParameters = ",\n".join(params) |
@@ -0,0 +1,2 @@ | |||
def hasSectionGenerated(entity): | |||
return len(entity.actions) or len(entity.pipes) or entity.isPresent |
@@ -0,0 +1,11 @@ | |||
def isNotKeyword(str): | |||
keywords = [ | |||
"ex", | |||
"recent", | |||
"set", | |||
"toggle", | |||
"toggleNil", | |||
"vm", | |||
"$vm" | |||
] | |||
return str not in keywords |
@@ -0,0 +1,5 @@ | |||
def isPipeRecent(name, entity): | |||
if name in entity.pipes: | |||
props = entity.pipes[name] | |||
return "recent" in props | |||
return False |
@@ -0,0 +1,14 @@ | |||
def isToggle(name, structure): | |||
# Проверяем наличие `toggle` в свойствах канала ядра. | |||
if name in structure.core.pipes: | |||
props = structure.core.pipes[name] | |||
if "toggle" in props: | |||
return True | |||
# Проверяем наличие `toggle` в свойствах канала сервиса. | |||
if name in structure.service.pipes: | |||
props = structure.service.pipes[name] | |||
if "toggle" in props: | |||
return True | |||
return False |
@@ -0,0 +1,14 @@ | |||
def pipeFormat(fmtExRecent, fmtRecent, fmtSet, fmtToggle, fmtToggleNil, name, entity): | |||
props = entity.pipes[name] | |||
if "recent" and "ex" in props: | |||
return fmtExRecent | |||
elif "recent" in props: | |||
return fmtRecent | |||
elif "set" in props: | |||
return fmtSet | |||
elif "toggle" in props: | |||
return fmtToggle | |||
elif "toggleNil" in props: | |||
return fmtToggleNil | |||
return "НИНАЮ" |
@@ -0,0 +1,20 @@ | |||
from generation.isNotKeyword import * | |||
def pipeSource(name, entity): | |||
props = entity.pipes[name] | |||
if "vm" in props: | |||
return "core.vm." + name | |||
elif "$vm" in props: | |||
return "core.vm.$" + name | |||
else: | |||
# Если это что-то неизвестное заранее, то ищем строку, | |||
# отличную от известных ключевых слов для инструкции pipe. | |||
default = "world" | |||
src = next(filter(isNotKeyword, props), default) | |||
# Прямое обращение к VM. | |||
if src.startswith("vm."): | |||
src = "core." + src | |||
# Значение по умолчанию. | |||
elif src == default: | |||
return default + "." + name | |||
return src |
@@ -0,0 +1,14 @@ | |||
def sectionFunctions(name, c): | |||
path = f"{c.src}/{c.module}.Section{name}.swift" | |||
lines = c.readFile(path) | |||
items = [] | |||
for ln in lines: | |||
if ln.startswith(" static func destroyCore"): | |||
items.append("destroyCore") | |||
elif ln.startswith(" static func setupCore"): | |||
items.append("setupCore") | |||
elif ln.startswith(" static func setupService"): | |||
items.append("setupService") | |||
return items |
@@ -0,0 +1,14 @@ | |||
def sectionGeneratedActionShouldLoad(key, sub, c): | |||
lines = c.readFile(f"{c.dir}/templates/section-generated-action-shouldLoad") | |||
name = key[len("shouldLoad"):] | |||
request = name[0].lower() + name[1:] | |||
output = "" | |||
for fmt in lines: | |||
ln = fmt \ | |||
.replace("%NAME%", name) \ | |||
.replace("%REQUEST%", request) \ | |||
.replace("%SUB%", sub) | |||
output += ln + "\n" | |||
return output |
@@ -0,0 +1,42 @@ | |||
from generation.shortenName import * | |||
from generation.pipeFormat import * | |||
from generation.pipeSource import * | |||
def sectionGeneratedPipes(entity, sub, c): | |||
fmtExRecent = c.readFile(f"{c.dir}/templates/section-generated-pipe-ex-recent") | |||
fmtRecent = c.readFile(f"{c.dir}/templates/section-generated-pipe-recent") | |||
fmtSet = c.readFile(f"{c.dir}/templates/section-generated-pipe-set") | |||
fmtToggle = c.readFile(f"{c.dir}/templates/section-generated-pipe-toggle") | |||
fmtToggleNil = c.readFile(f"{c.dir}/templates/section-generated-pipe-toggleNil") | |||
output = "" | |||
for key in entity.pipes: | |||
values = entity.pipes[key] | |||
# EX_NAME. | |||
firstLetter = key[:1].capitalize() | |||
exName = f"""ex{firstLetter}{key[1:]}""" | |||
# PIPE. | |||
pipe = "pipeValue" | |||
if "toggle" in values: | |||
pipe = "pipe" | |||
# SHORT_SRC. | |||
shortSrc = shortenName(key) | |||
# SRC. | |||
src = pipeSource(key, entity) | |||
fmtPipe = pipeFormat(fmtExRecent, fmtRecent, fmtSet, fmtToggle, fmtToggleNil, key, entity) | |||
for fmt in fmtPipe: | |||
ln = fmt \ | |||
.replace("%EX_NAME%", exName) \ | |||
.replace("%NAME%", key) \ | |||
.replace("%PIPE%", pipe) \ | |||
.replace("%SHORT_SRC%", shortSrc) \ | |||
.replace("%SRC%", src) \ | |||
.replace("%SUB%", sub) | |||
output += ln + "\n" | |||
return output |
@@ -0,0 +1,19 @@ | |||
import os | |||
def sectionNames(c): | |||
prefix = "Section" | |||
ending = ".swift" | |||
fileNames = os.listdir(c.src) | |||
items = [] | |||
for fileName in sorted(fileNames): | |||
# Пропускаем файлы, не являющиеся секциями. | |||
id = fileName.find(prefix) | |||
if id == -1: | |||
continue | |||
# Вычленяем имя секции из имени файла. | |||
name = fileName[id + len(prefix):-len(ending)] | |||
items.append(name) | |||
return items |
@@ -0,0 +1,24 @@ | |||
def shortenName(text): | |||
capitals = [l for l in text if l.isupper()] | |||
# Нет заглавных. | |||
if len(capitals) == 0: | |||
return text | |||
capId = 0 | |||
# Первая - заглавная. | |||
if text[0].isupper(): | |||
capId = 1 | |||
# Заглавная лишь первая. | |||
if ( | |||
capId == 1 and | |||
len(capitals) == 1 | |||
): | |||
return text | |||
# Убираем первое заглавное слово. | |||
if capId == 1: | |||
capitals = capitals[1:] | |||
# Есть ещё заглавные. | |||
firstCap = text.find(capitals[0]) | |||
return text[:firstCap] + "".join(capitals) |
@@ -0,0 +1,12 @@ | |||
from generation.isToggle import * | |||
_keywords = { "init", "var", "escape" } | |||
def worldFieldTypeInit(key, structure): | |||
values = structure.world.fields[key] | |||
values = [v for v in values if v not in _keywords] | |||
if len(values) == 1: | |||
return values[0] | |||
if isToggle(key, structure): | |||
return "Void" | |||
return structure.model.fields[key][0] |
@@ -0,0 +1,9 @@ | |||
from generation.isToggle import * | |||
def worldFieldTypePS(key, structure): | |||
values = structure.world.fields[key] | |||
if len(values) == 2: | |||
return values[0] | |||
if isToggle(key, structure): | |||
return "Void" | |||
return structure.model.fields[key][0] |
@@ -0,0 +1,39 @@ | |||
from parsing.readKeyValue import * | |||
from parsing.valueArray import * | |||
class CoreService: | |||
def __init__(self): | |||
self.actions = {} | |||
self.isPresent = False | |||
self.pipes = {} | |||
def parseAction(self, line): | |||
kv = readKeyValue(line) | |||
# В действии всегда должен быть хотя бы ключ. | |||
if kv is None: | |||
return | |||
# Лишь ключ. | |||
if kv[1] is None: | |||
self.actions[kv[0]] = "" | |||
return | |||
# Ключ и значение. | |||
self.actions[kv[0]] = kv[1] | |||
def parsePipe(self, line): | |||
kv = readKeyValue(line) | |||
# В канале всегда должны быть и ключ, и значение. | |||
if ( | |||
kv is None or | |||
kv[1] is None | |||
): | |||
return | |||
values = valueArray(kv[1]) | |||
# В канале каждое значение должно быть массивом. | |||
if values is None: | |||
return | |||
self.pipes[kv[0]] = values |
@@ -0,0 +1,31 @@ | |||
class Mode: | |||
def __init__(self): | |||
self.reset() | |||
def parseLine(self, line): | |||
if line.startswith("model:"): | |||
self.reset() | |||
self.isModel = True | |||
elif line.startswith("core:"): | |||
self.reset() | |||
self.isCore = True | |||
elif line.startswith("service:"): | |||
self.reset() | |||
self.isService = True | |||
elif line.startswith("world:"): | |||
self.reset() | |||
self.isWorld = True | |||
elif line.startswith(" actions:"): | |||
self.isAction = True | |||
self.isPipe = False | |||
elif line.startswith(" pipes:"): | |||
self.isAction = False | |||
self.isPipe = True | |||
def reset(self): | |||
self.isAction = False | |||
self.isCore = False | |||
self.isModel = False | |||
self.isPipe = False | |||
self.isService = False | |||
self.isWorld = False |
@@ -0,0 +1,24 @@ | |||
from parsing.readKeyValue import * | |||
from parsing.valueArray import * | |||
class ModelWorld: | |||
def __init__(self): | |||
self.fields = {} | |||
def parseLine(self, line): | |||
kv = readKeyValue(line) | |||
# В модели/мире всегда должен быть хотя бы ключ. | |||
if kv is None: | |||
return | |||
# Лишь ключ. | |||
if kv[1] is None: | |||
self.fields[kv[0]] = [] | |||
return | |||
values = valueArray(kv[1]) | |||
# В модели/мире указанное значение должно быть массивом. | |||
if values is None: | |||
return | |||
self.fields[kv[0]] = values |
@@ -0,0 +1,9 @@ | |||
from parsing.CoreService import * | |||
from parsing.ModelWorld import * | |||
class Structure: | |||
def __init__(self): | |||
self.core = CoreService() | |||
self.model = ModelWorld() | |||
self.service = CoreService() | |||
self.world = ModelWorld() |
@@ -0,0 +1,42 @@ | |||
from parsing.CoreService import * | |||
from parsing.Mode import * | |||
from parsing.ModelWorld import * | |||
def parseLines(lines, structure): | |||
mode = Mode() | |||
for line in lines: | |||
# Определяем режим строки. | |||
mode.parseLine(line) | |||
ln = line.strip() | |||
# Игнорируем пустую строку. | |||
if len(ln) == 0: | |||
continue | |||
# Игнорируем комментарий. | |||
if ln.startswith("#"): | |||
continue | |||
# model. | |||
if mode.isModel: | |||
structure.model.parseLine(ln) | |||
# core. | |||
elif mode.isCore: | |||
structure.core.isPresent = True | |||
if mode.isAction: | |||
structure.core.parseAction(ln) | |||
elif mode.isPipe: | |||
structure.core.parsePipe(ln) | |||
# service. | |||
elif mode.isService: | |||
structure.service.isPresent = True | |||
if mode.isAction: | |||
structure.service.parseAction(ln) | |||
elif mode.isPipe: | |||
structure.service.parsePipe(ln) | |||
# world. | |||
elif mode.isWorld: | |||
structure.world.parseLine(ln) |
@@ -0,0 +1,15 @@ | |||
def readKeyValue(line): | |||
parts = line.split(": ") | |||
# Ключ со значением. | |||
if len(parts) == 2: | |||
return (parts[0], parts[1]) | |||
# Ключ без значения. | |||
if ( | |||
line.endswith(":") == False and | |||
len(parts) == 1 | |||
): | |||
return (parts[0], None) | |||
# Не является ключом со значением. | |||
return None |
@@ -0,0 +1,12 @@ | |||
def valueArray(value): | |||
# Удостоверяем наличие скобок. | |||
if not ( | |||
value.startswith("[") and | |||
value.endswith("]") | |||
): | |||
return None | |||
# Исключаем скобки. | |||
noBrackets = value[1:-1] | |||
parts = noBrackets.split(", ") | |||
return parts |
@@ -0,0 +1,2 @@ | |||
var %NAME%: %TYPE% { get } | |||
var %NAME%: MPAK.Recent<%TYPE%> { get } |
@@ -0,0 +1,17 @@ | |||
// MARK: - Core | |||
final class Core { | |||
%CORE_VM% | |||
var subscriptions = [AnyCancellable]() | |||
%CORE_WINDOW% | |||
deinit { | |||
/**/aelog("😟 %MODULE_SHORT%Core.deinit") | |||
%CORE_SECTIONS_DESTROY% | |||
} | |||
init(_ ctrl: %MODULE%.Controller, _ world: %MODULE%.World) { | |||
/**/aelog("😀 %MODULE_SHORT%Core.init") | |||
%CORE_SECTIONS_SETUP% | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
Section%NAME%.destroyCore(self) | |||
Section%NAME%.setupCore(self, ctrl, world) | |||
SectionGenerated.setupPlatform(self, ctrl, world) |
@@ -0,0 +1,15 @@ | |||
// MARK: - SectionGenerated Core | |||
static func setupPlatform( | |||
_ core: Core, | |||
_ ctrl: Controller, | |||
_ world: World | |||
) { | |||
// MARK: - SectionGenerated Core Actions | |||
%CORE_SECTION_GENERATED_ACTIONS% | |||
// MARK: - SectionGenerated Core Pipes | |||
%CORE_SECTION_GENERATED_PIPES% | |||
} |
@@ -0,0 +1,5 @@ | |||
ctrl.m | |||
.compactMap { %SHOULD%($0) } | |||
.receive(on: DispatchQueue.main) | |||
.sink { [weak core] v in %SINK% } | |||
.store(in: &core.subscriptions) |
@@ -0,0 +1,4 @@ | |||
ctrl.m | |||
.compactMap { %SHOULD%($0) } | |||
.sink { [weak core] v in %SINK% } | |||
.store(in: &core.subscriptions) |
@@ -0,0 +1,2 @@ | |||
let vm = VM() | |||
var wnd: UIWindow? |
@@ -0,0 +1,68 @@ | |||
// ВНИМАНИЕ Сгенерировано автоматом из файла %MODULE%.yml | |||
// ВНИМАНИЕ Не менять руками! | |||
%IMPORTS% | |||
// MARK: - Context | |||
public protocol %MODULE%Context { | |||
%CONTEXT_FIELDS% | |||
} | |||
// MARK: - Controller | |||
extension %MODULE% { | |||
final class Controller: MPAK.Controller<%MODULE%.Model> { | |||
init() { | |||
super.init( | |||
%MODULE%.Model(), | |||
debugClassName: "%MODULE_SHORT%Ctrl", | |||
debugLog: { aelog($0) } | |||
) | |||
} | |||
} | |||
%CORE% | |||
// MARK: - Model | |||
public struct Model: %MODULE%Context { | |||
%MODEL_FIELDS% | |||
} | |||
// MARK: - Service | |||
public final class Service { | |||
let ctrl = Controller() | |||
let world: World | |||
%SERVICE_CORE% | |||
var subscriptions = [AnyCancellable]() | |||
static private(set) weak var singleton: Service? | |||
public init(_ world: World) { | |||
self.world = world | |||
Self.singleton = self | |||
%SERVICE_SECTIONS% | |||
} | |||
} | |||
// MARK: - World | |||
public struct World { | |||
%WORLD_FIELDS% | |||
public init( | |||
%WORLD_PARAMETERS% | |||
) { | |||
%WORLD_CONSTRUCTOR% | |||
} | |||
} | |||
enum SectionGenerated { | |||
%CORE_SECTION_GENERATED% | |||
%SERVICE_SECTION_GENERATED% | |||
} | |||
} |
@@ -0,0 +1,2 @@ | |||
public var %NAME%: %TYPE% = %DEFAULT% | |||
public var %NAME%: MPAK.Recent<%TYPE%> = .init(%DEFAULT%) |
@@ -0,0 +1,8 @@ | |||
ctrl.m | |||
.compactMap { shouldLoad%NAME%($0) } | |||
.flatMap { v -> AnyPublisher<Net.Result%NAME%, Never> in | |||
world.net().%REQUEST%(v).eraseToAnyPublisher() | |||
} | |||
.receive(on: DispatchQueue.main) | |||
.sink { v in world.result%NAME%.send(v) } | |||
.store(in: &%SUB%.subscriptions) |
@@ -0,0 +1,13 @@ | |||
ctrl.%PIPE%( | |||
dbg: "%SHORT_SRC%", | |||
sub: %SUB%, | |||
%SRC%.eraseToAnyPublisher(), | |||
{ | |||
$0.%NAME%.value = $1 | |||
$0.%NAME%.isRecent = true | |||
}, | |||
{ | |||
$0.%NAME%.isRecent = false | |||
$0.%EX_NAME% = $1 | |||
} | |||
) |
@@ -0,0 +1,13 @@ | |||
ctrl.%PIPE%( | |||
dbg: "%SHORT_SRC%", | |||
sub: %SUB%, | |||
%SRC%.eraseToAnyPublisher(), | |||
{ | |||
$0.%NAME%.value = $1 | |||
$0.%NAME%.isRecent = true | |||
}, | |||
{ m, _ in m.%NAME%.isRecent = false } | |||
) | |||
@@ -0,0 +1,13 @@ | |||
ctrl.%PIPE%( | |||
dbg: "%SHORT_SRC%", | |||
sub: %SUB%, | |||
%SRC%.eraseToAnyPublisher(), | |||
{ $0.%NAME% = $1 } | |||
) | |||
@@ -0,0 +1,13 @@ | |||
ctrl.%PIPE%( | |||
dbg: "%SHORT_SRC%", | |||
sub: %SUB%, | |||
%SRC%.eraseToAnyPublisher(), | |||
{ $0.%NAME% = true }, | |||
{ $0.%NAME% = false } | |||
) | |||
@@ -0,0 +1,13 @@ | |||
ctrl.%PIPE%( | |||
dbg: "%SHORT_SRC%", | |||
sub: %SUB%, | |||
%SRC%.eraseToAnyPublisher(), | |||
{ $0.%NAME% = $1 }, | |||
{ m, _ in m.%NAME% = nil } | |||
) | |||
@@ -0,0 +1 @@ | |||
var core: Core? |
@@ -0,0 +1,2 @@ | |||
Section%NAME%.setupService(ctrl, self, world) | |||
SectionGenerated.setupPlatform(ctrl, self, world) |
@@ -0,0 +1,15 @@ | |||
// MARK: - SectionGenerated Service | |||
static func setupPlatform( | |||
_ ctrl: Controller, | |||
_ service: Service, | |||
_ world: World | |||
) { | |||
// MARK: - SectionGenerated Service Actions | |||
%SERVICE_SECTION_GENERATED_ACTIONS% | |||
// MARK: - SectionGenerated Service Pipes | |||
%SERVICE_SECTION_GENERATED_PIPES% | |||
} |
@@ -0,0 +1,5 @@ | |||
ctrl.m | |||
.compactMap { %SHOULD%($0) } | |||
.receive(on: DispatchQueue.main) | |||
.sink { v in %SINK% } | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1,4 @@ | |||
ctrl.m | |||
.compactMap { %SHOULD%($0) } | |||
.sink { v in %SINK% } | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1,5 @@ | |||
ctrl.m | |||
.compactMap { %SHOULD%($0) } | |||
.delay(for: .seconds(0.3), scheduler: RunLoop.main) | |||
.sink { v in %SINK% } | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1,3 @@ | |||
ctrl.m | |||
.sink { v in world.model.send(v); modelRelay.send(v) } | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1,4 @@ | |||
modelRelay | |||
.compactMap { %SHOULD%($0) } | |||
.sink { v in %SINK% } | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1,7 @@ | |||
ctrl.m | |||
.compactMap { shouldResetCore($0) } | |||
.sink { v in | |||
service.core = v ? Core(ctrl, world) : nil | |||
world.isCoreRunning.send(v) | |||
} | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1,4 @@ | |||
ctrl.m | |||
.receive(on: DispatchQueue.main) | |||
.sink { v in world.model.send(v) } | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1 @@ | |||
let modelRelay = PassthroughSubject<Model, Never>() |
@@ -0,0 +1,8 @@ | |||
ctrl.m | |||
.compactMap { shouldResetCore($0) } | |||
.receive(on: DispatchQueue.main) | |||
.sink { v in | |||
service.core = v ? Core(ctrl, world) : nil | |||
world.isCoreRunning.send(v) | |||
} | |||
.store(in: &service.subscriptions) |
@@ -0,0 +1 @@ | |||
self.%NAME% = %NAME% |
@@ -0,0 +1,7 @@ | |||
let %NAME%: %TYPE% | |||
let %NAME% = CurrentValueSubject<%TYPE%, Never>(%DEFAULT%) | |||
let %NAME%: AnyPublisher<%TYPE%, Never> | |||
let model: PassthroughSubject<%MODULE%.Model, Never> | |||
let net: () -> Net.Publisher | |||
let %NAME% = PassthroughSubject<%TYPE%, Never>() | |||
var %NAME%: %TYPE% = %DEFAULT% |
@@ -0,0 +1,5 @@ | |||
_ %NAME%: %TYPE% | |||
_ %NAME%: @escaping %TYPE% | |||
_ %NAME%: AnyPublisher<%TYPE%, Never> | |||
_ model: PassthroughSubject<%MODULE%.Model, Never> | |||
_ net: @escaping () -> Net.Publisher |
@@ -0,0 +1,15 @@ | |||
class Mode: | |||
def __init__(self): | |||
self.reset() | |||
def parseLine(self, line): | |||
if line.startswith("src:"): | |||
self.reset() | |||
self.isSource = True | |||
elif line.startswith("replace:"): | |||
self.reset() | |||
self.isReplacement = True | |||
def reset(self): | |||
self.isSource = False | |||
self.isReplacement = False |
@@ -0,0 +1,12 @@ | |||
from readKeyValue import * | |||
class Replace: | |||
def __init__(self): | |||
self.items = { } | |||
def parseLine(self, line): | |||
kv = readKeyValue(line) | |||
# Игнорируем всё, что не является ключом со значением. | |||
if kv is None: | |||
return | |||
self.items[kv[0]] = kv[1] |
@@ -0,0 +1,5 @@ | |||
class Result: | |||
def __init__(self, structure): | |||
self.structure = structure | |||
self.file = "" |
@@ -0,0 +1,13 @@ | |||
from readKeyValue import * | |||
class Source: | |||
def __init__(self): | |||
self.name = "" | |||
def parseLine(self, line): | |||
kv = readKeyValue(line) | |||
# Игнорируем всё, что не является ключом со значением. | |||
if kv is None: | |||
return | |||
self.name = kv[1] |
@@ -0,0 +1,8 @@ | |||
from Replace import * | |||
from Source import * | |||
class Structure: | |||
def __init__(self): | |||
self.orig = "" | |||
self.replace = Replace() | |||
self.src = Source() |
@@ -0,0 +1,48 @@ | |||
#!/usr/bin/env python3 | |||
import os | |||
import sys | |||
from argparse import ArgumentParser | |||
from generateStructure import * | |||
from parseLines import * | |||
from readModuleSrc import * | |||
from Structure import * | |||
DIR = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
# Импорт из общей для всех генераторов директории. | |||
sys.path.append(f"{DIR}/../common") | |||
from modulePaths import * | |||
from readFile import * | |||
parser = ArgumentParser(prog='generate v3') | |||
parser.add_argument('module', type=str, | |||
help='the name of the module to generate') | |||
parser.add_argument('-i', '--input', type=str, | |||
help='The path and name of the input file') | |||
parser.add_argument('-o', '--output', type=str, | |||
help='The path of the output files') | |||
parser.add_argument('-s', '--source', type=str, | |||
help='The path of the source files') | |||
args = parser.parse_args() | |||
(PATH, MODULE) = modulePaths(args.module) | |||
print(f"Generating platform for module '{PATH}'...") | |||
FILE_IN = args.input or f"{DIR}/../../../Modules/{PATH}/{MODULE}.yml" | |||
DIR_OUT = args.output or f"{DIR}/../../../Modules/{PATH}/src/" | |||
FILE_OUT = os.path.join(DIR_OUT, f"{MODULE}.Generated.swift") | |||
# Читаем файл и разбираем его на ключи-значения. | |||
lines = readFile(FILE_IN) | |||
structure = Structure() | |||
parseLines(lines, structure) | |||
ORIG_SRC = args.source or f"{DIR}/../../../Modules/{structure.src.name}/src" | |||
structure.orig = readModuleSrc(ORIG_SRC, readFile) | |||
# Генерируем код. | |||
output = generateStructure(structure) | |||
# Сохраняем файл. | |||
with open(FILE_OUT, "w") as file: | |||
file.write(output) |
@@ -0,0 +1,9 @@ | |||
def generateStructure(structure): | |||
output = f"""// ВНИМАНИЕ Сгенерировано автоматом из файла {structure.src.name}.yml | |||
// ВНИМАНИЕ Не менять руками! | |||
""" | |||
output += structure.orig | |||
for key in structure.replace.items: | |||
value = structure.replace.items[key] | |||
output = output.replace(key, value) | |||
return output |
@@ -0,0 +1,23 @@ | |||
from Mode import * | |||
def parseLines(lines, structure): | |||
mode = Mode() | |||
for line in lines: | |||
# Определяем режим строки. | |||
mode.parseLine(line) | |||
ln = line.strip() | |||
# Игнорируем пустую строку. | |||
if len(ln) == 0: | |||
continue | |||
# Игнорируем комментарий. | |||
if ln.startswith("#"): | |||
continue | |||
# replace. | |||
if mode.isReplacement: | |||
structure.replace.parseLine(ln) | |||
# src. | |||
elif mode.isSource: | |||
structure.src.parseLine(ln) |
@@ -0,0 +1,8 @@ | |||
def readKeyValue(line): | |||
parts = line.split(": ") | |||
# Ключ со значением. | |||
if len(parts) == 2: | |||
return (parts[0], parts[1]) | |||
# Не является ключом со значением. | |||
return None |
@@ -0,0 +1,12 @@ | |||
import os | |||
def readModuleSrc(dir, readFile): | |||
fileNames = os.listdir(dir) | |||
contents = [] | |||
for fileName in sorted(fileNames): | |||
path = dir + "/" + fileName | |||
lines = readFile(path) | |||
contents.extend(lines) | |||
return "\n".join(contents) |
@@ -0,0 +1,13 @@ | |||
from readKeyValue import * | |||
class FeatureToggle: | |||
def __init__(self): | |||
self.link = None | |||
def parseLine(self, line): | |||
kv = readKeyValue(line) | |||
# Игнорируем всё, что не является ключом со значением. | |||
if kv is None: | |||
return | |||
self.link = kv[1] |
@@ -0,0 +1,11 @@ | |||
class Mode: | |||
def __init__(self): | |||
self.reset() | |||
def parseLine(self, line): | |||
if line.startswith("featureToggle:"): | |||
self.reset() | |||
self.isFeatureToggle = True | |||
def reset(self): | |||
self.isFeatureToggle = False |
@@ -0,0 +1,7 @@ | |||
from FeatureToggle import * | |||
class Structure: | |||
def __init__(self): | |||
self.dir = "" | |||
self.module = "" | |||
self.featureToggle = FeatureToggle() |
@@ -0,0 +1,28 @@ | |||
#!/usr/bin/env python3 | |||
import os | |||
import sys | |||
from Structure import * | |||
from generateFeatureToggle import * | |||
from parseLines import * | |||
DIR = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
MODULE = sys.argv[1] | |||
# Импорт из общей для всех генераторов директории. | |||
sys.path.append(f"{DIR}/../common") | |||
from readFile import * | |||
print(f"Генерируем сборный модуль '{MODULE}'...") | |||
MODULE_DIR = f"{DIR}/../../../Modules/{MODULE}" | |||
FILE_IN = f"{MODULE_DIR}/{MODULE}.yml" | |||
# Читаем файл и разбираем его на ключи-значения. | |||
lines = readFile(FILE_IN) | |||
structure = Structure() | |||
parseLines(lines, structure) | |||
structure.moduleDir = MODULE_DIR | |||
structure.module = MODULE | |||
# Генерируем модуль FeatureToggle. | |||
generateFeatureToggle(structure) |
@@ -0,0 +1,28 @@ | |||
from pathlib import Path | |||
def generateFeatureToggle(s): | |||
# Пропускаем генерацию FeatureToggle, если не указана ссылка ivcsdbg | |||
if s.featureToggle.link is None: | |||
return | |||
# Создаём директории модуля. | |||
dirs = f"{s.moduleDir}/{s.module}FeatureToggle/src" | |||
Path(dirs).mkdir(parents=True, exist_ok=True) | |||
# Создаём YML для генератора-3. | |||
content = f"""version: 3 | |||
# ВНИМАНИЕ Сгенерировано автоматом из файла {s.module}.yml | |||
# ВНИМАНИЕ Не менять руками! | |||
src: ChatsFeatureToggle | |||
replace: | |||
ChatsFeatureToggle: {s.module}FeatureToggle | |||
ChatsFTCtrl: {s.module}FTCtrl | |||
chats: {s.featureToggle.link} | |||
""" | |||
# Сохраняем YML. | |||
fileName = f"{s.moduleDir}/{s.module}FeatureToggle/{s.module}FeatureToggle.yml" | |||
with open(fileName, "w") as file: | |||
file.write(content) |
@@ -0,0 +1,20 @@ | |||
from Mode import * | |||
def parseLines(lines, structure): | |||
mode = Mode() | |||
for line in lines: | |||
# Определяем режим строки. | |||
mode.parseLine(line) | |||
ln = line.strip() | |||
# Игнорируем пустую строку. | |||
if len(ln) == 0: | |||
continue | |||
# Игнорируем комментарий. | |||
if ln.startswith("#"): | |||
continue | |||
# featureToggle. | |||
if mode.isFeatureToggle: | |||
structure.featureToggle.parseLine(ln) |
@@ -0,0 +1,8 @@ | |||
def readKeyValue(line): | |||
parts = line.split(": ") | |||
# Ключ со значением. | |||
if len(parts) == 2: | |||
return (parts[0], parts[1]) | |||
# Не является ключом со значением. | |||
return None |
@@ -0,0 +1,168 @@ | |||
ADFSRedirection | |||
ApiHostSave | |||
C4 | |||
C4/C4Ava | |||
C4/C4Attributes | |||
C4/C4Call | |||
C4/C4Canvas | |||
C4/C4Cleanup | |||
C4/C4Content | |||
C4/C4Data | |||
C4/C4Date | |||
C4/C4DeleteChat | |||
C4/C4Empty | |||
C4/C4FastScrollBtn | |||
C4/C4FeatureToggle | |||
C4/C4Group | |||
C4/C4LastMessage | |||
C4/C4LeaveChat | |||
C4/C4Navigation | |||
C4/C4NotificationStatus | |||
C4/C4QuickActions | |||
C4/C4Paging | |||
C4/C4Reload | |||
C4/C4Sorting | |||
C4/C4Status | |||
C4/C4SetNotifications | |||
C4/C4TextInputSearch | |||
C4/C4TextInputSearchCanvas | |||
C4/C4Title | |||
C4/C4Typing | |||
C4/C4Unread | |||
C4/C4SearchAttributes | |||
C4/C4SearchContent | |||
C4/C4SearchData | |||
C4/C4SearchEmpty | |||
C4/C4SearchPaging | |||
C4/C4SearchSorting | |||
ChatCallError | |||
ChatsAccessVerification | |||
ChatTitle | |||
ConnectionStatus | |||
IPC | |||
Login/LoginGodActivity | |||
Login/LoginStart | |||
LoginADFSAuth | |||
LoginADFSButtons | |||
LoginADFSProviders | |||
LoginADFSWebViewControlPanel | |||
LoginADFSWebView | |||
LoginMeetupId | |||
LoginMeetupIdBtn | |||
LoginHost | |||
M5Buttons | |||
M5I/M5ICanvas | |||
M5I/M5ICloseBtn | |||
M5I/M5IConferenceType | |||
M5I/M5IConferenceNumber | |||
M5I/M5IData | |||
M5I/M5IDeleteBtn | |||
M5I/M5IDescription | |||
M5I/M5IDetails | |||
M5I/M5IError | |||
M5I/M5IGuestLink | |||
M5I/M5IInviteResponseBtn | |||
M5I/M5IJoinBtn | |||
M5I/M5IJoinInfo | |||
M5I/M5ILinksBtn | |||
M5I/M5ILoading | |||
M5I/M5IMenu | |||
M5I/M5IReload | |||
M5I/M5IStatus | |||
M5I/M5IStatusAddon | |||
M5I/M5ITitle | |||
M5I/M5IType | |||
M5I/M5ITypeName | |||
M5I/M5ITypeUpdate | |||
M5ILinks/M5ILinksCanvas | |||
M5ILinks/M5ILinksCloseBtn | |||
M5ILinks/M5ILinksJoinByGuest | |||
M5ILinks/M5ILinksJoinByID | |||
M5ILinks/M5ILinksJoinByModerator | |||
M5ILinks/M5ILinksJoinByPhone | |||
M5ILinks/M5ILinksJoinBySpeaker | |||
M5ILinks/M5ILinksJoinByVvoip | |||
M5IPush/M5IPushExitAlert | |||
M5IPush/M5IPushNavigation | |||
M5IUsers/M5IUsersAttributes | |||
M5IUsers/M5IUsersAva | |||
M5IUsers/M5IUsersData | |||
M5IUsers/M5IUsersCleanup | |||
M5IUsers/M5IUsersContent | |||
M5IUsers/M5IUsersCount | |||
M5IUsers/M5IUsersCountOnline | |||
M5IUsers/M5IUsersInvitation | |||
M5IUsers/M5IUsersLoading | |||
M5IUsers/M5IUsersLoadingError | |||
M5IUsers/M5IUsersPaging | |||
M5IUsers/M5IUsersReload | |||
M5IUsers/M5IUsersRole | |||
M5IUsers/M5IUsersSorting | |||
M5IUsers/M5IUsersTitle | |||
M5IUsers/M5IUsersType | |||
M5ChatParticipantOnline | |||
M5Media/M5MediaData | |||
M5Media/M5MediaAttributes | |||
M5Media/M5MediaStatus | |||
M5Media/M5PresentationInfo | |||
M5Media/M5ScreenShare | |||
M5Media/M5SpeakerInfo | |||
M5Media/M5Streaming | |||
M5Media/M5VideoQuality | |||
M5Typing | |||
MeetupCallLogoutAlert | |||
MeetupLink/MeetupLinkSpinner | |||
MeetupGoToAnotherMeetupLink | |||
MeetupLinkApiHostRollback | |||
MeetupLinkLaunch | |||
Meetups/MeetupsAttributes | |||
Meetups/MeetupsData | |||
Meetups/MeetupsEmpty | |||
Meetups/MeetupsPaging | |||
Ori | |||
Password | |||
PasswordCanvas | |||
PasswordFeatureToggle | |||
ProtoM5Files | |||
Rooms | |||
RoomsAttributes | |||
RoomsAva | |||
RoomsCanvas | |||
RoomsCleanup | |||
RoomsData | |||
RoomsDuration | |||
RoomsEmpty | |||
RoomsJoinBtn | |||
RoomsLastDate | |||
RoomsOrganizer | |||
RoomsPages | |||
RoomsReload | |||
RoomsSorting | |||
RoomsTitle | |||
RoomsUsers | |||
S5Panel/S5PanelMeetups | |||
S5Panel/S5PanelRooms | |||
SystemInfo | |||
TimeDistance | |||
Toast | |||
WindowPriority |
@@ -0,0 +1,7 @@ | |||
def modulePaths(name): | |||
parts = name.split("/") | |||
if len(parts) == 2: | |||
return (name, parts[1]) | |||
else: | |||
return (name, name) | |||
@@ -0,0 +1,6 @@ | |||
def readFile(fileName): | |||
lines = [] | |||
with open(fileName) as file: | |||
for line in file: | |||
lines.append(line.rstrip()) | |||
return lines |
@@ -0,0 +1,30 @@ | |||
#!/usr/bin/env python3 | |||
import os | |||
import sys | |||
import subprocess | |||
from argparse import ArgumentParser | |||
parser = ArgumentParser() | |||
parser.add_argument('module', type=str, | |||
help='the name of the module to generate') | |||
parser.add_argument('-i', '--input', type=str, | |||
help='The path and name of the input file') | |||
args, _ = parser.parse_known_args() | |||
DIR = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
# Импорт из общей для всех генераторов директории. | |||
sys.path.append(f"{DIR}/common") | |||
from modulePaths import * | |||
(PATH, MODULE) = modulePaths(args.module) | |||
FILE_IN = args.input or f"{DIR}/../../Modules/{PATH}/{MODULE}.yml" | |||
# Запускаем указанную в файле YML версию генератора. | |||
with open(FILE_IN) as file: | |||
ln = file.readline().rstrip() | |||
version = ln[-1] | |||
cmd = f"{DIR}/{version}/generate" | |||
subprocess.call(args=[cmd] + sys.argv[1:]) |
@@ -0,0 +1,87 @@ | |||
#!/usr/bin/env python3 | |||
import os | |||
import sys | |||
import json | |||
import subprocess | |||
from datetime import datetime | |||
from hashlib import sha256 | |||
from tempfile import TemporaryDirectory | |||
from shutil import copyfile | |||
FILENAME = 'Platfile' | |||
LOCKFILE = FILENAME + '.lock' | |||
VERSION = '2.0' | |||
def initialize(dir: str): | |||
print('Initializing ', FILENAME) | |||
dir_modules = os.path.join(dir, '..', '..', 'Modules') | |||
path = os.path.join(dir, FILENAME) | |||
with open(path, 'w') as file: | |||
entries = os.listdir(dir_modules) | |||
for entry in entries: | |||
if os.path.isfile(os.path.join(dir_modules, entry, entry + '.yml')): | |||
file.write(entry + '\n') | |||
def generate(dir: str, generator: str): | |||
path = os.path.join(dir, FILENAME) | |||
with open(path) as file: | |||
modules = file.readlines() | |||
modules = set(m.strip() for m in modules if m.strip()) | |||
modules = sorted(modules) | |||
dir_modules = os.path.join(dir, '..', '..', 'Modules') | |||
print(f'Generating modules: {len(modules)}') | |||
generator = generator or os.path.join(dir, 'generate-platform') | |||
for module in modules: | |||
src_dir = os.path.join(dir_modules, module, 'src') | |||
with TemporaryDirectory() as tmpdir: | |||
subprocess.call([generator, module, '-o', tmpdir]) | |||
_merge_changes(tmpdir, src_dir) | |||
if os.path.exists(LOCKFILE): | |||
os.remove(LOCKFILE) | |||
def _merge_changes(src_dir: str, dst_dir: str): | |||
for file_name in os.listdir(src_dir): | |||
src_file = os.path.join(src_dir, file_name) | |||
src_hash = _hash_file(src_file) | |||
dst_file = os.path.join(dst_dir, file_name) | |||
if os.path.exists(dst_file): | |||
dst_hash = _hash_file(dst_file) | |||
if src_hash == dst_hash: | |||
return | |||
copyfile(src_file, dst_file) | |||
print('Updated file: ', file_name) | |||
def _hash_file(path: str): | |||
alg = sha256() | |||
with open(path, 'rb') as file: | |||
for line in file.readlines(): | |||
if line: | |||
alg.update(line) | |||
return alg.hexdigest() | |||
def main(): | |||
from argparse import ArgumentParser | |||
parser = ArgumentParser( | |||
prog='GeneratePlatforms', | |||
description='Automatic code generator' | |||
) | |||
parser.add_argument('-i', '--init', action='store_true') | |||
parser.add_argument('-g', '--generator', type=str) | |||
args = parser.parse_args() | |||
dir = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
if args.init: | |||
initialize(dir) | |||
else: | |||
generate(dir, args.generator) | |||
if __name__ == "__main__": | |||
main() |