@@ -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() |