diff --git a/Utilities/platform/1/generate b/Utilities/platform/1/generate new file mode 100755 index 0000000..cecd88b --- /dev/null +++ b/Utilities/platform/1/generate @@ -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) diff --git a/Utilities/platform/2/generate b/Utilities/platform/2/generate new file mode 100755 index 0000000..3cf34a2 --- /dev/null +++ b/Utilities/platform/2/generate @@ -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) diff --git a/Utilities/platform/2/generation/Result.py b/Utilities/platform/2/generation/Result.py new file mode 100644 index 0000000..e6df072 --- /dev/null +++ b/Utilities/platform/2/generation/Result.py @@ -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 = "" diff --git a/Utilities/platform/2/generation/fieldFormat.py b/Utilities/platform/2/generation/fieldFormat.py new file mode 100644 index 0000000..d66b310 --- /dev/null +++ b/Utilities/platform/2/generation/fieldFormat.py @@ -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 diff --git a/Utilities/platform/2/generation/generateContextFields.py b/Utilities/platform/2/generation/generateContextFields.py new file mode 100644 index 0000000..9ba21d8 --- /dev/null +++ b/Utilities/platform/2/generation/generateContextFields.py @@ -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) diff --git a/Utilities/platform/2/generation/generateCore.py b/Utilities/platform/2/generation/generateCore.py new file mode 100644 index 0000000..9e95b12 --- /dev/null +++ b/Utilities/platform/2/generation/generateCore.py @@ -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] diff --git a/Utilities/platform/2/generation/generateCoreSectionGenerated.py b/Utilities/platform/2/generation/generateCoreSectionGenerated.py new file mode 100644 index 0000000..86732ec --- /dev/null +++ b/Utilities/platform/2/generation/generateCoreSectionGenerated.py @@ -0,0 +1,4 @@ +def generateCoreSectionGenerated(c): + fileName = f"{c.dir}/templates/core-section-generated" + lines = c.readFile(fileName) + c.coreSectionGenerated = "\n".join(lines) diff --git a/Utilities/platform/2/generation/generateCoreSectionGeneratedActions.py b/Utilities/platform/2/generation/generateCoreSectionGeneratedActions.py new file mode 100644 index 0000000..28e6482 --- /dev/null +++ b/Utilities/platform/2/generation/generateCoreSectionGeneratedActions.py @@ -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 diff --git a/Utilities/platform/2/generation/generateCoreSectionsDestroy.py b/Utilities/platform/2/generation/generateCoreSectionsDestroy.py new file mode 100644 index 0000000..225d2e6 --- /dev/null +++ b/Utilities/platform/2/generation/generateCoreSectionsDestroy.py @@ -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) diff --git a/Utilities/platform/2/generation/generateCoreSectionsSetup.py b/Utilities/platform/2/generation/generateCoreSectionsSetup.py new file mode 100644 index 0000000..3bdbfc4 --- /dev/null +++ b/Utilities/platform/2/generation/generateCoreSectionsSetup.py @@ -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) diff --git a/Utilities/platform/2/generation/generateCoreWindow.py b/Utilities/platform/2/generation/generateCoreWindow.py new file mode 100644 index 0000000..d29b1e3 --- /dev/null +++ b/Utilities/platform/2/generation/generateCoreWindow.py @@ -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 diff --git a/Utilities/platform/2/generation/generateFile.py b/Utilities/platform/2/generation/generateFile.py new file mode 100644 index 0000000..0603e02 --- /dev/null +++ b/Utilities/platform/2/generation/generateFile.py @@ -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" diff --git a/Utilities/platform/2/generation/generateImports.py b/Utilities/platform/2/generation/generateImports.py new file mode 100644 index 0000000..1e7ae83 --- /dev/null +++ b/Utilities/platform/2/generation/generateImports.py @@ -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)) diff --git a/Utilities/platform/2/generation/generateModelFields.py b/Utilities/platform/2/generation/generateModelFields.py new file mode 100644 index 0000000..5131817 --- /dev/null +++ b/Utilities/platform/2/generation/generateModelFields.py @@ -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) diff --git a/Utilities/platform/2/generation/generateServiceSectionGenerated.py b/Utilities/platform/2/generation/generateServiceSectionGenerated.py new file mode 100644 index 0000000..561f004 --- /dev/null +++ b/Utilities/platform/2/generation/generateServiceSectionGenerated.py @@ -0,0 +1,4 @@ +def generateServiceSectionGenerated(c): + fileName = f"{c.dir}/templates/service-section-generated" + lines = c.readFile(fileName) + c.serviceSectionGenerated = "\n".join(lines) diff --git a/Utilities/platform/2/generation/generateServiceSectionGeneratedActions.py b/Utilities/platform/2/generation/generateServiceSectionGeneratedActions.py new file mode 100644 index 0000000..59522d9 --- /dev/null +++ b/Utilities/platform/2/generation/generateServiceSectionGeneratedActions.py @@ -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 diff --git a/Utilities/platform/2/generation/generateServiceSections.py b/Utilities/platform/2/generation/generateServiceSections.py new file mode 100644 index 0000000..47e4815 --- /dev/null +++ b/Utilities/platform/2/generation/generateServiceSections.py @@ -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) diff --git a/Utilities/platform/2/generation/generateStructure.py b/Utilities/platform/2/generation/generateStructure.py new file mode 100644 index 0000000..b545bd6 --- /dev/null +++ b/Utilities/platform/2/generation/generateStructure.py @@ -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) diff --git a/Utilities/platform/2/generation/generateWorldConstructor.py b/Utilities/platform/2/generation/generateWorldConstructor.py new file mode 100644 index 0000000..3072c63 --- /dev/null +++ b/Utilities/platform/2/generation/generateWorldConstructor.py @@ -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) diff --git a/Utilities/platform/2/generation/generateWorldFields.py b/Utilities/platform/2/generation/generateWorldFields.py new file mode 100644 index 0000000..dd5c67e --- /dev/null +++ b/Utilities/platform/2/generation/generateWorldFields.py @@ -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 + 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) diff --git a/Utilities/platform/2/generation/generateWorldParameters.py b/Utilities/platform/2/generation/generateWorldParameters.py new file mode 100644 index 0000000..da6c8c9 --- /dev/null +++ b/Utilities/platform/2/generation/generateWorldParameters.py @@ -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) diff --git a/Utilities/platform/2/generation/hasSectionGenerated.py b/Utilities/platform/2/generation/hasSectionGenerated.py new file mode 100644 index 0000000..3a568e8 --- /dev/null +++ b/Utilities/platform/2/generation/hasSectionGenerated.py @@ -0,0 +1,2 @@ +def hasSectionGenerated(entity): + return len(entity.actions) or len(entity.pipes) or entity.isPresent diff --git a/Utilities/platform/2/generation/isNotKeyword.py b/Utilities/platform/2/generation/isNotKeyword.py new file mode 100644 index 0000000..5812858 --- /dev/null +++ b/Utilities/platform/2/generation/isNotKeyword.py @@ -0,0 +1,11 @@ +def isNotKeyword(str): + keywords = [ + "ex", + "recent", + "set", + "toggle", + "toggleNil", + "vm", + "$vm" + ] + return str not in keywords diff --git a/Utilities/platform/2/generation/isPipeRecent.py b/Utilities/platform/2/generation/isPipeRecent.py new file mode 100644 index 0000000..6ad9933 --- /dev/null +++ b/Utilities/platform/2/generation/isPipeRecent.py @@ -0,0 +1,5 @@ +def isPipeRecent(name, entity): + if name in entity.pipes: + props = entity.pipes[name] + return "recent" in props + return False diff --git a/Utilities/platform/2/generation/isToggle.py b/Utilities/platform/2/generation/isToggle.py new file mode 100644 index 0000000..21985ac --- /dev/null +++ b/Utilities/platform/2/generation/isToggle.py @@ -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 diff --git a/Utilities/platform/2/generation/pipeFormat.py b/Utilities/platform/2/generation/pipeFormat.py new file mode 100644 index 0000000..4c08641 --- /dev/null +++ b/Utilities/platform/2/generation/pipeFormat.py @@ -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 "НИНАЮ" diff --git a/Utilities/platform/2/generation/pipeSource.py b/Utilities/platform/2/generation/pipeSource.py new file mode 100644 index 0000000..33374a1 --- /dev/null +++ b/Utilities/platform/2/generation/pipeSource.py @@ -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 diff --git a/Utilities/platform/2/generation/sectionFunctions.py b/Utilities/platform/2/generation/sectionFunctions.py new file mode 100644 index 0000000..5e0e912 --- /dev/null +++ b/Utilities/platform/2/generation/sectionFunctions.py @@ -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 diff --git a/Utilities/platform/2/generation/sectionGeneratedActionShouldLoad.py b/Utilities/platform/2/generation/sectionGeneratedActionShouldLoad.py new file mode 100644 index 0000000..250822f --- /dev/null +++ b/Utilities/platform/2/generation/sectionGeneratedActionShouldLoad.py @@ -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 diff --git a/Utilities/platform/2/generation/sectionGeneratedPipes.py b/Utilities/platform/2/generation/sectionGeneratedPipes.py new file mode 100644 index 0000000..095e2a0 --- /dev/null +++ b/Utilities/platform/2/generation/sectionGeneratedPipes.py @@ -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 diff --git a/Utilities/platform/2/generation/sectionNames.py b/Utilities/platform/2/generation/sectionNames.py new file mode 100644 index 0000000..50313f1 --- /dev/null +++ b/Utilities/platform/2/generation/sectionNames.py @@ -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 diff --git a/Utilities/platform/2/generation/shortenName.py b/Utilities/platform/2/generation/shortenName.py new file mode 100644 index 0000000..38ab4d6 --- /dev/null +++ b/Utilities/platform/2/generation/shortenName.py @@ -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) diff --git a/Utilities/platform/2/generation/worldFieldTypeInit.py b/Utilities/platform/2/generation/worldFieldTypeInit.py new file mode 100644 index 0000000..7937dfb --- /dev/null +++ b/Utilities/platform/2/generation/worldFieldTypeInit.py @@ -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] diff --git a/Utilities/platform/2/generation/worldFieldTypePS.py b/Utilities/platform/2/generation/worldFieldTypePS.py new file mode 100644 index 0000000..222e8ac --- /dev/null +++ b/Utilities/platform/2/generation/worldFieldTypePS.py @@ -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] diff --git a/Utilities/platform/2/parsing/CoreService.py b/Utilities/platform/2/parsing/CoreService.py new file mode 100644 index 0000000..3ccd0c1 --- /dev/null +++ b/Utilities/platform/2/parsing/CoreService.py @@ -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 diff --git a/Utilities/platform/2/parsing/Mode.py b/Utilities/platform/2/parsing/Mode.py new file mode 100644 index 0000000..609a12d --- /dev/null +++ b/Utilities/platform/2/parsing/Mode.py @@ -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 diff --git a/Utilities/platform/2/parsing/ModelWorld.py b/Utilities/platform/2/parsing/ModelWorld.py new file mode 100644 index 0000000..11f0394 --- /dev/null +++ b/Utilities/platform/2/parsing/ModelWorld.py @@ -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 diff --git a/Utilities/platform/2/parsing/Structure.py b/Utilities/platform/2/parsing/Structure.py new file mode 100644 index 0000000..e74fb95 --- /dev/null +++ b/Utilities/platform/2/parsing/Structure.py @@ -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() diff --git a/Utilities/platform/2/parsing/parseLines.py b/Utilities/platform/2/parsing/parseLines.py new file mode 100644 index 0000000..d473879 --- /dev/null +++ b/Utilities/platform/2/parsing/parseLines.py @@ -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) diff --git a/Utilities/platform/2/parsing/readKeyValue.py b/Utilities/platform/2/parsing/readKeyValue.py new file mode 100644 index 0000000..d6f1768 --- /dev/null +++ b/Utilities/platform/2/parsing/readKeyValue.py @@ -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 diff --git a/Utilities/platform/2/parsing/valueArray.py b/Utilities/platform/2/parsing/valueArray.py new file mode 100644 index 0000000..16d797f --- /dev/null +++ b/Utilities/platform/2/parsing/valueArray.py @@ -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 diff --git a/Utilities/platform/2/templates/context-field b/Utilities/platform/2/templates/context-field new file mode 100644 index 0000000..8cce3b9 --- /dev/null +++ b/Utilities/platform/2/templates/context-field @@ -0,0 +1,2 @@ + var %NAME%: %TYPE% { get } + var %NAME%: MPAK.Recent<%TYPE%> { get } diff --git a/Utilities/platform/2/templates/core b/Utilities/platform/2/templates/core new file mode 100644 index 0000000..4f6650e --- /dev/null +++ b/Utilities/platform/2/templates/core @@ -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% + } + } diff --git a/Utilities/platform/2/templates/core-section b/Utilities/platform/2/templates/core-section new file mode 100644 index 0000000..83486c4 --- /dev/null +++ b/Utilities/platform/2/templates/core-section @@ -0,0 +1,3 @@ + Section%NAME%.destroyCore(self) + Section%NAME%.setupCore(self, ctrl, world) + SectionGenerated.setupPlatform(self, ctrl, world) diff --git a/Utilities/platform/2/templates/core-section-generated b/Utilities/platform/2/templates/core-section-generated new file mode 100644 index 0000000..ce2cd32 --- /dev/null +++ b/Utilities/platform/2/templates/core-section-generated @@ -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% + } diff --git a/Utilities/platform/2/templates/core-section-generated-action b/Utilities/platform/2/templates/core-section-generated-action new file mode 100644 index 0000000..95d69cf --- /dev/null +++ b/Utilities/platform/2/templates/core-section-generated-action @@ -0,0 +1,5 @@ + ctrl.m + .compactMap { %SHOULD%($0) } + .receive(on: DispatchQueue.main) + .sink { [weak core] v in %SINK% } + .store(in: &core.subscriptions) diff --git a/Utilities/platform/2/templates/core-section-generated-action-instant b/Utilities/platform/2/templates/core-section-generated-action-instant new file mode 100644 index 0000000..d71c021 --- /dev/null +++ b/Utilities/platform/2/templates/core-section-generated-action-instant @@ -0,0 +1,4 @@ + ctrl.m + .compactMap { %SHOULD%($0) } + .sink { [weak core] v in %SINK% } + .store(in: &core.subscriptions) diff --git a/Utilities/platform/2/templates/core-window b/Utilities/platform/2/templates/core-window new file mode 100644 index 0000000..9c02bae --- /dev/null +++ b/Utilities/platform/2/templates/core-window @@ -0,0 +1,2 @@ + let vm = VM() + var wnd: UIWindow? diff --git a/Utilities/platform/2/templates/file b/Utilities/platform/2/templates/file new file mode 100644 index 0000000..fcc6ba2 --- /dev/null +++ b/Utilities/platform/2/templates/file @@ -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% + + } +} diff --git a/Utilities/platform/2/templates/model-field b/Utilities/platform/2/templates/model-field new file mode 100644 index 0000000..57aa271 --- /dev/null +++ b/Utilities/platform/2/templates/model-field @@ -0,0 +1,2 @@ + public var %NAME%: %TYPE% = %DEFAULT% + public var %NAME%: MPAK.Recent<%TYPE%> = .init(%DEFAULT%) diff --git a/Utilities/platform/2/templates/section-generated-action-shouldLoad b/Utilities/platform/2/templates/section-generated-action-shouldLoad new file mode 100644 index 0000000..dab3b29 --- /dev/null +++ b/Utilities/platform/2/templates/section-generated-action-shouldLoad @@ -0,0 +1,8 @@ + ctrl.m + .compactMap { shouldLoad%NAME%($0) } + .flatMap { v -> AnyPublisher in + world.net().%REQUEST%(v).eraseToAnyPublisher() + } + .receive(on: DispatchQueue.main) + .sink { v in world.result%NAME%.send(v) } + .store(in: &%SUB%.subscriptions) diff --git a/Utilities/platform/2/templates/section-generated-pipe-ex-recent b/Utilities/platform/2/templates/section-generated-pipe-ex-recent new file mode 100644 index 0000000..f983d05 --- /dev/null +++ b/Utilities/platform/2/templates/section-generated-pipe-ex-recent @@ -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 + } + ) diff --git a/Utilities/platform/2/templates/section-generated-pipe-recent b/Utilities/platform/2/templates/section-generated-pipe-recent new file mode 100644 index 0000000..6e911d2 --- /dev/null +++ b/Utilities/platform/2/templates/section-generated-pipe-recent @@ -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 } + ) + + + diff --git a/Utilities/platform/2/templates/section-generated-pipe-set b/Utilities/platform/2/templates/section-generated-pipe-set new file mode 100644 index 0000000..5c6a802 --- /dev/null +++ b/Utilities/platform/2/templates/section-generated-pipe-set @@ -0,0 +1,13 @@ + ctrl.%PIPE%( + dbg: "%SHORT_SRC%", + sub: %SUB%, + %SRC%.eraseToAnyPublisher(), + { $0.%NAME% = $1 } + ) + + + + + + + diff --git a/Utilities/platform/2/templates/section-generated-pipe-toggle b/Utilities/platform/2/templates/section-generated-pipe-toggle new file mode 100644 index 0000000..fd19700 --- /dev/null +++ b/Utilities/platform/2/templates/section-generated-pipe-toggle @@ -0,0 +1,13 @@ + ctrl.%PIPE%( + dbg: "%SHORT_SRC%", + sub: %SUB%, + %SRC%.eraseToAnyPublisher(), + { $0.%NAME% = true }, + { $0.%NAME% = false } + ) + + + + + + diff --git a/Utilities/platform/2/templates/section-generated-pipe-toggleNil b/Utilities/platform/2/templates/section-generated-pipe-toggleNil new file mode 100644 index 0000000..1adea00 --- /dev/null +++ b/Utilities/platform/2/templates/section-generated-pipe-toggleNil @@ -0,0 +1,13 @@ + ctrl.%PIPE%( + dbg: "%SHORT_SRC%", + sub: %SUB%, + %SRC%.eraseToAnyPublisher(), + { $0.%NAME% = $1 }, + { m, _ in m.%NAME% = nil } + ) + + + + + + diff --git a/Utilities/platform/2/templates/service-core b/Utilities/platform/2/templates/service-core new file mode 100644 index 0000000..3a2328c --- /dev/null +++ b/Utilities/platform/2/templates/service-core @@ -0,0 +1 @@ + var core: Core? diff --git a/Utilities/platform/2/templates/service-section b/Utilities/platform/2/templates/service-section new file mode 100644 index 0000000..1fe2b50 --- /dev/null +++ b/Utilities/platform/2/templates/service-section @@ -0,0 +1,2 @@ + Section%NAME%.setupService(ctrl, self, world) + SectionGenerated.setupPlatform(ctrl, self, world) diff --git a/Utilities/platform/2/templates/service-section-generated b/Utilities/platform/2/templates/service-section-generated new file mode 100644 index 0000000..4248465 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated @@ -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% + } diff --git a/Utilities/platform/2/templates/service-section-generated-action b/Utilities/platform/2/templates/service-section-generated-action new file mode 100644 index 0000000..92db079 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action @@ -0,0 +1,5 @@ + ctrl.m + .compactMap { %SHOULD%($0) } + .receive(on: DispatchQueue.main) + .sink { v in %SINK% } + .store(in: &service.subscriptions) diff --git a/Utilities/platform/2/templates/service-section-generated-action-instant b/Utilities/platform/2/templates/service-section-generated-action-instant new file mode 100644 index 0000000..29cc010 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-instant @@ -0,0 +1,4 @@ + ctrl.m + .compactMap { %SHOULD%($0) } + .sink { v in %SINK% } + .store(in: &service.subscriptions) diff --git a/Utilities/platform/2/templates/service-section-generated-action-instant-delay b/Utilities/platform/2/templates/service-section-generated-action-instant-delay new file mode 100644 index 0000000..64a192f --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-instant-delay @@ -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) diff --git a/Utilities/platform/2/templates/service-section-generated-action-instant-model b/Utilities/platform/2/templates/service-section-generated-action-instant-model new file mode 100644 index 0000000..56fbbcc --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-instant-model @@ -0,0 +1,3 @@ + ctrl.m + .sink { v in world.model.send(v); modelRelay.send(v) } + .store(in: &service.subscriptions) diff --git a/Utilities/platform/2/templates/service-section-generated-action-instant-relay b/Utilities/platform/2/templates/service-section-generated-action-instant-relay new file mode 100644 index 0000000..62d95e1 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-instant-relay @@ -0,0 +1,4 @@ + modelRelay + .compactMap { %SHOULD%($0) } + .sink { v in %SINK% } + .store(in: &service.subscriptions) diff --git a/Utilities/platform/2/templates/service-section-generated-action-instant-shouldResetCore b/Utilities/platform/2/templates/service-section-generated-action-instant-shouldResetCore new file mode 100644 index 0000000..0c1bb32 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-instant-shouldResetCore @@ -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) diff --git a/Utilities/platform/2/templates/service-section-generated-action-model b/Utilities/platform/2/templates/service-section-generated-action-model new file mode 100644 index 0000000..2d69174 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-model @@ -0,0 +1,4 @@ + ctrl.m + .receive(on: DispatchQueue.main) + .sink { v in world.model.send(v) } + .store(in: &service.subscriptions) diff --git a/Utilities/platform/2/templates/service-section-generated-action-relay b/Utilities/platform/2/templates/service-section-generated-action-relay new file mode 100644 index 0000000..7574c0c --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-relay @@ -0,0 +1 @@ + let modelRelay = PassthroughSubject() diff --git a/Utilities/platform/2/templates/service-section-generated-action-shouldResetCore b/Utilities/platform/2/templates/service-section-generated-action-shouldResetCore new file mode 100644 index 0000000..d779225 --- /dev/null +++ b/Utilities/platform/2/templates/service-section-generated-action-shouldResetCore @@ -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) diff --git a/Utilities/platform/2/templates/world-constructor b/Utilities/platform/2/templates/world-constructor new file mode 100644 index 0000000..a6e9f31 --- /dev/null +++ b/Utilities/platform/2/templates/world-constructor @@ -0,0 +1 @@ + self.%NAME% = %NAME% diff --git a/Utilities/platform/2/templates/world-field b/Utilities/platform/2/templates/world-field new file mode 100644 index 0000000..65a5f59 --- /dev/null +++ b/Utilities/platform/2/templates/world-field @@ -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% diff --git a/Utilities/platform/2/templates/world-parameter b/Utilities/platform/2/templates/world-parameter new file mode 100644 index 0000000..00d079b --- /dev/null +++ b/Utilities/platform/2/templates/world-parameter @@ -0,0 +1,5 @@ + _ %NAME%: %TYPE% + _ %NAME%: @escaping %TYPE% + _ %NAME%: AnyPublisher<%TYPE%, Never> + _ model: PassthroughSubject<%MODULE%.Model, Never> + _ net: @escaping () -> Net.Publisher diff --git a/Utilities/platform/3/Mode.py b/Utilities/platform/3/Mode.py new file mode 100644 index 0000000..0115e94 --- /dev/null +++ b/Utilities/platform/3/Mode.py @@ -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 diff --git a/Utilities/platform/3/Replace.py b/Utilities/platform/3/Replace.py new file mode 100644 index 0000000..bdcc697 --- /dev/null +++ b/Utilities/platform/3/Replace.py @@ -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] diff --git a/Utilities/platform/3/Result.py b/Utilities/platform/3/Result.py new file mode 100644 index 0000000..37f54f8 --- /dev/null +++ b/Utilities/platform/3/Result.py @@ -0,0 +1,5 @@ +class Result: + def __init__(self, structure): + self.structure = structure + + self.file = "" diff --git a/Utilities/platform/3/Source.py b/Utilities/platform/3/Source.py new file mode 100644 index 0000000..418352c --- /dev/null +++ b/Utilities/platform/3/Source.py @@ -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] diff --git a/Utilities/platform/3/Structure.py b/Utilities/platform/3/Structure.py new file mode 100644 index 0000000..0132f25 --- /dev/null +++ b/Utilities/platform/3/Structure.py @@ -0,0 +1,8 @@ +from Replace import * +from Source import * + +class Structure: + def __init__(self): + self.orig = "" + self.replace = Replace() + self.src = Source() diff --git a/Utilities/platform/3/generate b/Utilities/platform/3/generate new file mode 100755 index 0000000..26a0e24 --- /dev/null +++ b/Utilities/platform/3/generate @@ -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) diff --git a/Utilities/platform/3/generateStructure.py b/Utilities/platform/3/generateStructure.py new file mode 100644 index 0000000..b97b744 --- /dev/null +++ b/Utilities/platform/3/generateStructure.py @@ -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 diff --git a/Utilities/platform/3/parseLines.py b/Utilities/platform/3/parseLines.py new file mode 100644 index 0000000..0fb0ca7 --- /dev/null +++ b/Utilities/platform/3/parseLines.py @@ -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) diff --git a/Utilities/platform/3/readKeyValue.py b/Utilities/platform/3/readKeyValue.py new file mode 100644 index 0000000..c095410 --- /dev/null +++ b/Utilities/platform/3/readKeyValue.py @@ -0,0 +1,8 @@ +def readKeyValue(line): + parts = line.split(": ") + # Ключ со значением. + if len(parts) == 2: + return (parts[0], parts[1]) + + # Не является ключом со значением. + return None diff --git a/Utilities/platform/3/readModuleSrc.py b/Utilities/platform/3/readModuleSrc.py new file mode 100644 index 0000000..7ff0293 --- /dev/null +++ b/Utilities/platform/3/readModuleSrc.py @@ -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) diff --git a/Utilities/platform/4/FeatureToggle.py b/Utilities/platform/4/FeatureToggle.py new file mode 100644 index 0000000..038dd19 --- /dev/null +++ b/Utilities/platform/4/FeatureToggle.py @@ -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] diff --git a/Utilities/platform/4/Mode.py b/Utilities/platform/4/Mode.py new file mode 100644 index 0000000..cfcafcc --- /dev/null +++ b/Utilities/platform/4/Mode.py @@ -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 diff --git a/Utilities/platform/4/Structure.py b/Utilities/platform/4/Structure.py new file mode 100644 index 0000000..3318ea7 --- /dev/null +++ b/Utilities/platform/4/Structure.py @@ -0,0 +1,7 @@ +from FeatureToggle import * + +class Structure: + def __init__(self): + self.dir = "" + self.module = "" + self.featureToggle = FeatureToggle() diff --git a/Utilities/platform/4/generate b/Utilities/platform/4/generate new file mode 100755 index 0000000..8a10ca3 --- /dev/null +++ b/Utilities/platform/4/generate @@ -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) diff --git a/Utilities/platform/4/generateFeatureToggle.py b/Utilities/platform/4/generateFeatureToggle.py new file mode 100644 index 0000000..833f486 --- /dev/null +++ b/Utilities/platform/4/generateFeatureToggle.py @@ -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) diff --git a/Utilities/platform/4/parseLines.py b/Utilities/platform/4/parseLines.py new file mode 100644 index 0000000..f6bbbaa --- /dev/null +++ b/Utilities/platform/4/parseLines.py @@ -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) diff --git a/Utilities/platform/4/readKeyValue.py b/Utilities/platform/4/readKeyValue.py new file mode 100644 index 0000000..c095410 --- /dev/null +++ b/Utilities/platform/4/readKeyValue.py @@ -0,0 +1,8 @@ +def readKeyValue(line): + parts = line.split(": ") + # Ключ со значением. + if len(parts) == 2: + return (parts[0], parts[1]) + + # Не является ключом со значением. + return None diff --git a/Utilities/platform/Platfile b/Utilities/platform/Platfile new file mode 100644 index 0000000..f41c950 --- /dev/null +++ b/Utilities/platform/Platfile @@ -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 diff --git a/Utilities/platform/common/modulePaths.py b/Utilities/platform/common/modulePaths.py new file mode 100644 index 0000000..78884af --- /dev/null +++ b/Utilities/platform/common/modulePaths.py @@ -0,0 +1,7 @@ +def modulePaths(name): + parts = name.split("/") + if len(parts) == 2: + return (name, parts[1]) + else: + return (name, name) + diff --git a/Utilities/platform/common/readFile.py b/Utilities/platform/common/readFile.py new file mode 100644 index 0000000..9f15054 --- /dev/null +++ b/Utilities/platform/common/readFile.py @@ -0,0 +1,6 @@ +def readFile(fileName): + lines = [] + with open(fileName) as file: + for line in file: + lines.append(line.rstrip()) + return lines diff --git a/Utilities/platform/generate-platform b/Utilities/platform/generate-platform new file mode 100755 index 0000000..52dbb97 --- /dev/null +++ b/Utilities/platform/generate-platform @@ -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:]) diff --git a/Utilities/platform/generate-platforms b/Utilities/platform/generate-platforms new file mode 100755 index 0000000..15cb2ce --- /dev/null +++ b/Utilities/platform/generate-platforms @@ -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()