mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
f1563c9890
Initial commit Add notification for divkit Allow overwrite for migration [migration] browser/bitbucket/personal/booster/divkit Note: mandatory check (NEED_CHECK) was skipped DivKit files moved dIVKIT-0: Fix ts builder build update div-gallery visibility screenshots update div-gallery visibility screens a.yaml cleanup updated codegen scripts change gallery alignment change internal types in expressions removed copyrights remove permissions TemplatesSupportTests refactoring Ничего не поменялось. Отрефакторил и почистил тесты. Split package.json send all public removed yandex-team links URL added type as ArgumentType added camera permission description safe area insets for errors count view `EdgeInsets` for debug view with errors count. Needed for fullscreen divs. Naming corresponds to mapping `EdgeInsets` to div-variables. Will be used here: https://nda.ya.ru/t/Lhr5Lxgb5KN6nX fixed compiler warnings fix DivKitComponents variables setup, add update func fix DivKitComponents variables setup, add update func authors clean up Удалил коллег из инфраструктуры, тестирования и мессенджера. Половину людей я вообще тут не знаю. Список какой-то косячный, в нём, например, @booster и @askvortsov нет. Предлагаю заново вручную составить и записать туда тех, кто точно дивкитом занимался, а не просто мимо проходил. Fix DivKit pods compilability Fix DivKit pods compilability use only english in demo app translate divKit demo app russian text Scroll when keyboard opened Scroll when keyboard opened Fix gallery scroll in a11y Это копия пр-а, который я делал в репо алискита: https://bitbucket.browser.yandex-team.ru/projects/ML/repos/mobile-alice-library-android/pull-requests/18808/overview authors clean up2 SwiftGenerator flake8 errors fix & ya.make edit swift generation tests swift references swift generator implementation init swift generator code refactoring 1) rename to modeling 2) rename kind to PropertyType 3) delete apply_pluralization_rules 4) description default value is empty string 5) remove entity_type_field_alias, typename_alias, client_optional, profileEntities, ignore_properties, generate_plain_constructor 6) remove client prefix Add XcodeBuild in ignore Add XcodeBuild in ignore introduce new global variable api introduce new global variable api Revert commit bdcbc0081395ebe44ed7fe24bf5f4b25569c6df0 LayoutKitSnapshotTests Fix sandboxRelease dependencies. support zero value for width and height remove unused css variables "CONTRIBUTING.md" README draft updated gitignore/arcignore introduce color type in expressions introduce color type in expressions added xcconfig files fixed swift formatting fail tests if updating references Чтобы уж точно не забыть вернуть False. Layout changes Fix expression parsing don't change clickable state in a11y don't change clickable state in a11y Update items checks Parse description references to translations add licence file link fixed expression properties generation add ability to track requests to global variables Добавляем в GlobalVariableController возможность подписаться на запросы к глобальным переменным. Нужно дляОткрыт Накопительная палитра , а так же в целом полезная функциональность, через которую можно делать ленивый прогрев глобальных переменных Changelog Minor fixes bump divkit version up to 3.0.0 fixed snapshot tests depdendencies Иногда не запускалась сборка SnapshotTestsHostApp перед стартом тестов. used new api-generator deserialization refactoring removed gitignore samples text fixes fixed samples text remove theme button remove theme button demo activity redesign demo activity redesign habr sample app change deleted images in screenshot tests change deleted images in screenshot tests new ic_launcher new ic_launcher more readme Binding variable for div input Biding variable for div input Implement documentation generator Rename max_lines to max_visible_lines Rename max_lines to max_visible_lines add habr and medium fix div-functions doc Fix link in description Text selecting for demo app enabled Text selecting for demo app enabled KotlinGenerator kotlin code generation kotlin generation tests kotlin generation references add gradle wrapper to kotlin json builder add gradle wrapper to kotlin json builder DemoApp redesign fixed codegen scripts debugView safeAreaInsets DebugView safe area insets TypeScript Generator ya.make changes TypeScript generator TypeScript tests TypeScript test references fix enumeration aliases debugInfoParams fix compile Fix black box flicker in video-custom Fixed stub image showing by moving it away from PlayerView. Increased stub loading speed by adding VideoViewModel warmup. Fixed redundant redraws by combining stub visibility and bitmap data. PlayerView background was set to transparent. DivExpression fix DivExpression fix Implement translations Save & share bind globalVariableController to divContext bind variableController to divContext used new TypeScript generator Подключил генератор на питоне. Изменения в сгенерированных файлах связаны в основном с переводом описаний на английский язык. moved LivePreview Перенёс LivePreview из отдельного модуля в DivKitDemoApp. Добавил сканер кодов в UrlInputView. Удалил часть дублирующегося кода. remove sms and phone_state permission fix links interception fix links interception Fix link in documentation remove site from "config.stable.json" Support highlight_color and keyboardType in div input Support highlight_color in div input Action animation fix for iOS
493 lines
13 KiB
Swift
493 lines
13 KiB
Swift
// Copyright 2021 Yandex LLC. All rights reserved.
|
|
|
|
import Foundation
|
|
|
|
public typealias JSONDictionary = JSONObject.Object
|
|
|
|
public enum JSONObject: Codable, Equatable {
|
|
public enum Error: Swift.Error {
|
|
case invalidPathComponent(String)
|
|
case noValueFound(subpath: String)
|
|
case typeMismatch(subpath: String, expected: String, received: String)
|
|
}
|
|
|
|
public typealias Array = [JSONObject]
|
|
public typealias Object = [String: JSONObject]
|
|
|
|
case bool(Bool)
|
|
case number(Double)
|
|
case string(String)
|
|
case null
|
|
indirect case array(Array)
|
|
indirect case object(JSONDictionary)
|
|
|
|
public static let `true` = JSONObject.bool(true)
|
|
public static let `false` = JSONObject.bool(false)
|
|
|
|
public static func number<IntType: BinaryInteger>(_ integer: IntType) -> Self {
|
|
.number(Double(integer))
|
|
}
|
|
|
|
private init?(untypedScalar: Any?) {
|
|
if let number = untypedScalar as? NSNumber {
|
|
if number === kCFBooleanTrue {
|
|
self = .bool(true)
|
|
} else if number === kCFBooleanFalse {
|
|
self = .bool(false)
|
|
} else {
|
|
self = .number(number.doubleValue)
|
|
}
|
|
} else if untypedScalar is NSNull {
|
|
self = .null
|
|
} else if let string = untypedScalar as? String {
|
|
self = .string(string)
|
|
} else if let json = untypedScalar as? JSONObject {
|
|
self = json
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private init?(singleValueContainer: SingleValueDecodingContainer) {
|
|
if singleValueContainer.decodeNil() {
|
|
self = .null
|
|
return
|
|
}
|
|
|
|
let value: Any? = (try? singleValueContainer.decode(Double.self))
|
|
?? (try? singleValueContainer.decode(Bool.self))
|
|
?? (try? singleValueContainer.decode(String.self))
|
|
|
|
if let obj = JSONObject(untypedScalar: value) {
|
|
self = obj
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
if let container = try? decoder.singleValueContainer(),
|
|
let result = JSONObject(singleValueContainer: container) {
|
|
self = result
|
|
} else if var array = try? decoder.unkeyedContainer() {
|
|
var result: [JSONObject] = []
|
|
while !array.isAtEnd {
|
|
result.append(try array.decode(JSONObject.self))
|
|
}
|
|
self = .array(result)
|
|
} else if let obj = try? decoder.container(keyedBy: StringCodingKey.self) {
|
|
var result: [String: JSONObject] = [:]
|
|
try obj.allKeys.forEach {
|
|
result[$0.stringValue] = try obj.decode(JSONObject.self, forKey: $0)
|
|
}
|
|
self = .object(result)
|
|
} else {
|
|
throw DecodingError
|
|
.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: ""))
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
switch self {
|
|
case let .bool(bool):
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(bool)
|
|
case let .number(number):
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(number)
|
|
case let .string(string):
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(string)
|
|
case let .array(array):
|
|
var container = encoder.unkeyedContainer()
|
|
try array.forEach { try container.encode($0) }
|
|
case let .object(object):
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
try object.forEach {
|
|
try container.encode(
|
|
$0.value,
|
|
forKey: StringCodingKey(stringValue: $0.key)
|
|
)
|
|
}
|
|
case .null:
|
|
var container = encoder.singleValueContainer()
|
|
try container.encodeNil()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension JSONObject {
|
|
private var shortDescription: String {
|
|
switch self {
|
|
case let .bool(bool):
|
|
return "bool: \(bool)"
|
|
case let .number(number):
|
|
return "number: \(number)"
|
|
case let .string(string):
|
|
return "string: \(string)"
|
|
case .array:
|
|
return "array"
|
|
case .object:
|
|
return "object"
|
|
case .null:
|
|
return "null"
|
|
}
|
|
}
|
|
|
|
private func value<C>(
|
|
atPath path: C,
|
|
subpath: String = ""
|
|
) throws -> JSONObject
|
|
where C: Collection, C.Element == Path.Component {
|
|
guard !path.isEmpty else {
|
|
return self
|
|
}
|
|
|
|
let currentComponent = path.first!
|
|
let nextSubpath = subpath + currentComponent.stringValue
|
|
switch (self, currentComponent) {
|
|
case let (.object(dict), .key(key)):
|
|
guard let enclosedValue = dict[key] else {
|
|
throw Error.noValueFound(subpath: nextSubpath)
|
|
}
|
|
return try enclosedValue.value(
|
|
atPath: Swift.Array(path.dropFirst()),
|
|
subpath: nextSubpath
|
|
)
|
|
case let (.array(array), .index(index)):
|
|
guard index < array.count else {
|
|
throw Error.noValueFound(subpath: nextSubpath)
|
|
}
|
|
return try array[index].value(
|
|
atPath: Swift.Array(path.dropFirst()),
|
|
subpath: nextSubpath
|
|
)
|
|
case (.object, _),
|
|
(.array, _),
|
|
(.bool, _),
|
|
(.number, _),
|
|
(.string, _),
|
|
(.null, _):
|
|
throw Error.typeMismatch(
|
|
subpath: nextSubpath,
|
|
expected: currentComponent.expectedType,
|
|
received: shortDescription
|
|
)
|
|
}
|
|
}
|
|
|
|
public func value(atPath path: Path) throws -> JSONObject {
|
|
try value(atPath: path.components)
|
|
}
|
|
|
|
public func value(atPath path: String) throws -> JSONObject {
|
|
try value(atPath: Path(string: path))
|
|
}
|
|
|
|
public func getString(atPath path: String) throws -> String {
|
|
let val = try value(atPath: Path.Component.components(from: path))
|
|
guard case let .string(string) = val else {
|
|
throw Error.typeMismatch(
|
|
subpath: path,
|
|
expected: "String",
|
|
received: val.shortDescription
|
|
)
|
|
}
|
|
return string
|
|
}
|
|
}
|
|
|
|
extension JSONObject {
|
|
public var untypedValue: Any {
|
|
switch self {
|
|
case let .bool(value):
|
|
return NSNumber(value: value)
|
|
case let .number(value):
|
|
return value as NSNumber
|
|
case let .string(value):
|
|
return value
|
|
case let .array(array):
|
|
return array.map { $0.untypedValue }
|
|
case let .object(dict):
|
|
return dict.mapValues {
|
|
$0.untypedValue
|
|
}
|
|
case .null:
|
|
return NSNull()
|
|
}
|
|
}
|
|
|
|
public func makeDictionary() -> [String: Any]? {
|
|
untypedValue as? [String: Any]
|
|
}
|
|
|
|
fileprivate init?(
|
|
value: Any,
|
|
fallback: (Any) -> JSONObject?,
|
|
errorHandler: (Any) throws -> Void
|
|
) rethrows {
|
|
if let result = JSONObject(untypedScalar: value) {
|
|
self = result
|
|
} else if let array = value as? [Any] {
|
|
self = try JSONObject(array: array, fallback: fallback, errorHandler: errorHandler)
|
|
} else if let dict = value as? [String: Any] {
|
|
self = try JSONObject(
|
|
dictionary: dict,
|
|
fallback: fallback,
|
|
errorHandler: errorHandler
|
|
)
|
|
} else if let fallbackResult = fallback(value) {
|
|
self = fallbackResult
|
|
} else {
|
|
try errorHandler(value)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private init(
|
|
array: [Any],
|
|
fallback: (Any) -> JSONObject?,
|
|
errorHandler: (Any) throws -> Void
|
|
) rethrows {
|
|
self = .array(try array.compactMap {
|
|
try JSONObject(value: $0, fallback: fallback, errorHandler: errorHandler)
|
|
})
|
|
}
|
|
|
|
public static func from(untypedValue: Any) -> JSONObject? {
|
|
from(untypedValue: untypedValue, fallback: { _ in nil })
|
|
}
|
|
|
|
public static func from(
|
|
untypedValue: Any,
|
|
fallback: (Any) -> JSONObject?
|
|
) -> JSONObject? {
|
|
(try? JSONObject(
|
|
value: untypedValue,
|
|
fallback: fallback,
|
|
errorHandler: {
|
|
throw Error.typeMismatch(
|
|
subpath: "N/A",
|
|
expected: "Valid JSON value",
|
|
received: "\($0)"
|
|
)
|
|
}
|
|
)).flatMap { $0 }
|
|
}
|
|
|
|
public init(
|
|
dictionary: [String: Any],
|
|
fallback: (Any) -> JSONObject? = { _ in nil },
|
|
errorHandler: (Any) throws -> Void = { _ in }
|
|
) rethrows {
|
|
self = .object(try dictionary.typedJSON(fallback: fallback, errorHandler: errorHandler))
|
|
}
|
|
}
|
|
|
|
extension JSONObject: CustomStringConvertible {
|
|
private func indentedDescription(indent: String) -> String {
|
|
switch self {
|
|
case let .bool(value):
|
|
return "\(value)"
|
|
case let .number(num):
|
|
return "\(num)"
|
|
case let .string(string):
|
|
return string
|
|
case let .array(array):
|
|
let contents = array
|
|
.map { $0.indentedDescription(indent: indent + " ") }
|
|
.joined(separator: ",\n")
|
|
return "\n\(indent)[\n" + contents + "\n\(indent)]"
|
|
case let .object(dict):
|
|
let contents = dict
|
|
.map { "\(indent + " ")\($0.key): \($0.value.indentedDescription(indent: indent + " "))" }
|
|
.joined(separator: ",\n")
|
|
return "\(indent){\n" + contents + "\n\(indent)}"
|
|
case .null:
|
|
return indent + "null"
|
|
}
|
|
}
|
|
|
|
public var description: String {
|
|
indentedDescription(indent: "")
|
|
}
|
|
}
|
|
|
|
extension Dictionary where Key == String {
|
|
public func typedJSON(fallback: (Any) -> JSONObject? = { _ in nil }) -> [String: JSONObject] {
|
|
typedJSON(fallback: fallback, errorHandler: { _ in })
|
|
}
|
|
|
|
public func typedJSON(
|
|
fallback: (Any) -> JSONObject?,
|
|
errorHandler: (Any) throws -> Void
|
|
) rethrows
|
|
-> [String: JSONObject] {
|
|
let keyValueArray: [(String, JSONObject)] = try compactMap {
|
|
let key = $0.key
|
|
return try JSONObject(
|
|
value: $0.value,
|
|
fallback: fallback,
|
|
errorHandler: errorHandler
|
|
).map { (key, $0) }
|
|
}
|
|
|
|
return [String: JSONObject](keyValueArray, uniquingKeysWith: { $1 })
|
|
}
|
|
}
|
|
|
|
extension Dictionary where Key == String, Value == JSONObject {
|
|
public func untypedJSON() -> [String: Any] {
|
|
mapValues {
|
|
$0.untypedValue
|
|
}
|
|
}
|
|
}
|
|
|
|
extension JSONObject {
|
|
public struct Path: Codable, CustomDebugStringConvertible, ExpressibleByStringLiteral, Hashable {
|
|
public typealias Component = JSONPathComponent
|
|
|
|
public var components: ArraySlice<Component>
|
|
|
|
public var isEmpty: Bool {
|
|
components.isEmpty
|
|
}
|
|
|
|
public init(components: ArraySlice<Component>) {
|
|
self.components = components
|
|
}
|
|
|
|
public init(components: [Component]) {
|
|
self.init(components: ArraySlice<Component>(components))
|
|
}
|
|
|
|
public init(string: String) throws {
|
|
self.init(components: try Component.components(from: string))
|
|
}
|
|
|
|
public init(stringLiteral value: String) {
|
|
try! self.init(string: value)
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
try self.init(string: try String(from: decoder))
|
|
}
|
|
|
|
public static let empty = Self(components: [])
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
try asString.encode(to: encoder)
|
|
}
|
|
|
|
public func dropFirst() -> Path {
|
|
Path(components: components.dropFirst())
|
|
}
|
|
|
|
public func appending(path: Path) -> Path {
|
|
Path(components: components + path.components)
|
|
}
|
|
|
|
public mutating func append(path: Path) {
|
|
components.append(contentsOf: path.components)
|
|
}
|
|
|
|
public func appending(component: Component) -> Path {
|
|
Path(components: components + [component])
|
|
}
|
|
|
|
public mutating func append(component: Component) {
|
|
components.append(component)
|
|
}
|
|
|
|
public var asString: String {
|
|
components
|
|
.reduce(into: [String]()) { partialResult, component in
|
|
switch component {
|
|
case let .index(value):
|
|
partialResult.append("[\(value)]")
|
|
case let .key(value):
|
|
let prefix = partialResult.isEmpty ? "" : "."
|
|
partialResult.append("\(prefix)\(value)")
|
|
}
|
|
}
|
|
.joined()
|
|
}
|
|
|
|
public var debugDescription: String { asString }
|
|
}
|
|
}
|
|
|
|
extension Data {
|
|
public func decodeJSON() throws -> JSONObject {
|
|
try JSONDecoder().decode(JSONObject.self, from: self)
|
|
}
|
|
}
|
|
|
|
private struct StringCodingKey: CodingKey {
|
|
let stringValue: String
|
|
|
|
init(stringValue: String) {
|
|
self.stringValue = stringValue
|
|
}
|
|
|
|
var intValue: Int? { nil }
|
|
public init?(intValue _: Int) { nil }
|
|
}
|
|
|
|
public enum JSONPathComponent: Hashable {
|
|
case key(String)
|
|
case index(Int)
|
|
|
|
var stringValue: String {
|
|
switch self {
|
|
case let .index(index):
|
|
return "[\(index)]"
|
|
case let .key(key):
|
|
return key
|
|
}
|
|
}
|
|
|
|
var expectedType: String {
|
|
switch self {
|
|
case .key:
|
|
return "Object"
|
|
case .index:
|
|
return "Array"
|
|
}
|
|
}
|
|
|
|
static func components(from string: String) throws -> [Self] {
|
|
if string.isEmpty {
|
|
return []
|
|
}
|
|
|
|
var result: [Self] = []
|
|
try string.components(separatedBy: ".").forEach { component in
|
|
let error = JSONObject.Error.invalidPathComponent(component)
|
|
let subscriptComponents = component.components(separatedBy: "]")
|
|
if subscriptComponents.count > 1 {
|
|
for `subscript` in subscriptComponents.filter({ !$0.isEmpty }) {
|
|
let components = `subscript`.components(separatedBy: "[")
|
|
guard components.count == 2,
|
|
let int = Int(components[1]) else {
|
|
throw error
|
|
}
|
|
|
|
if !components[0].isEmpty {
|
|
result.append(.key(components[0]))
|
|
}
|
|
result.append(.index(int))
|
|
}
|
|
} else {
|
|
guard !component.isEmpty else {
|
|
throw error
|
|
}
|
|
result.append(.key(component))
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|