Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fad16246a | ||
|
|
a5e1fffc25 | ||
|
|
b63dbd4aab | ||
|
|
4bb34d212a | ||
|
|
b420341e60 | ||
|
|
e24f269e62 | ||
|
|
8bb677258f | ||
|
|
556b3c5879 | ||
|
|
9e6545229f | ||
|
|
d0b3097851 | ||
|
|
9fc0a09f6f | ||
|
|
93181f57a7 | ||
|
|
050aa5c90e | ||
|
|
5bc5f8d2d9 | ||
|
|
d0ef91759d | ||
|
|
bb26364522 | ||
|
|
49cee91239 | ||
|
|
a0f8e2d31e | ||
|
|
87ee7f26d0 | ||
|
|
0c18821fca | ||
|
|
a66b172dc5 | ||
|
|
3d3578c801 | ||
|
|
c528e4796c | ||
|
|
b5b70bbdc7 | ||
|
|
1c864231ec | ||
|
|
e1e5ebc614 | ||
|
|
1d1e235d99 | ||
|
|
53c95b4e3a | ||
|
|
7173a79e98 | ||
|
|
b42649b249 | ||
|
|
e844e8100e | ||
|
|
b9ca315dad | ||
|
|
472af2e766 | ||
|
|
d5d16cd627 | ||
|
|
454fbd9686 | ||
|
|
6886a049ae | ||
|
|
45e61ae350 | ||
|
|
3a3e0325a2 | ||
|
|
d81012e5fc | ||
|
|
6be169e676 | ||
|
|
33d81438f5 | ||
|
|
21fcb9af4a | ||
|
|
f5fd715e3e | ||
|
|
1f17414db4 | ||
|
|
806c04bcc4 | ||
|
|
18e098141d | ||
|
|
16c798c31c | ||
|
|
713f206714 | ||
|
|
59f1da6b87 | ||
|
|
496c34a89f | ||
|
|
a7f5161e5e | ||
|
|
30eb15950a | ||
|
|
be8c1bf6bd | ||
|
|
b3a386cde7 | ||
|
|
9e941f01a5 | ||
|
|
625e2244df | ||
|
|
e0f9938367 | ||
|
|
5cd4ad6d74 | ||
|
|
cca1543856 | ||
|
|
a5bff85223 | ||
|
|
1a731e2fad | ||
|
|
d3a78ee599 | ||
|
|
1d3419d5df | ||
|
|
ee57d253bc | ||
|
|
a76df252db | ||
|
|
4033fba38c | ||
|
|
76cef40423 | ||
|
|
061548b8b4 | ||
|
|
deb238bb21 | ||
|
|
273298e4c9 | ||
|
|
a0ad8da8df | ||
|
|
1779eb3efe | ||
|
|
153e5919ce |
1
Modules/BusX
Symbolic link
1
Modules/BusX
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../u/Modules/BusX
|
||||
@@ -1,31 +0,0 @@
|
||||
import Combine
|
||||
|
||||
public extension Bus {
|
||||
final class Async<Src, Dst> {
|
||||
let v = PassthroughSubject<Src, Never>()
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(
|
||||
_ handler: @escaping ((Src) -> Dst?),
|
||||
_ src: String,
|
||||
_ dst: String
|
||||
) {
|
||||
// Вход.
|
||||
Bus.receiveSync(
|
||||
[src],
|
||||
{ [weak self] _, v in self?.v.send(v) },
|
||||
&subscriptions
|
||||
)
|
||||
// Выход.
|
||||
Bus.sendSync(
|
||||
dst,
|
||||
v
|
||||
.compactMap { (v: Src) in handler(v) }
|
||||
// Асинхронно.
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher(),
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
public extension Bus {
|
||||
/// Пропускаем далее единственный ключ.
|
||||
static func convertKeyValue<T>(
|
||||
_ key: String,
|
||||
_ v: (key: String, value: Any)
|
||||
) -> (String, T)? {
|
||||
guard
|
||||
key == v.key,
|
||||
let value = v.value as? T
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return (key, value)
|
||||
}
|
||||
|
||||
/// Пропускаем далее множество ключей.
|
||||
static func convertKeyValue<T>(
|
||||
_ keys: Set<String>,
|
||||
_ v: (key: String, value: Any)
|
||||
) -> (String, T)? {
|
||||
guard
|
||||
keys.contains(v.key),
|
||||
let value = v.value as? T
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return (v.key, value)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import Combine
|
||||
|
||||
public extension Bus {
|
||||
final class Debounce<Src, Dst> {
|
||||
let v = PassthroughSubject<Src, Never>()
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(
|
||||
_ handler: @escaping ((Src) -> Dst?),
|
||||
_ sec: Double,
|
||||
_ src: String,
|
||||
_ dst: String
|
||||
) {
|
||||
// Вход.
|
||||
Bus.receiveSync(
|
||||
[src],
|
||||
{ [weak self] _, v in self?.v.send(v) },
|
||||
&subscriptions
|
||||
)
|
||||
// Выход.
|
||||
Bus.sendSync(
|
||||
dst,
|
||||
v
|
||||
.debounce(for: .seconds(sec), scheduler: DispatchQueue.main)
|
||||
.compactMap { (v: Src) in handler(v) }
|
||||
.eraseToAnyPublisher(),
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import Combine
|
||||
|
||||
public extension Bus {
|
||||
final class Delay<Src, Dst> {
|
||||
let v = PassthroughSubject<Src, Never>()
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(
|
||||
_ handler: @escaping ((Src) -> Dst?),
|
||||
_ sec: Double,
|
||||
_ src: String,
|
||||
_ dst: String
|
||||
) {
|
||||
// Вход.
|
||||
Bus.receiveSync(
|
||||
[src],
|
||||
{ [weak self] _, v in self?.v.send(v) },
|
||||
&subscriptions
|
||||
)
|
||||
// Выход.
|
||||
Bus.sendSync(
|
||||
dst,
|
||||
v
|
||||
.delay(for: .seconds(sec), scheduler: DispatchQueue.main)
|
||||
.compactMap { (v: Src) in handler(v) }
|
||||
.eraseToAnyPublisher(),
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import Combine
|
||||
|
||||
public extension Bus {
|
||||
final class Sync<Src, Dst> {
|
||||
let v = PassthroughSubject<Src, Never>()
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(
|
||||
_ handler: @escaping ((Src) -> Dst?),
|
||||
_ src: String,
|
||||
_ dst: String
|
||||
) {
|
||||
// Вход.
|
||||
Bus.receiveSync(
|
||||
[src],
|
||||
{ [weak self] _, v in self?.v.send(v) },
|
||||
&subscriptions
|
||||
)
|
||||
// Выход.
|
||||
Bus.sendSync(
|
||||
dst,
|
||||
v
|
||||
.compactMap { (v: Src) in handler(v) }
|
||||
.eraseToAnyPublisher(),
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
public enum Bus {
|
||||
private static let e = PassthroughSubject<(key: String, value: Any), Never>()
|
||||
|
||||
public static var events: AnyPublisher<(key: String, value: Any), Never> {
|
||||
e.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
public extension Bus {
|
||||
/// Асинхронно обрабатываем входящие события из шины.
|
||||
static func receiveAsync<T>(
|
||||
_ keys: Set<String>,
|
||||
_ handler: @escaping ((String, T) -> Void),
|
||||
_ subscriptions: inout [AnyCancellable]
|
||||
) {
|
||||
e
|
||||
.compactMap { convertKeyValue(keys, $0) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { v in handler(v.0, v.1) }
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
|
||||
/// Синхронно обрабатываем входящие события из шины.
|
||||
static func receiveSync<T>(
|
||||
_ keys: Set<String>,
|
||||
_ handler: @escaping ((String, T) -> Void),
|
||||
_ subscriptions: inout [AnyCancellable]
|
||||
) {
|
||||
e
|
||||
.compactMap { convertKeyValue(keys, $0) }
|
||||
.sink { v in handler(v.0, v.1) }
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
|
||||
/// Синхронно отправляем события из узла в шину.
|
||||
///
|
||||
/// Для асинхронной отправки достаточно добавить оператор `receive(on:)`
|
||||
/// в цепочке параметра `node`
|
||||
static func sendSync<T>(
|
||||
_ key: String,
|
||||
_ node: AnyPublisher<T, Never>,
|
||||
_ subscriptions: inout [AnyCancellable]
|
||||
) {
|
||||
node
|
||||
.sink { v in e.send((key, v)) }
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
|
||||
/// Единоразово синхронно отправляем событие в шину.
|
||||
static func send(_ key: String, _ value: Any) {
|
||||
e.send((key, value))
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
extension BusUI {
|
||||
/// Пропускаем лишь значения от UI
|
||||
///
|
||||
/// - Returns: Значение без префиксов "a:"/"u:"
|
||||
static func onlyUIText(_ s: String) -> String? {
|
||||
guard s.hasPrefix("u:") else { return nil }
|
||||
return String(s.dropFirst(2))
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import Combine
|
||||
|
||||
extension BusUI {
|
||||
public final class Button: ObservableObject {
|
||||
public let v = PassthroughSubject<Void, Never>()
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(_ key: String) {
|
||||
Bus.sendSync(
|
||||
key,
|
||||
v.map { true }.eraseToAnyPublisher(),
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
extension BusUI {
|
||||
public final class TextField: ObservableObject {
|
||||
@Published public var v = "a:"
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(
|
||||
_ textApp: String,
|
||||
_ textUI: String
|
||||
) {
|
||||
Bus.sendSync(
|
||||
textUI,
|
||||
$v
|
||||
.removeDuplicates()
|
||||
.compactMap(onlyUIText)
|
||||
.eraseToAnyPublisher(),
|
||||
&subscriptions
|
||||
)
|
||||
|
||||
Bus.receiveSync(
|
||||
[textApp],
|
||||
{ [weak self] (_, v: String) in self?.v = "a:\(v)" },
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension BusUI {
|
||||
public final class TextFieldSource: Formatter {
|
||||
/// Выдаём для отображения очищенную от источника строку.
|
||||
public override func string(for obj: Any?) -> String? {
|
||||
guard let str = obj as? String else { return nil }
|
||||
return String(str.dropFirst(2))
|
||||
}
|
||||
|
||||
/// Выдаём для использования кодом строку с указанием источника
|
||||
/// в виде пользователя `u:`
|
||||
public override func getObjectValue(
|
||||
_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
|
||||
for string: String,
|
||||
errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?
|
||||
) -> Bool {
|
||||
obj?.pointee = "u:\(string)" as AnyObject
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import Combine
|
||||
|
||||
extension BusUI {
|
||||
public final class Value<T>: ObservableObject {
|
||||
@Published public var v: T
|
||||
var subscriptions = [AnyCancellable]()
|
||||
|
||||
public init(
|
||||
_ key: String,
|
||||
_ defaultValue: T
|
||||
) {
|
||||
v = defaultValue
|
||||
Bus.receiveSync(
|
||||
[key],
|
||||
{ [weak self] (_, v: T) in self?.v = v },
|
||||
&subscriptions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
public enum BusUI { }
|
||||
@@ -1,14 +0,0 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = 'BusX'
|
||||
s.version = '2023.12.30'
|
||||
s.license = 'IVCS'
|
||||
s.summary = 'Шина общения элементов приложения'
|
||||
s.homepage = 'IVCS'
|
||||
s.author = 'IVCS'
|
||||
s.source = { :git => 'https://fake.com/FAKE.git', :tag => s.version }
|
||||
s.source_files = '**/**/*.swift'
|
||||
s.swift_version = '5.2'
|
||||
s.ios.deployment_target = '14.0'
|
||||
|
||||
end
|
||||
@@ -5,14 +5,13 @@ extension MeetupId {
|
||||
public struct V: View {
|
||||
@StateObject var isJoinAvailable = BusUI.Value(K.isJoinAvailable, false)
|
||||
@StateObject var isLoading = BusUI.Value(K.isLoading, false)
|
||||
@StateObject var join = BusUI.Button(K.join)
|
||||
@StateObject var textField = BusUI.TextField(K.textApp, K.textUI)
|
||||
let processors: [Any]
|
||||
|
||||
public init() {
|
||||
processors = [
|
||||
Bus.Debounce(shouldResetText, 0.2, K.M, K.textApp),
|
||||
Bus.Delay(shouldFinishLoading, 5, K.M, K.finishLoading),
|
||||
Bus.ConstDebounce(shouldResetText, 0.2, K.M, K.textApp),
|
||||
Bus.ConstDelay(shouldFinishLoading, 5, K.M, K.finishLoading),
|
||||
Bus.Sync(shouldEnableJoin, K.M, K.isJoinAvailable),
|
||||
Bus.Sync(shouldResetLoading, K.M, K.isLoading)
|
||||
]
|
||||
@@ -34,7 +33,7 @@ extension MeetupId {
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: join.v.send) {
|
||||
Button(action: { Bus.send(K.join, true) }) {
|
||||
Text("Join")
|
||||
.padding(8)
|
||||
.border(
|
||||
|
||||
18
Modules/MicX/Mic/Mic.yml
Normal file
18
Modules/MicX/Mic/Mic.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: 2
|
||||
|
||||
model:
|
||||
activeIds: [[String], []]
|
||||
activityDates: [[String:Date], [:]]
|
||||
requestActivityDate: [String?, nil]
|
||||
|
||||
service:
|
||||
actions:
|
||||
shouldDeliverActivityDates: Bus.deliver(K.activityDate, v)
|
||||
shouldResetActivityDates: Bus.send(K.activityDates, v)
|
||||
|
||||
pipes:
|
||||
activeIds: [recent, K]
|
||||
activityDates: [recent, K]
|
||||
requestActivityDate: [toggleNil, K]
|
||||
|
||||
world:
|
||||
5
Modules/MicX/Mic/src/Mic.C.swift
Normal file
5
Modules/MicX/Mic/src/Mic.C.swift
Normal file
@@ -0,0 +1,5 @@
|
||||
public extension Mic {
|
||||
enum C {
|
||||
static let activityTimeout: TimeInterval = 5
|
||||
}
|
||||
}
|
||||
140
Modules/MicX/Mic/src/Mic.Generated.swift
Normal file
140
Modules/MicX/Mic/src/Mic.Generated.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
// ВНИМАНИЕ Сгенерировано автоматом из файла Mic.yml
|
||||
// ВНИМАНИЕ Не менять руками!
|
||||
|
||||
import AELog
|
||||
import BusX
|
||||
import Combine
|
||||
import Foundation
|
||||
import MPAKX
|
||||
import UIKit
|
||||
|
||||
// MARK: - Context
|
||||
|
||||
public protocol MicContext {
|
||||
var activeIds: MPAK.Recent<[String]> { get }
|
||||
var activityDates: MPAK.Recent<[String:Date]> { get }
|
||||
var requestActivityDate: String? { get }
|
||||
}
|
||||
|
||||
// MARK: - Controller
|
||||
|
||||
extension Mic {
|
||||
final class Controller: MPAK.Controller<Mic.Model> {
|
||||
init() {
|
||||
super.init(
|
||||
Mic.Model(),
|
||||
debugClassName: "MicCtrl",
|
||||
debugLog: { aelog($0) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Model
|
||||
|
||||
public struct Model: MicContext {
|
||||
public var activeIds: MPAK.Recent<[String]> = .init([])
|
||||
public var activityDates: MPAK.Recent<[String:Date]> = .init([:])
|
||||
public var requestActivityDate: String? = nil
|
||||
}
|
||||
|
||||
// MARK: - Service
|
||||
|
||||
public final class Service {
|
||||
let ctrl = Controller()
|
||||
let world: World
|
||||
|
||||
var subscriptions = [AnyCancellable]()
|
||||
static private(set) weak var singleton: Service?
|
||||
|
||||
public init(_ world: World) {
|
||||
self.world = world
|
||||
Self.singleton = self
|
||||
SectionGenerated.setupPlatform(ctrl, self, world)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - World
|
||||
|
||||
public struct World {
|
||||
|
||||
|
||||
public init(
|
||||
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
enum SectionGenerated {
|
||||
|
||||
|
||||
|
||||
// MARK: - SectionGenerated Service
|
||||
|
||||
static func setupPlatform(
|
||||
_ ctrl: Controller,
|
||||
_ service: Service,
|
||||
_ world: World
|
||||
) {
|
||||
// MARK: - SectionGenerated Service Actions
|
||||
|
||||
ctrl.m
|
||||
.compactMap { shouldDeliverActivityDates($0) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { v in Bus.deliver(K.activityDate, v) }
|
||||
.store(in: &service.subscriptions)
|
||||
ctrl.m
|
||||
.compactMap { shouldResetActivityDates($0) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { v in Bus.send(K.activityDates, v) }
|
||||
.store(in: &service.subscriptions)
|
||||
|
||||
|
||||
// MARK: - SectionGenerated Service Pipes
|
||||
|
||||
ctrl.pipeValue(
|
||||
dbg: "activeI",
|
||||
sub: nil,
|
||||
Bus.events.compactMap { Bus.convertKeyValue(K.activeIds, $0) }.map { (k: String, v: [String]) in v }.eraseToAnyPublisher(),
|
||||
{
|
||||
$0.activeIds.value = $1
|
||||
$0.activeIds.isRecent = true
|
||||
},
|
||||
{ m, _ in m.activeIds.isRecent = false }
|
||||
)
|
||||
|
||||
|
||||
|
||||
ctrl.pipeValue(
|
||||
dbg: "activityD",
|
||||
sub: nil,
|
||||
Bus.events.compactMap { Bus.convertKeyValue(K.activityDates, $0) }.map { (k: String, v: [String:Date]) in v }.eraseToAnyPublisher(),
|
||||
{
|
||||
$0.activityDates.value = $1
|
||||
$0.activityDates.isRecent = true
|
||||
},
|
||||
{ m, _ in m.activityDates.isRecent = false }
|
||||
)
|
||||
|
||||
|
||||
|
||||
ctrl.pipeValue(
|
||||
dbg: "requestAD",
|
||||
sub: nil,
|
||||
Bus.events.compactMap { Bus.convertKeyValue(K.requestActivityDate, $0) }.map { (k: String, v: String?) in v }.eraseToAnyPublisher(),
|
||||
{ $0.requestActivityDate = $1 },
|
||||
{ m, _ in m.requestActivityDate = nil }
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Modules/MicX/Mic/src/Mic.K.swift
Normal file
11
Modules/MicX/Mic/src/Mic.K.swift
Normal file
@@ -0,0 +1,11 @@
|
||||
public extension Mic {
|
||||
enum K {
|
||||
public static let activeIds = "Mic.activeIds"
|
||||
public static let activityDate = "Mic.activityDate"
|
||||
public static let activityDates = "Mic.activityDates"
|
||||
public static let isActive = "Mic.isActive"
|
||||
public static let MI = "MicItem"
|
||||
public static let requestActivityDate = "Mic.requestActivityDate"
|
||||
public static let timeout = "Mic.timeout"
|
||||
}
|
||||
}
|
||||
63
Modules/MicX/Mic/src/Mic.Shoulds.swift
Normal file
63
Modules/MicX/Mic/src/Mic.Shoulds.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
public extension Mic {
|
||||
/// Следует доставить состояния звуковой активности элементам
|
||||
///
|
||||
/// Условия:
|
||||
/// 1. Изменились даты активности
|
||||
/// 2. Элемент запросил свою дату активности при наличии валидной даты
|
||||
/// 2. Элемент запросил свою дату активности без наличия валидной даты
|
||||
///
|
||||
/// - Returns: Словарь состояний
|
||||
static func shouldDeliverActivityDates(_ c: MicContext) -> [String: Date]? {
|
||||
if let ad = shouldResetActivityDates(c) {
|
||||
return ad
|
||||
}
|
||||
|
||||
if
|
||||
let id = c.requestActivityDate,
|
||||
let timeout = c.activityDates.value[id],
|
||||
timeout > Date()
|
||||
{
|
||||
return [id: timeout]
|
||||
}
|
||||
|
||||
if let id = c.requestActivityDate {
|
||||
return [id: .distantPast]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Следует обновить словарь состояний звуковой активности
|
||||
///
|
||||
/// Условия:
|
||||
/// 1. Пришли id активных участников
|
||||
///
|
||||
/// - Returns: Словарь активных состояний
|
||||
static func shouldResetActivityDates(_ c: MicContext) -> [String: Date]? {
|
||||
if c.activeIds.isRecent {
|
||||
var ad = c.activityDates.value
|
||||
let ids = c.activeIds.value
|
||||
let now = Date()
|
||||
// Задаём абсолютную дату истечения для активных id.
|
||||
for id in ids {
|
||||
ad[id] = now + C.activityTimeout
|
||||
}
|
||||
|
||||
// Собираем истёкшие id.
|
||||
var expiredIds = [String]()
|
||||
for (id, timeout) in ad {
|
||||
if timeout < now {
|
||||
expiredIds.append(id)
|
||||
}
|
||||
}
|
||||
// Удаляем истёкшие id.
|
||||
for id in expiredIds {
|
||||
ad[id] = nil
|
||||
}
|
||||
|
||||
return ad
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
1
Modules/MicX/Mic/src/Mic.swift
Normal file
1
Modules/MicX/Mic/src/Mic.swift
Normal file
@@ -0,0 +1 @@
|
||||
public enum Mic { }
|
||||
4
Modules/MicX/MicItem/MicItem.yml
Normal file
4
Modules/MicX/MicItem/MicItem.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
version: 5
|
||||
|
||||
activityDate: [Date?, nil, recent]
|
||||
timeout: [Bool, false, toggle]
|
||||
64
Modules/MicX/MicItem/src/MicItem.Generated.swift
Normal file
64
Modules/MicX/MicItem/src/MicItem.Generated.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
// ВНИМАНИЕ Сгенерировано автоматом из файла MicItem.yml
|
||||
// ВНИМАНИЕ Не менять руками!
|
||||
|
||||
import AELog
|
||||
import BusX
|
||||
import Combine
|
||||
import Foundation
|
||||
import MPAKX
|
||||
import UIKit
|
||||
|
||||
// MARK: - Context
|
||||
|
||||
public protocol MicItemContext {
|
||||
var activityDate: MPAK.Recent<Date?> { get }
|
||||
var timeout: Bool { get }
|
||||
}
|
||||
|
||||
extension MicItem {
|
||||
|
||||
// MARK: - Model
|
||||
|
||||
public struct Model: MicItemContext {
|
||||
public var activityDate: MPAK.Recent<Date?> = .init(nil)
|
||||
public var timeout: Bool = false
|
||||
}
|
||||
|
||||
// MARK: - Controller
|
||||
|
||||
final class Controller: MPAK.Controller<MicItem.Model> {
|
||||
init(_ id: String? = nil) {
|
||||
var sid = ""
|
||||
if let id {
|
||||
sid = " : \(id)"
|
||||
}
|
||||
super.init(
|
||||
MicItem.Model(),
|
||||
debugClassName: "MicItemCtrl",
|
||||
debugLog: { aelog("\($0)\(sid)") }
|
||||
)
|
||||
|
||||
// Отправляем модель в Шину.
|
||||
m
|
||||
.sink { v in Bus.send(Bus.keyId("MicItem", id), v) }
|
||||
.store(in: &subscriptions)
|
||||
|
||||
pipeValue(
|
||||
dbg: "activityD",
|
||||
Bus.events.compactMap { Bus.convertKeyValue(Bus.keyId(K.activityDate, id), $0) }.map { (k: String, v: Date?) in v }.eraseToAnyPublisher(),
|
||||
{
|
||||
$0.activityDate.value = $1
|
||||
$0.activityDate.isRecent = true
|
||||
},
|
||||
{ m, _ in m.activityDate.isRecent = false }
|
||||
)
|
||||
pipe(
|
||||
dbg: "timeout",
|
||||
Bus.events.compactMap { Bus.convertKeyValue(Bus.keyId(K.timeout, id), $0) }.map { (k: String, v: Bool) in v }.eraseToAnyPublisher(),
|
||||
{ $0.timeout = true },
|
||||
{ $0.timeout = false }
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Modules/MicX/MicItem/src/MicItem.Shoulds.swift
Normal file
42
Modules/MicX/MicItem/src/MicItem.Shoulds.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
public extension MicItem {
|
||||
/// Следует задать активность элемента
|
||||
///
|
||||
/// Условия:
|
||||
/// 1. Пришла актуальная дата завершения активности
|
||||
/// 2. Таймер сообщил о завершении активности
|
||||
///
|
||||
/// - Returns: Состояние активности элемента
|
||||
static func shouldResetActivity(_ c: MicItemContext) -> Bool? {
|
||||
if
|
||||
c.activityDate.isRecent,
|
||||
let end = c.activityDate.value
|
||||
{
|
||||
let now = Date()
|
||||
return now < end
|
||||
}
|
||||
|
||||
if c.timeout {
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Следует задать время истечения активности
|
||||
///
|
||||
/// Условия:
|
||||
/// 1. Пришла актуальная дата завершения активности
|
||||
///
|
||||
/// - Returns: Время истечения активности
|
||||
static func shouldResetTimeout(_ c: MicItemContext) -> TimeInterval? {
|
||||
if
|
||||
c.activityDate.isRecent,
|
||||
let end = c.activityDate.value
|
||||
{
|
||||
let timeout = end.timeIntervalSinceNow
|
||||
return timeout > 0 ? timeout : nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
37
Modules/MicX/MicItem/src/MicItem.V.swift
Normal file
37
Modules/MicX/MicItem/src/MicItem.V.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
import BusX
|
||||
import SwiftUI
|
||||
|
||||
extension MicItem {
|
||||
public struct V: View {
|
||||
let id: String?
|
||||
@StateObject var holder = BusUI.Holder()
|
||||
@StateObject var isActive = BusUI.Value(K.isActive, false)
|
||||
|
||||
public init(_ id: String? = nil) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
func configure(_ id: String? = nil) {
|
||||
isActive.id = id
|
||||
holder.items = [
|
||||
Bus.Delay(shouldResetTimeout, K.MI, K.timeout, id),
|
||||
Bus.Sync(shouldResetActivity, K.MI, K.isActive, id),
|
||||
MicItem.Controller(id),
|
||||
]
|
||||
// Запрашиваем актуальные данные при (пере)создании.
|
||||
Bus.send(K.requestActivityDate, id)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Text("Mic activity")
|
||||
.padding(8)
|
||||
.border(
|
||||
isActive.v ? Color.black : Color.gray,
|
||||
width: isActive.v ? 3 : 1
|
||||
)
|
||||
.animation(.easeInOut(duration: 0.3))
|
||||
.onAppear { self.configure(id) }
|
||||
.onChange(of: id) { id in self.configure(id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Modules/MicX/MicItem/src/MicItem.swift
Normal file
3
Modules/MicX/MicItem/src/MicItem.swift
Normal file
@@ -0,0 +1,3 @@
|
||||
public enum MicItem { }
|
||||
|
||||
typealias K = Mic.K
|
||||
17
Modules/MicX/MicX.podspec
Normal file
17
Modules/MicX/MicX.podspec
Normal file
@@ -0,0 +1,17 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = 'MicX'
|
||||
s.version = '2024.01.15'
|
||||
s.license = 'IVCS'
|
||||
s.summary = 'Отображение звуковой активности участника'
|
||||
s.homepage = 'IVCS'
|
||||
s.author = 'IVCS'
|
||||
s.source = { :git => 'https://fake.com/FAKE.git', :tag => s.version }
|
||||
s.source_files = '**/**/*.{swift,yml}'
|
||||
s.swift_version = '5.2'
|
||||
s.ios.deployment_target = '14.0'
|
||||
s.dependency 'AELog'
|
||||
s.dependency 'BusX'
|
||||
s.dependency 'MPAKX'
|
||||
|
||||
end
|
||||
@@ -1,373 +0,0 @@
|
||||
#!/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)
|
||||
1
Utilities/platform/2/generation
Symbolic link
1
Utilities/platform/2/generation
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/mk/m/u/Utilities/platform/2/generation
|
||||
@@ -1,29 +0,0 @@
|
||||
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 = ""
|
||||
@@ -1,10 +0,0 @@
|
||||
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
|
||||
@@ -1,18 +0,0 @@
|
||||
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)
|
||||
@@ -1,7 +0,0 @@
|
||||
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]
|
||||
@@ -1,4 +0,0 @@
|
||||
def generateCoreSectionGenerated(c):
|
||||
fileName = f"{c.dir}/templates/core-section-generated"
|
||||
lines = c.readFile(fileName)
|
||||
c.coreSectionGenerated = "\n".join(lines)
|
||||
@@ -1,37 +0,0 @@
|
||||
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
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
@@ -1,27 +0,0 @@
|
||||
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)
|
||||
@@ -1,11 +0,0 @@
|
||||
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
|
||||
@@ -1,28 +0,0 @@
|
||||
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"
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
# Для обычного модуля с окончание X учитываем такое же название podspec.
|
||||
if c.path[-1] == "X":
|
||||
fileName = fileName.replace(f"{c.module}.podspec", f"{c.module}X.podspec")
|
||||
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))
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
@@ -1,4 +0,0 @@
|
||||
def generateServiceSectionGenerated(c):
|
||||
fileName = f"{c.dir}/templates/service-section-generated"
|
||||
lines = c.readFile(fileName)
|
||||
c.serviceSectionGenerated = "\n".join(lines)
|
||||
@@ -1,79 +0,0 @@
|
||||
from generation.sectionGeneratedActionShouldLoad import *
|
||||
|
||||
def generateServiceSectionGeneratedActions(c):
|
||||
base = f"{c.dir}/templates/service-section-generated-action"
|
||||
fmtBusModel = c.readFile(f"{base}-bus-model")
|
||||
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 == "":
|
||||
# busModel.
|
||||
if key == "busModel":
|
||||
c.serviceSectionGeneratedActions += "\n".join(fmtBusModel) + "\n"
|
||||
continue
|
||||
|
||||
# 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) \
|
||||
.replace("%MODULE%", c.module)
|
||||
output += ln + "\n"
|
||||
|
||||
c.serviceSectionGeneratedActions += output
|
||||
@@ -1,27 +0,0 @@
|
||||
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)
|
||||
@@ -1,43 +0,0 @@
|
||||
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)
|
||||
@@ -1,18 +0,0 @@
|
||||
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)
|
||||
@@ -1,77 +0,0 @@
|
||||
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)
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
@@ -1,2 +0,0 @@
|
||||
def hasSectionGenerated(entity):
|
||||
return len(entity.actions) or len(entity.pipes) or entity.isPresent
|
||||
@@ -1,11 +0,0 @@
|
||||
def isNotKeyword(str):
|
||||
keywords = [
|
||||
"ex",
|
||||
"recent",
|
||||
"set",
|
||||
"toggle",
|
||||
"toggleNil",
|
||||
"vm",
|
||||
"$vm"
|
||||
]
|
||||
return str not in keywords
|
||||
@@ -1,5 +0,0 @@
|
||||
def isPipeRecent(name, entity):
|
||||
if name in entity.pipes:
|
||||
props = entity.pipes[name]
|
||||
return "recent" in props
|
||||
return False
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,5 +0,0 @@
|
||||
def pipeBusSource(name, entity, busKey, structure, fmt):
|
||||
valueType = structure.model.fields[name][0]
|
||||
return fmt \
|
||||
.replace("%BUS_KEY%", busKey) \
|
||||
.replace("%BUS_VALUE_TYPE%", valueType)
|
||||
@@ -1,14 +0,0 @@
|
||||
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 "НИНАЮ"
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,47 +0,0 @@
|
||||
from generation.shortenName import *
|
||||
from generation.pipeBusSource import *
|
||||
from generation.pipeFormat import *
|
||||
from generation.pipeSource import *
|
||||
|
||||
def sectionGeneratedPipes(entity, sub, c):
|
||||
fmtBusPipe = c.readFile(f"{c.dir}/templates/section-generated-pipe-src-bus")[0]
|
||||
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)
|
||||
# Bus.
|
||||
if src.startswith("K."):
|
||||
src = pipeBusSource(key, entity, src, c.structure, fmtBusPipe)
|
||||
|
||||
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
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
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)
|
||||
@@ -1,12 +0,0 @@
|
||||
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]
|
||||
@@ -1,9 +0,0 @@
|
||||
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]
|
||||
1
Utilities/platform/2/parsing
Symbolic link
1
Utilities/platform/2/parsing
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/mk/m/u/Utilities/platform/2/parsing
|
||||
@@ -1,39 +0,0 @@
|
||||
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
|
||||
@@ -1,31 +0,0 @@
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
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
|
||||
@@ -1,9 +0,0 @@
|
||||
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()
|
||||
@@ -1,42 +0,0 @@
|
||||
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)
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,12 +0,0 @@
|
||||
def valueArray(value):
|
||||
# Удостоверяем наличие скобок.
|
||||
if not (
|
||||
value.startswith("[") and
|
||||
value.endswith("]")
|
||||
):
|
||||
return None
|
||||
|
||||
# Исключаем скобки.
|
||||
noBrackets = value[1:-1]
|
||||
parts = noBrackets.split(", ")
|
||||
return parts
|
||||
1
Utilities/platform/2/templates
Symbolic link
1
Utilities/platform/2/templates
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/mk/m/u/Utilities/platform/2/templates
|
||||
@@ -1,2 +0,0 @@
|
||||
var %NAME%: %TYPE% { get }
|
||||
var %NAME%: MPAK.Recent<%TYPE%> { get }
|
||||
@@ -1,17 +0,0 @@
|
||||
// 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%
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
Section%NAME%.destroyCore(self)
|
||||
Section%NAME%.setupCore(self, ctrl, world)
|
||||
SectionGenerated.setupPlatform(self, ctrl, world)
|
||||
@@ -1,15 +0,0 @@
|
||||
// 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%
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
ctrl.m
|
||||
.compactMap { %SHOULD%($0) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak core] v in %SINK% }
|
||||
.store(in: &core.subscriptions)
|
||||
@@ -1,4 +0,0 @@
|
||||
ctrl.m
|
||||
.compactMap { %SHOULD%($0) }
|
||||
.sink { [weak core] v in %SINK% }
|
||||
.store(in: &core.subscriptions)
|
||||
@@ -1,2 +0,0 @@
|
||||
let vm = VM()
|
||||
var wnd: UIWindow?
|
||||
@@ -1,68 +0,0 @@
|
||||
// ВНИМАНИЕ Сгенерировано автоматом из файла %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%
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
public var %NAME%: %TYPE% = %DEFAULT%
|
||||
public var %NAME%: MPAK.Recent<%TYPE%> = .init(%DEFAULT%)
|
||||
@@ -1,8 +0,0 @@
|
||||
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)
|
||||
@@ -1,13 +0,0 @@
|
||||
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
|
||||
}
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
ctrl.%PIPE%(
|
||||
dbg: "%SHORT_SRC%",
|
||||
sub: %SUB%,
|
||||
%SRC%.eraseToAnyPublisher(),
|
||||
{
|
||||
$0.%NAME%.value = $1
|
||||
$0.%NAME%.isRecent = true
|
||||
},
|
||||
{ m, _ in m.%NAME%.isRecent = false }
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
ctrl.%PIPE%(
|
||||
dbg: "%SHORT_SRC%",
|
||||
sub: %SUB%,
|
||||
%SRC%.eraseToAnyPublisher(),
|
||||
{ $0.%NAME% = $1 }
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Bus.events.compactMap { Bus.convertKeyValue(%BUS_KEY%, $0) }.map { (k: String, v: %BUS_VALUE_TYPE%) in v }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
ctrl.%PIPE%(
|
||||
dbg: "%SHORT_SRC%",
|
||||
sub: %SUB%,
|
||||
%SRC%.eraseToAnyPublisher(),
|
||||
{ $0.%NAME% = true },
|
||||
{ $0.%NAME% = false }
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
ctrl.%PIPE%(
|
||||
dbg: "%SHORT_SRC%",
|
||||
sub: %SUB%,
|
||||
%SRC%.eraseToAnyPublisher(),
|
||||
{ $0.%NAME% = $1 },
|
||||
{ m, _ in m.%NAME% = nil }
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
var core: Core?
|
||||
@@ -1,2 +0,0 @@
|
||||
Section%NAME%.setupService(ctrl, self, world)
|
||||
SectionGenerated.setupPlatform(ctrl, self, world)
|
||||
@@ -1,15 +0,0 @@
|
||||
// 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%
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
ctrl.m
|
||||
.compactMap { %SHOULD%($0) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { v in %SINK% }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,3 +0,0 @@
|
||||
ctrl.m
|
||||
.sink { v in Bus.send("%MODULE%", v) }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,4 +0,0 @@
|
||||
ctrl.m
|
||||
.compactMap { %SHOULD%($0) }
|
||||
.sink { v in %SINK% }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,5 +0,0 @@
|
||||
ctrl.m
|
||||
.compactMap { %SHOULD%($0) }
|
||||
.delay(for: .seconds(0.3), scheduler: RunLoop.main)
|
||||
.sink { v in %SINK% }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,3 +0,0 @@
|
||||
ctrl.m
|
||||
.sink { v in world.model.send(v); modelRelay.send(v) }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,4 +0,0 @@
|
||||
modelRelay
|
||||
.compactMap { %SHOULD%($0) }
|
||||
.sink { v in %SINK% }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,7 +0,0 @@
|
||||
ctrl.m
|
||||
.compactMap { shouldResetCore($0) }
|
||||
.sink { v in
|
||||
service.core = v ? Core(ctrl, world) : nil
|
||||
world.isCoreRunning.send(v)
|
||||
}
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1,4 +0,0 @@
|
||||
ctrl.m
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { v in world.model.send(v) }
|
||||
.store(in: &service.subscriptions)
|
||||
@@ -1 +0,0 @@
|
||||
let modelRelay = PassthroughSubject<Model, Never>()
|
||||
@@ -1,8 +0,0 @@
|
||||
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)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user