Михаил Капелько 4 месяцев назад
Родитель
Сommit
d893364ff4
93 измененных файлов: 2130 добавлений и 0 удалений
  1. +373
    -0
      Utilities/platform/1/generate
  2. +55
    -0
      Utilities/platform/2/generate
  3. +29
    -0
      Utilities/platform/2/generation/Result.py
  4. +10
    -0
      Utilities/platform/2/generation/fieldFormat.py
  5. +18
    -0
      Utilities/platform/2/generation/generateContextFields.py
  6. +7
    -0
      Utilities/platform/2/generation/generateCore.py
  7. +4
    -0
      Utilities/platform/2/generation/generateCoreSectionGenerated.py
  8. +37
    -0
      Utilities/platform/2/generation/generateCoreSectionGeneratedActions.py
  9. +19
    -0
      Utilities/platform/2/generation/generateCoreSectionsDestroy.py
  10. +27
    -0
      Utilities/platform/2/generation/generateCoreSectionsSetup.py
  11. +11
    -0
      Utilities/platform/2/generation/generateCoreWindow.py
  12. +28
    -0
      Utilities/platform/2/generation/generateFile.py
  13. +16
    -0
      Utilities/platform/2/generation/generateImports.py
  14. +19
    -0
      Utilities/platform/2/generation/generateModelFields.py
  15. +4
    -0
      Utilities/platform/2/generation/generateServiceSectionGenerated.py
  16. +72
    -0
      Utilities/platform/2/generation/generateServiceSectionGeneratedActions.py
  17. +27
    -0
      Utilities/platform/2/generation/generateServiceSections.py
  18. +43
    -0
      Utilities/platform/2/generation/generateStructure.py
  19. +18
    -0
      Utilities/platform/2/generation/generateWorldConstructor.py
  20. +77
    -0
      Utilities/platform/2/generation/generateWorldFields.py
  21. +35
    -0
      Utilities/platform/2/generation/generateWorldParameters.py
  22. +2
    -0
      Utilities/platform/2/generation/hasSectionGenerated.py
  23. +11
    -0
      Utilities/platform/2/generation/isNotKeyword.py
  24. +5
    -0
      Utilities/platform/2/generation/isPipeRecent.py
  25. +14
    -0
      Utilities/platform/2/generation/isToggle.py
  26. +14
    -0
      Utilities/platform/2/generation/pipeFormat.py
  27. +20
    -0
      Utilities/platform/2/generation/pipeSource.py
  28. +14
    -0
      Utilities/platform/2/generation/sectionFunctions.py
  29. +14
    -0
      Utilities/platform/2/generation/sectionGeneratedActionShouldLoad.py
  30. +42
    -0
      Utilities/platform/2/generation/sectionGeneratedPipes.py
  31. +19
    -0
      Utilities/platform/2/generation/sectionNames.py
  32. +24
    -0
      Utilities/platform/2/generation/shortenName.py
  33. +12
    -0
      Utilities/platform/2/generation/worldFieldTypeInit.py
  34. +9
    -0
      Utilities/platform/2/generation/worldFieldTypePS.py
  35. +39
    -0
      Utilities/platform/2/parsing/CoreService.py
  36. +31
    -0
      Utilities/platform/2/parsing/Mode.py
  37. +24
    -0
      Utilities/platform/2/parsing/ModelWorld.py
  38. +9
    -0
      Utilities/platform/2/parsing/Structure.py
  39. +42
    -0
      Utilities/platform/2/parsing/parseLines.py
  40. +15
    -0
      Utilities/platform/2/parsing/readKeyValue.py
  41. +12
    -0
      Utilities/platform/2/parsing/valueArray.py
  42. +2
    -0
      Utilities/platform/2/templates/context-field
  43. +17
    -0
      Utilities/platform/2/templates/core
  44. +3
    -0
      Utilities/platform/2/templates/core-section
  45. +15
    -0
      Utilities/platform/2/templates/core-section-generated
  46. +5
    -0
      Utilities/platform/2/templates/core-section-generated-action
  47. +4
    -0
      Utilities/platform/2/templates/core-section-generated-action-instant
  48. +2
    -0
      Utilities/platform/2/templates/core-window
  49. +68
    -0
      Utilities/platform/2/templates/file
  50. +2
    -0
      Utilities/platform/2/templates/model-field
  51. +8
    -0
      Utilities/platform/2/templates/section-generated-action-shouldLoad
  52. +13
    -0
      Utilities/platform/2/templates/section-generated-pipe-ex-recent
  53. +13
    -0
      Utilities/platform/2/templates/section-generated-pipe-recent
  54. +13
    -0
      Utilities/platform/2/templates/section-generated-pipe-set
  55. +13
    -0
      Utilities/platform/2/templates/section-generated-pipe-toggle
  56. +13
    -0
      Utilities/platform/2/templates/section-generated-pipe-toggleNil
  57. +1
    -0
      Utilities/platform/2/templates/service-core
  58. +2
    -0
      Utilities/platform/2/templates/service-section
  59. +15
    -0
      Utilities/platform/2/templates/service-section-generated
  60. +5
    -0
      Utilities/platform/2/templates/service-section-generated-action
  61. +4
    -0
      Utilities/platform/2/templates/service-section-generated-action-instant
  62. +5
    -0
      Utilities/platform/2/templates/service-section-generated-action-instant-delay
  63. +3
    -0
      Utilities/platform/2/templates/service-section-generated-action-instant-model
  64. +4
    -0
      Utilities/platform/2/templates/service-section-generated-action-instant-relay
  65. +7
    -0
      Utilities/platform/2/templates/service-section-generated-action-instant-shouldResetCore
  66. +4
    -0
      Utilities/platform/2/templates/service-section-generated-action-model
  67. +1
    -0
      Utilities/platform/2/templates/service-section-generated-action-relay
  68. +8
    -0
      Utilities/platform/2/templates/service-section-generated-action-shouldResetCore
  69. +1
    -0
      Utilities/platform/2/templates/world-constructor
  70. +7
    -0
      Utilities/platform/2/templates/world-field
  71. +5
    -0
      Utilities/platform/2/templates/world-parameter
  72. +15
    -0
      Utilities/platform/3/Mode.py
  73. +12
    -0
      Utilities/platform/3/Replace.py
  74. +5
    -0
      Utilities/platform/3/Result.py
  75. +13
    -0
      Utilities/platform/3/Source.py
  76. +8
    -0
      Utilities/platform/3/Structure.py
  77. +48
    -0
      Utilities/platform/3/generate
  78. +9
    -0
      Utilities/platform/3/generateStructure.py
  79. +23
    -0
      Utilities/platform/3/parseLines.py
  80. +8
    -0
      Utilities/platform/3/readKeyValue.py
  81. +12
    -0
      Utilities/platform/3/readModuleSrc.py
  82. +13
    -0
      Utilities/platform/4/FeatureToggle.py
  83. +11
    -0
      Utilities/platform/4/Mode.py
  84. +7
    -0
      Utilities/platform/4/Structure.py
  85. +28
    -0
      Utilities/platform/4/generate
  86. +28
    -0
      Utilities/platform/4/generateFeatureToggle.py
  87. +20
    -0
      Utilities/platform/4/parseLines.py
  88. +8
    -0
      Utilities/platform/4/readKeyValue.py
  89. +168
    -0
      Utilities/platform/Platfile
  90. +7
    -0
      Utilities/platform/common/modulePaths.py
  91. +6
    -0
      Utilities/platform/common/readFile.py
  92. +30
    -0
      Utilities/platform/generate-platform
  93. +87
    -0
      Utilities/platform/generate-platforms

