This commit is contained in:
Михаил Капелько
2023-12-28 13:33:48 +03:00
parent 16026a7b47
commit d893364ff4
93 changed files with 2130 additions and 0 deletions

View 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 = ""

View File

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

View File

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

View File

@@ -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]

View File

@@ -0,0 +1,4 @@
def generateCoreSectionGenerated(c):
fileName = f"{c.dir}/templates/core-section-generated"
lines = c.readFile(fileName)
c.coreSectionGenerated = "\n".join(lines)

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
def generateServiceSectionGenerated(c):
fileName = f"{c.dir}/templates/service-section-generated"
lines = c.readFile(fileName)
c.serviceSectionGenerated = "\n".join(lines)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
def hasSectionGenerated(entity):
return len(entity.actions) or len(entity.pipes) or entity.isPresent

View File

@@ -0,0 +1,11 @@
def isNotKeyword(str):
keywords = [
"ex",
"recent",
"set",
"toggle",
"toggleNil",
"vm",
"$vm"
]
return str not in keywords

View File

@@ -0,0 +1,5 @@
def isPipeRecent(name, entity):
if name in entity.pipes:
props = entity.pipes[name]
return "recent" in props
return False

View File

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

View File

@@ -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 "НИНАЮ"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]

View File

@@ -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]