+ 373
- 0
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)

+ 55
- 0
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)

+ 29
- 0
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 = ""

+ 10
- 0
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

+ 18
- 0
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)

+ 7
- 0
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]

+ 4
- 0
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)

+ 37
- 0
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

+ 19
- 0
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)

+ 27
- 0
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)

+ 11
- 0
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

+ 28
- 0
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"

+ 16
- 0
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))

+ 19
- 0
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)

+ 4
- 0
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)

+ 72
- 0
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

+ 27
- 0
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)

+ 43
- 0
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)

+ 18
- 0
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)

+ 77
- 0
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<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)

+ 35
- 0
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)

+ 2
- 0
Utilities/platform/2/generation/hasSectionGenerated.py Просмотреть файл

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

+ 11
- 0
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

+ 5
- 0
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

+ 14
- 0
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

+ 14
- 0
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 "НИНАЮ"

+ 20
- 0
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

+ 14
- 0
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

+ 14
- 0
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

+ 42
- 0
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

+ 19
- 0
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

+ 24
- 0
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)

+ 12
- 0
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]

+ 9
- 0
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]

+ 39
- 0
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

+ 31
- 0
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

+ 24
- 0
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

+ 9
- 0
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()

+ 42
- 0
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)

+ 15
- 0
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

+ 12
- 0
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

+ 2
- 0
Utilities/platform/2/templates/context-field Просмотреть файл

@@ -0,0 +1,2 @@
var %NAME%: %TYPE% { get }
var %NAME%: MPAK.Recent<%TYPE%> { get }

+ 17
- 0
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%
}
}

+ 3
- 0
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)

+ 15
- 0
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%
}

+ 5
- 0
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)

+ 4
- 0
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)

+ 2
- 0
Utilities/platform/2/templates/core-window Просмотреть файл

@@ -0,0 +1,2 @@
let vm = VM()
var wnd: UIWindow?

+ 68
- 0
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%

}
}

+ 2
- 0
Utilities/platform/2/templates/model-field Просмотреть файл

@@ -0,0 +1,2 @@
public var %NAME%: %TYPE% = %DEFAULT%
public var %NAME%: MPAK.Recent<%TYPE%> = .init(%DEFAULT%)

+ 8
- 0
Utilities/platform/2/templates/section-generated-action-shouldLoad Просмотреть файл

@@ -0,0 +1,8 @@
ctrl.m
.compactMap { shouldLoad%NAME%($0) }
.flatMap { v -> AnyPublisher<Net.Result%NAME%, Never> in
world.net().%REQUEST%(v).eraseToAnyPublisher()
}
.receive(on: DispatchQueue.main)
.sink { v in world.result%NAME%.send(v) }
.store(in: &%SUB%.subscriptions)

+ 13
- 0
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
}
)

+ 13
- 0
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 }
)

+ 13
- 0
Utilities/platform/2/templates/section-generated-pipe-set Просмотреть файл

@@ -0,0 +1,13 @@
ctrl.%PIPE%(
dbg: "%SHORT_SRC%",
sub: %SUB%,
%SRC%.eraseToAnyPublisher(),
{ $0.%NAME% = $1 }
)

+ 13
- 0
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 }
)

+ 13
- 0
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 }
)

+ 1
- 0
Utilities/platform/2/templates/service-core Просмотреть файл

@@ -0,0 +1 @@
var core: Core?

+ 2
- 0
Utilities/platform/2/templates/service-section Просмотреть файл

@@ -0,0 +1,2 @@
Section%NAME%.setupService(ctrl, self, world)
SectionGenerated.setupPlatform(ctrl, self, world)

+ 15
- 0
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%
}

+ 5
- 0
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)

+ 4
- 0
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)

+ 5
- 0
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)

+ 3
- 0
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)

+ 4
- 0
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)

+ 7
- 0
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)

+ 4
- 0
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)

+ 1
- 0
Utilities/platform/2/templates/service-section-generated-action-relay Просмотреть файл

@@ -0,0 +1 @@
let modelRelay = PassthroughSubject<Model, Never>()

+ 8
- 0
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)

+ 1
- 0
Utilities/platform/2/templates/world-constructor Просмотреть файл

@@ -0,0 +1 @@
self.%NAME% = %NAME%

+ 7
- 0
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%

+ 5
- 0
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

+ 15
- 0
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

+ 12
- 0
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]

+ 5
- 0
Utilities/platform/3/Result.py Просмотреть файл

@@ -0,0 +1,5 @@
class Result:
def __init__(self, structure):
self.structure = structure

self.file = ""

+ 13
- 0
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]

+ 8
- 0
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()

+ 48
- 0
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)

+ 9
- 0
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

+ 23
- 0
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)

+ 8
- 0
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

+ 12
- 0
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)

+ 13
- 0
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]

+ 11
- 0
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

+ 7
- 0
Utilities/platform/4/Structure.py Просмотреть файл

@@ -0,0 +1,7 @@
from FeatureToggle import *

class Structure:
def __init__(self):
self.dir = ""
self.module = ""
self.featureToggle = FeatureToggle()

+ 28
- 0
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)

+ 28
- 0
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)

+ 20
- 0
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)

+ 8
- 0
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

+ 168
- 0
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

+ 7
- 0
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)


+ 6
- 0
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

+ 30
- 0
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:])

+ 87
- 0
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()

Загрузка…
Отмена
Сохранить