├── Assets └── pjango.png ├── Source └── Pjango │ ├── Plugin │ ├── PCTaskPlugin.swift │ ├── PCRunable.swift │ ├── PCPlugin.swift │ ├── PCLogFilterPlugin.swift │ ├── PCTimerPlugin.swift │ └── PCHTTPFilterPlugin.swift │ ├── Pjango │ ├── Global.swift │ ├── Settings.swift │ └── Pjango.swift │ ├── View │ ├── PCDetailView.swift │ ├── PCViewable.swift │ ├── PCListView.swift │ └── PCView.swift │ ├── DB │ ├── PCDataBaseConfig.swift │ ├── PCFileDBConfig.swift │ ├── PCFileDBUtility.swift │ ├── PCDataBaseField.swift │ ├── PCDataBase.swift │ └── PCFileDBDataBase.swift │ ├── Model │ ├── PCObject.swift │ └── PCModel.swift │ ├── Server │ └── PCUrlConfig.swift │ ├── Utility │ ├── PCMustacheUtility.swift │ ├── Extension.swift │ ├── PCSqlUtility.swift │ └── PCLog.swift │ └── Runtime │ ├── PjangoDelegate.swift │ └── PjangoRuntime.swift ├── .github └── ISSUE_TEMPLATE.md ├── Package.swift ├── .gitignore ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md └── LICENSE /Assets/pjango.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enums/Pjango/HEAD/Assets/pjango.png -------------------------------------------------------------------------------- /Source/Pjango/Plugin/PCTaskPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCTaskPlugin.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCTaskPlugin: PCPlugin { } 12 | -------------------------------------------------------------------------------- /Source/Pjango/Pjango/Global.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Global.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/27. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public let PJANGO_HOST_DEFAULT = "_pjango_default" 12 | 13 | -------------------------------------------------------------------------------- /Source/Pjango/View/PCDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCDetailView.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCDetailView: PCView { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Source/Pjango/View/PCViewable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCViewable.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol PCViewable { 12 | 13 | func toViewParam() -> PCViewParam 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Source/Pjango/DB/PCDataBaseConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCDataBaseConfig.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCDataBaseConfig { 12 | 13 | public init() { } 14 | 15 | public init?(param: Dictionary) { } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Source/Pjango/Plugin/PCRunable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCRunable.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | public typealias PCTask = ()->Void 13 | 14 | public protocol PCRunable { 15 | 16 | var taskQueue: DispatchQueue { get } 17 | 18 | var task: PCTask? { get } 19 | 20 | func run() 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Source/Pjango/Model/PCObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCObject.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCObject { 12 | 13 | public var _pjango_core_class_name: String { 14 | return String(describing: Mirror.init(reflecting: self).subjectType) 15 | } 16 | 17 | public static var _pjango_core_class_name: String { 18 | return String(describing: Mirror.init(reflecting: self).subjectType).components(separatedBy: ".")[0] 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Source/Pjango/Server/PCUrlConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCUrlConfig.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/13. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | 12 | public struct PCUrlConfig { 13 | 14 | public var url: String 15 | public var handle: RequestHandler 16 | public var name: String? 17 | 18 | public init(url: String, handle: @escaping RequestHandler, name: String? = nil) { 19 | self.url = url 20 | self.handle = handle 21 | self.name = name 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Source/Pjango/Pjango/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Pjango 12 | 13 | public var PJANGO_WORKSPACE_PATH = "" 14 | 15 | public var PJANGO_SERVER_PORT: UInt16 = 8080 16 | 17 | public var PJANGO_LOG_DEBUG = true 18 | 19 | public var PJANGO_LOG_TO_FILE = true 20 | 21 | public var PJANGO_LOG_PATH = "pjango.log" 22 | 23 | // Django 24 | public var PJANGO_BASE_DIR = "" 25 | 26 | public var PJANGO_TEMPLATES_DIR = "templates" 27 | 28 | public var PJANGO_STATIC_URL = "static" 29 | 30 | -------------------------------------------------------------------------------- /Source/Pjango/Utility/PCMustacheUtility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCMustacheUtility.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/15. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectMustache 11 | 12 | final public class PCMustacheUtility { 13 | 14 | static public func getTemplate(path: String, param: Dictionary = [:]) throws -> String { 15 | let context = MustacheEvaluationContext.init(templatePath: path, map: param) 16 | let collector = MustacheEvaluationOutputCollector.init() 17 | return try context.formulateResponse(withCollector: collector) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Check List 2 | 3 | Thanks for considering to open an issue. Before you submit your issue, please confirm these boxes are checked. 4 | 5 | - [ ] I have read the [README.md](https://github.com/enums/Pjango/blob/master/README.md), but there is no information I need. 6 | - [ ] I have searched in [existing issues](https://github.com/enums/Pjango/issues?utf8=%E2%9C%93&q=is%3Aissue), but did find a same one. 7 | 8 | ### Issue Description 9 | 10 | #### Description 11 | 12 | [Tell us about the issue] 13 | 14 | #### Reproduce 15 | 16 | [The steps to reproduce this issue. What are the parameters, where did you put your code, etc.] 17 | 18 | #### Other Comment 19 | 20 | [Add anything else here] 21 | -------------------------------------------------------------------------------- /Source/Pjango/DB/PCFileDBConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCFileDBConfig.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/7/2. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCFileDBConfig: PCDataBaseConfig { 12 | 13 | public var schema = "default" 14 | public var path = "\(PJANGO_WORKSPACE_PATH)/filedb" 15 | 16 | public override init?(param: Dictionary) { 17 | super.init() 18 | guard let schema = param["SCHEMA"] as? String else { 19 | return nil 20 | } 21 | guard let path = param["PATH"] as? String else { 22 | return nil 23 | } 24 | self.schema = schema 25 | self.path = path 26 | } 27 | 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Source/Pjango/DB/PCFileDBUtility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCFileDBUtility.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/7/2. 6 | // 7 | // 8 | 9 | import Foundation 10 | import PerfectLib 11 | 12 | internal class PCFileDBUtility { 13 | 14 | 15 | internal static func dirPathForSchema(path: String, schema: String) -> String { 16 | return "\(path)/\(schema)" 17 | } 18 | 19 | internal static func filePathForTable(path: String, schema: String, table: String) -> String { 20 | return "\(dirPathForSchema(path: path, schema: schema))/\(table).json" 21 | } 22 | 23 | internal static func filePathForTable(path: String, schema: String, model: PCModel) -> String { 24 | let table = model.tableName 25 | return filePathForTable(path: path, schema: schema, table: table) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Pjango", 8 | products: [ 9 | .library( 10 | name: "Pjango", 11 | targets: ["Pjango"]), 12 | ], 13 | dependencies: [ 14 | .package(url:"https://github.com/PerfectlySoft/Perfect-HTTPServer.git" , from: "3.0.19"), 15 | .package(url:"https://github.com/PerfectlySoft/Perfect-Mustache.git" , from: "3.0.2"), 16 | .package(url:"https://github.com/enums/Pjango-SwiftyJSON" , from: "1.0.0"), 17 | ], 18 | targets: [ 19 | .target( 20 | name: "Pjango", 21 | dependencies: ["PerfectHTTPServer", "PerfectMustache", "SwiftyJSON"]) 22 | ] 23 | ) 24 | 25 | 26 | -------------------------------------------------------------------------------- /Source/Pjango/Runtime/PjangoDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PjangoDelegate.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/20. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | 12 | public protocol PjangoDelegate { 13 | 14 | func setSettings() 15 | 16 | func setUrls() -> [String: [PCUrlConfig]]? 17 | 18 | func registerPlugins() -> [PCPlugin]? 19 | 20 | func registerModels() -> [PCModel]? 21 | 22 | func setDB() -> PCDataBase? 23 | } 24 | 25 | public extension PjangoDelegate { 26 | 27 | func setSettings() { } 28 | 29 | func setUrls() -> [String: [PCUrlConfig]]? { return nil } 30 | 31 | func registerPlugins() -> [PCPlugin]? { return nil } 32 | 33 | func registerModels() -> [PCModel]? { return nil } 34 | 35 | func setDB() -> PCDataBase? { return nil } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Source/Pjango/Plugin/PCPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCPlugin.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dispatch 11 | 12 | public typealias PCMetaPlugin = PCPlugin 13 | 14 | open class PCPlugin: PCObject, PCRunable { 15 | 16 | public static var meta: PCMetaPlugin { 17 | return self.init() 18 | } 19 | 20 | required override public init() { } 21 | 22 | public var taskQueue: DispatchQueue = DispatchQueue.init(label: _pjango_core_class_name) 23 | 24 | open var task: PCTask? { 25 | return nil 26 | } 27 | 28 | internal var _pjango_core_plugin_task: PCTask? { 29 | return task 30 | } 31 | 32 | public func run() { 33 | _pjango_core_log.info("Plugin [\(_pjango_core_class_name)] run!") 34 | taskQueue.async { 35 | self._pjango_core_plugin_task?() 36 | _pjango_core_log.info("Plugin [\(self._pjango_core_class_name)] done!") 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Source/Pjango/View/PCListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCListView.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCListView: PCView { 12 | 13 | override internal var _pjango_core_view_param: PCViewParam { 14 | var param = super._pjango_core_view_param 15 | guard let lists = listObjectSets else { 16 | return param 17 | } 18 | for (list, objs) in lists { 19 | param[list] = objs.map { (obj) -> PCViewParam in 20 | var param: Dictionary = obj.toViewParam() 21 | if let extParam = listUserField(inList: list, forModel: obj) { 22 | extParam.forEach { (key, value) in 23 | param[key] = value 24 | } 25 | } 26 | return param 27 | } 28 | } 29 | return param 30 | } 31 | 32 | open func listUserField(inList list: String, forModel model: PCModel) -> PCViewParam? { 33 | return nil 34 | } 35 | 36 | 37 | open var listObjectSets: [String: [PCModel]]? { 38 | return nil 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Source/Pjango/Plugin/PCLogFilterPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCLogFilterPlugin.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/21. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | 12 | open class PCLogFilterPlugin: PCHTTPFilterPlugin { 13 | 14 | open override func requestFilter(req: HTTPRequest, res: HTTPResponse) -> Bool { 15 | let url = (req.header(.host) ?? "nil") + req.uri 16 | let method = req.method 17 | let host = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 18 | let port = UInt16(req.header(.custom(name: "watchdog_port")) ?? "") ?? req.remoteAddress.port 19 | _pjango_core_log.info("Req: [\(method)][\(host)][\(port)]: \(url)") 20 | return true 21 | } 22 | 23 | open override func responseFilterHeader(req: HTTPRequest, res: HTTPResponse) -> Bool { 24 | let code = res.status.code 25 | let url = (req.header(.host) ?? "nil") + req.uri 26 | let method = req.method 27 | let host = req.header(.custom(name: "watchdog_ip")) ?? req.remoteAddress.host 28 | let port = UInt16(req.header(.custom(name: "watchdog_port")) ?? "") ?? req.remoteAddress.port 29 | _pjango_core_log.info("Res: [\(code)][\(method)][\(host)][\(port)]: \(url)") 30 | return true 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Source/Pjango/Utility/Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/13. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | 12 | fileprivate var dateFormatter = { () -> DateFormatter in 13 | let that = DateFormatter.init() 14 | that.timeZone = TimeZone.init(secondsFromGMT: 8 * 3600) 15 | that.dateFormat = "yyyy-MM-dd HH:mm:ss" 16 | return that 17 | }() 18 | 19 | public extension Date { 20 | var stringValue: String { 21 | return dateFormatter.string(from: self) 22 | } 23 | } 24 | 25 | public extension String { 26 | var dateValue: Date? { 27 | return dateFormatter.date(from: self) 28 | } 29 | } 30 | 31 | public extension HTTPResponse { 32 | 33 | func _pjango_safe_setBody(_ body: String?, _ setContentLength: Bool = true) { 34 | self.setBody(string: body ?? "nil") 35 | if setContentLength { 36 | self.setHeader(.contentLength, value: "\(self.bodyBytes.count)") 37 | } 38 | } 39 | 40 | func _pjango_safe_setBody(_ body: [UInt8]?, _ setContentLength: Bool = true) { 41 | self.setBody(bytes: body ?? []) 42 | if setContentLength { 43 | self.setHeader(.contentLength, value: "\(self.bodyBytes.count)") 44 | } 45 | } 46 | } 47 | 48 | public extension HTTPRequest { 49 | func getUrlParam(key: String) -> String? { 50 | return self.param(name: key) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/Pjango/Plugin/PCTimerPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCTimerPlugin.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/18. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class PCTimerPlugin: PCPlugin { 12 | 13 | open var timerInterval: TimeInterval { 14 | return 1 15 | } 16 | 17 | open var timerDelay: TimeInterval { 18 | return 0 19 | } 20 | 21 | open var timerRepeatTimes: Int { 22 | return 0 23 | } 24 | 25 | override var _pjango_core_plugin_task: PCTask? { 26 | return { 27 | Thread.sleep(forTimeInterval: self.timerDelay) 28 | if self.timerRepeatTimes <= 0 { 29 | while true { 30 | _pjango_core_log.debug("Plugin [\(self._pjango_core_class_name)] triggered!") 31 | self.task?() 32 | Thread.sleep(forTimeInterval: self.timerInterval) 33 | } 34 | } else { 35 | var time = 0 36 | while true { 37 | _pjango_core_log.debug("Plugin [\(self._pjango_core_class_name)] triggered!") 38 | self.task?() 39 | time += 1 40 | if time >= self.timerRepeatTimes { 41 | break 42 | } 43 | Thread.sleep(forTimeInterval: self.timerInterval) 44 | } 45 | } 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Source/Pjango/Plugin/PCHTTPFilterPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCHTTPFilterPlugin.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/26. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | 12 | open class PCHTTPFilterPlugin: PCPlugin, HTTPRequestFilter, HTTPResponseFilter { 13 | 14 | open var priority: HTTPFilterPriority { 15 | return .low 16 | } 17 | 18 | open func requestFilter(req: HTTPRequest, res: HTTPResponse) -> Bool { return true } 19 | 20 | open func responseFilterHeader(req: HTTPRequest, res: HTTPResponse) -> Bool { return true } 21 | 22 | open func responseFilterBody(req: HTTPRequest, res: HTTPResponse) -> Bool { return true } 23 | 24 | public func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) { 25 | if requestFilter(req: request, res: response) { 26 | callback(.continue(request, response)) 27 | } else { 28 | callback(.halt(request, response)) 29 | } 30 | } 31 | 32 | public func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 33 | if responseFilterHeader(req: response.request, res: response) { 34 | callback(.continue) 35 | } else { 36 | callback(.done) 37 | } 38 | } 39 | 40 | public func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 41 | if responseFilterBody(req: response.request, res: response) { 42 | callback(.continue) 43 | } else { 44 | callback(.done) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## OS 6 | .DS_Store 7 | .DS_Store? 8 | *.swp 9 | ._* 10 | .Spotlight-V100 11 | .Trashes 12 | Icon? 13 | ehthumbs.db 14 | Thumbs.db 15 | 16 | ## Build generated 17 | build/ 18 | DerivedData/ 19 | 20 | ## Various settings 21 | *.pbxuser 22 | !default.pbxuser 23 | *.mode1v3 24 | !default.mode1v3 25 | *.mode2v3 26 | !default.mode2v3 27 | *.perspectivev3 28 | !default.perspectivev3 29 | xcuserdata/ 30 | 31 | ## Other 32 | *.moved-aside 33 | *.xccheckout 34 | *.xcscmblueprint 35 | 36 | ## Obj-C/Swift specific 37 | *.hmap 38 | *.ipa 39 | *.dSYM.zip 40 | *.dSYM 41 | 42 | ## Playgrounds 43 | timeline.xctimeline 44 | playground.xcworkspace 45 | 46 | # Swift Package Manager 47 | # 48 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 49 | # Packages/ 50 | # Package.pins 51 | .build/ 52 | 53 | # CocoaPods 54 | # 55 | # We recommend against adding the Pods directory to your .gitignore. However 56 | # you should judge for yourself, the pros and cons are mentioned at: 57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 58 | # 59 | # Pods/ 60 | 61 | # Carthage 62 | # 63 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 64 | # Carthage/Checkouts 65 | 66 | Carthage/Build 67 | 68 | # fastlane 69 | # 70 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 71 | # screenshots whenever they are needed. 72 | # For more information about the recommended setup visit: 73 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 74 | 75 | fastlane/report.xml 76 | fastlane/Preview.html 77 | fastlane/screenshots 78 | fastlane/test_output 79 | Package.pins 80 | Pjango.xcodeproj/ 81 | /Package.resolved 82 | -------------------------------------------------------------------------------- /Source/Pjango/Pjango/Pjango.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pjango.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/13. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | 12 | public typealias PCUrlHandle = RequestHandler 13 | 14 | public func pjangoUrl(_ url: String, name: String? = nil, handle: @escaping (() -> PCUrlHandle)) -> PCUrlConfig { 15 | return pjangoUrl(url, name: name, handle: handle()) 16 | } 17 | 18 | public func pjangoUrl(_ url: String, name: String? = nil, handle: @escaping PCUrlHandle) -> PCUrlConfig { 19 | return PCUrlConfig(url: url, handle: handle, name: name) 20 | } 21 | 22 | public func pjangoUrlReverse(host: String, name: String) -> String? { 23 | guard let config = PjangoRuntime._pjango_runtime_urls_name2config["\(host)@\(name)"] else { 24 | return nil 25 | } 26 | if host == PJANGO_HOST_DEFAULT { 27 | return "/\(config.url)" 28 | } else { 29 | return "http://\(host)/\(config.url)" 30 | } 31 | } 32 | 33 | public func pjangoUrlConfigReverse(name: String) -> PCUrlConfig? { 34 | return PjangoRuntime._pjango_runtime_urls_name2config[name] 35 | } 36 | 37 | public func pjangoHttpResponse(_ msg: String) -> PCUrlHandle { 38 | return pjangoHttpResponse { req, res in 39 | res._pjango_safe_setBody(msg) 40 | } 41 | } 42 | 43 | public func pjangoHttpResponse(_ bytes: [UInt8]) -> PCUrlHandle { 44 | return pjangoHttpResponse { req, res in 45 | res._pjango_safe_setBody(bytes) 46 | } 47 | } 48 | 49 | public func pjangoHttpResponse(_ handle: @escaping RequestHandler) -> PCUrlHandle { 50 | return handle 51 | } 52 | 53 | public func pjangoHttpRedirect(host: String = PJANGO_HOST_DEFAULT, name: String) -> PCUrlHandle? { 54 | guard let url = pjangoUrlReverse(host: host, name: name) else { 55 | return nil 56 | } 57 | return pjangoHttpRedirect(url: url) 58 | } 59 | 60 | public func pjangoHttpRedirect(url: String) -> PCUrlHandle { 61 | return pjangoHttpResponse { _, res in 62 | res.status = .temporaryRedirect 63 | res.setHeader(.location, value: url) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Source/Pjango/View/PCView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCView.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/15. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | import PerfectHTTPServer 12 | import PerfectMustache 13 | 14 | public typealias PCViewParam = Dictionary 15 | 16 | open class PCView: PCObject { 17 | 18 | internal var _pjango_core_view_template_path: String { 19 | return "\(PJANGO_TEMPLATES_DIR)/\(templateName ?? "")" 20 | } 21 | 22 | internal var _pjango_core_view_param: PCViewParam { 23 | return viewParam ?? PCViewParam() 24 | } 25 | 26 | public static var meta: PCView { 27 | return self.init() 28 | } 29 | 30 | open var templateName: String? { 31 | return nil 32 | } 33 | 34 | open var viewParam: PCViewParam? { 35 | return nil 36 | } 37 | 38 | required public override init() { } 39 | 40 | open weak var currentRequest: HTTPRequest? = nil 41 | 42 | public static func asHandle() -> PCUrlHandle { 43 | let handle: RequestHandler = { req, res in 44 | let view = self.init() 45 | view.currentRequest = req 46 | guard view.requestVaild(req) else { 47 | guard let invaildHandle = view.requestInvaildHandle() else { 48 | res._pjango_safe_setBody("Oops! Request is invaild but the `invaild handle` is nil!") 49 | _pjango_core_log.error("Failed on rendering view when request is invaild!") 50 | return 51 | } 52 | invaildHandle(req, res) 53 | return 54 | } 55 | res._pjango_safe_setBody(view.getTemplate()) 56 | view.currentRequest = nil 57 | } 58 | return handle 59 | 60 | } 61 | 62 | open func requestVaild(_ req: HTTPRequest) -> Bool { 63 | return req.method == .get 64 | } 65 | 66 | open func requestInvaildHandle() -> PCUrlHandle? { 67 | return nil 68 | } 69 | 70 | open func getTemplate() -> String { 71 | do { 72 | let param = _pjango_core_view_param 73 | _pjango_core_log.debug("Rendering [\(_pjango_core_class_name)]:\nTemplate: \(_pjango_core_view_template_path)") 74 | return try PCMustacheUtility.getTemplate(path: _pjango_core_view_template_path, param: param) 75 | } catch { 76 | _pjango_core_log.error(error) 77 | return "Oops! Something wrong when rendering view!" 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Source/Pjango/DB/PCDataBaseField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCDataBaseField.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum PCDataBaseFieldType: String { 12 | case unknow = "" 13 | case string = "VARCHAR" 14 | case int = "INT" 15 | case text = "TEXT" 16 | } 17 | 18 | public protocol PCModelDataBaseFieldType { } 19 | extension String: PCModelDataBaseFieldType { } 20 | extension Int: PCModelDataBaseFieldType { } 21 | 22 | final public class PCDataBaseField { 23 | 24 | public weak var model: PCModel? = nil 25 | 26 | internal var _pjango_core_set_model_block: (()->Void)? = nil 27 | 28 | public var value: PCModelDataBaseFieldType? { 29 | get { 30 | return model?._pjango_core_model_get_filed_data(key: name) 31 | } 32 | set { 33 | guard let value = newValue else { 34 | return 35 | } 36 | model?._pjango_core_model_set_field_data(key: name, value: value) 37 | } 38 | } 39 | 40 | public var intValue: Int { 41 | get { 42 | return value as! Int 43 | } 44 | set { 45 | value = newValue 46 | } 47 | } 48 | 49 | public var strValue: String { 50 | get { 51 | return value as! String 52 | } 53 | set { 54 | value = newValue 55 | } 56 | } 57 | 58 | public var name: String 59 | public var type: PCDataBaseFieldType { 60 | get { 61 | return model?._pjango_core_model_fields_type[name] ?? .unknow 62 | } 63 | set { 64 | model?._pjango_core_model_fields_type[name] = newValue 65 | } 66 | } 67 | public var length = 11 68 | public var notNull = false 69 | public var defaultValue: String? 70 | 71 | public init(name: String, type: PCDataBaseFieldType, length: Int = 11, 72 | value: PCModelDataBaseFieldType? = nil, notNull: Bool = false, defaultValue: String? = nil) { 73 | self.name = name 74 | _pjango_core_set_model_block = { [weak self] in 75 | self?.length = length 76 | self?.value = value 77 | self?.type = type 78 | self?.notNull = notNull 79 | self?.defaultValue = defaultValue 80 | } 81 | } 82 | 83 | 84 | internal func _pjango_core_toSql() -> PCSqlStatement { 85 | let typeStr = "\(type.rawValue)(\(length))" 86 | let nullStr: String 87 | if notNull, let defaultValue = self.defaultValue { 88 | nullStr = "NOT NULL DEFAULT '\(defaultValue)'" 89 | } else { 90 | nullStr = "NULL" 91 | } 92 | return "`\(name)` \(typeStr) \(nullStr)" 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /Source/Pjango/Utility/PCSqlUtility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCSqlUtility.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias PCSqlStatement = String 12 | public typealias PCDataBaseRecord = [String] 13 | 14 | final public class PCSqlUtility { 15 | 16 | public static func createSchema(_ name: String) -> PCSqlStatement { 17 | return "CREATE SCHEMA `\(name)` DEFAULT CHARACTER SET utf8mb4;" 18 | } 19 | 20 | public static func dropSchema(_ name: String) -> PCSqlStatement { 21 | return "DROP DATABASE `\(name)`;" 22 | } 23 | 24 | public static func selectSchema(_ name: String) -> PCSqlStatement { 25 | return "SELECT * FROM information_schema.SCHEMATA where SCHEMA_NAME='\(name)';" 26 | } 27 | 28 | internal static func schemaAndTableToStr(_ schema: String? = nil, _ table: String) -> String { 29 | if let schema = schema { 30 | return "`\(schema)`.`\(table)`" 31 | } else { 32 | return "`\(table)`" 33 | } 34 | } 35 | 36 | public static func createTable(_ schema: String? = nil, _ table: String, _ fields: Array) -> PCSqlStatement { 37 | let fieldsStr = fields.reduce("") { 38 | $0 + ", " + $1._pjango_core_toSql() 39 | } 40 | return "CREATE TABLE \(schemaAndTableToStr(schema, table)) (`_pjango_id` INT AUTO_INCREMENT \(fieldsStr), PRIMARY KEY (`_pjango_id`));" 41 | } 42 | 43 | public static func dropTable(_ schema: String? = nil, _ table: String) -> PCSqlStatement { 44 | return "DROP TABLE \(schemaAndTableToStr(schema, table));" 45 | } 46 | 47 | public static func selectTable(_ schema: String? = nil, _ table: String, _ fields: String = "*", ext: String? = nil) -> PCSqlStatement { 48 | return "SELECT \(fields) FROM \(schemaAndTableToStr(schema, table)) \(ext ?? "");" 49 | } 50 | 51 | public static func insertRecord(_ schema: String? = nil, _ table: String, _ record: PCDataBaseRecord) -> PCSqlStatement { 52 | let recordStr = record.reduce("'0'") { 53 | "\($0), '\(($1.replacingOccurrences(of: "'", with: "\\'")))'" 54 | } 55 | return "INSERT INTO \(schemaAndTableToStr(schema, table)) VALUES (\(recordStr))" 56 | } 57 | 58 | public static func updateRecord(_ schema: String? = nil, _ table: String, id: Int64, fields: [String], record: PCDataBaseRecord) -> PCSqlStatement { 59 | var updateStr = "" 60 | for i in 0.. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | 一款基于 `Swift 3.x` 的服务端框架,使用 `MVC` 设计你的服务端软件。 15 | 16 | ## 更新 17 | 18 | 我正在计划结束这个项目,原因如下: 19 | 20 | - 早起参考了一些 Django 的设计,导致无论是架构还是 API 都非常不 Swifty。 21 | - 这个框架最早诞生于 Swift 3.x,经历了几次版本迁移,但都仅限于解决了编译问题。 22 | - 数据库方面薄弱,写页面依然需要写 HTML,整体完成度不高。 23 | - 早期的一些设计在今天有了更好的解决方法。 24 | 25 | 这是我第一个真正意义上的服务端项目,现如今它在线上跑了整整两年,目前依然在为我服务,但在不远的将来我会把它换下。 26 | 27 | 感谢关注。 28 | 29 | —— 2019.07.23 30 | 31 | 新的解决方案已经开发完成,见:[https://github.com/enums/Heze](https://github.com/enums/Heze) 32 | 33 | —— 2019.09.10 34 | 35 | 今日发现在 Swift 5 的编译器下已无法正常工作,请不要再使用了。PCModel 的 meta 方法构造出了父类实例,是个空的模型描述。 36 | 37 | —— 2019.12.02 38 | 39 | ## 使用方法 40 | 41 | - 可能需要安装 OpenSSL 1.0.2:[https://gist.github.com/mbejda/a1dabc45b32aaf8b25ae5e8d05923518](https://gist.github.com/mbejda/a1dabc45b32aaf8b25ae5e8d05923518) 42 | 43 | - 克隆此仓库 44 | - macOS:使用下面的命令生成 Xcode 工程进行编译: 45 | 46 | ```bash 47 | $ swift package generate-xcodeproj 48 | ``` 49 | 50 | - Linux:使用`Swift Package Manager`编译: 51 | 52 | ```bash 53 | $ swift build 54 | ``` 55 | 56 | ## 范例 57 | 58 | - [基础模板](https://github.com/enums/pjango-template):最基础的例子。 59 | - [Calatrava](https://github.com/enums/calatrava):我的开源博客,Pjango 的深度使用。 60 | - [Postman](https://github.com/enums/postman):HTTP 转发服务器,Calatrava 中的 Instagram 模块依赖在远程服务器部署的 Postman。 61 | 62 | ## 组件 63 | 64 | ### 模型组件 65 | 66 | - Pjango-Core-Model:内置的模型核心驱动。 67 | 68 | ### 视图组件 69 | 70 | - Pjango-Core-View:内置的视图核心驱动。 71 | - Pjango-Core-ListView:内置的列表类视图。 72 | - Pjango-Core-DetailView:内置的展示类视图。 73 | 74 | ### 数据库组件 75 | 76 | - Pjango-Core-DataBase:内置的数据库核心驱动。 77 | - Pjango-Core-FileDB:内置的文件驱动组件。 78 | - [Pjango-MySQL](https://github.com/enums/pjango-mysql):MySQL 数据库支持组件。 79 | 80 | ### 插件式组件 81 | 82 | - Pjango-Core-Plugin:内置的插件核心驱动。 83 | - Pjango-Core-TaskPlugin:内置的一次性任务组件。 84 | - Pjango-Core-TimerPlugin:内置的延时、定时、重复定时任务组件。 85 | - Pjango-Core-HTTPFilterPlugin:内置的 HTTP 服务过滤器组件。 86 | - Pjango-Core-LogFilterPlugin:内置的 HTTP 过滤器日志组件。 87 | 88 | ### 其他功能组件 89 | 90 | - [Pjango-JianshuPlugin](https://github.com/enums/Pjango-JianshuPlugin):简书的定时爬虫组件。 91 | - [Pjango-SteamPlugin](https://github.com/enums/Pjango-SteamPlugin):Steam 主页背景图和头像的抓取以及适用于 Calatrava 的背景和头像的替换。 92 | - [Pjango-Postman](https://github.com/enums/Pjango-Postman):向 Postman 代理发出请求的组件。 93 | 94 | 95 | ## 联系我 96 | 97 | 发邮件给我:[enum@enumsblog.com](mailto:enum@enumsblog.com) 98 | 99 | ## 协议 100 | 101 | Apache-2.0 license 102 | 103 | Pjango 基于 Apache-2.0 协议进行分发和使用,更多信息参见协议文件。 104 | -------------------------------------------------------------------------------- /Source/Pjango/Model/PCModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCModel.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias PCMetaModel = PCModel 12 | 13 | open class PCModel: PCObject, PCViewable { 14 | 15 | internal var _pjango_core_model_fields = Array() 16 | 17 | internal var _pjango_core_model_fields_key = Array() 18 | 19 | internal var _pjango_core_model_fields_type = Dictionary() 20 | 21 | internal var _pjango_core_model_fields_value = Dictionary() 22 | 23 | internal func _pjango_core_model_get_filed_data(key: String) -> PCModelDataBaseFieldType? { 24 | guard let type = _pjango_core_model_fields_type[key], let value = _pjango_core_model_fields_value[key] else { 25 | return nil 26 | } 27 | switch type { 28 | case .unknow: return nil 29 | case .string: return value as? String 30 | case .int: return value as? Int 31 | case .text: return value as? String 32 | } 33 | } 34 | 35 | internal func _pjango_core_model_set_field_data(index: Int, value: Any) { 36 | guard index < _pjango_core_model_fields_key.count else { 37 | return 38 | } 39 | let key = _pjango_core_model_fields_key[index] 40 | _pjango_core_model_set_field_data(key: key, value: value) 41 | } 42 | 43 | 44 | internal func _pjango_core_model_set_field_data(key: String, value: Any) { 45 | guard let type = _pjango_core_model_fields_type[key] else { 46 | return 47 | } 48 | switch type { 49 | case .string, .text: 50 | guard let strValue = value as? String else { 51 | return 52 | } 53 | _pjango_core_model_fields_value[key] = strValue 54 | case .int: 55 | let intValue: Int 56 | if let int = value as? Int { 57 | intValue = int 58 | } else if let str = value as? String, let int = Int(str) { 59 | intValue = int 60 | } else { 61 | return 62 | } 63 | _pjango_core_model_fields_value[key] = intValue 64 | case .unknow: 65 | return 66 | } 67 | } 68 | 69 | internal static var _pjango_core_model_cache = Dictionary>() 70 | 71 | internal static var _pjango_core_model_cache_time = Dictionary() 72 | 73 | internal var _pjango_core_model_id: Int64? = nil 74 | 75 | open var tableName: String { 76 | return "" 77 | } 78 | 79 | open class var cacheTime: TimeInterval? { 80 | return nil 81 | } 82 | 83 | open class func cacheRemove() { 84 | _pjango_core_model_cache[_pjango_core_class_name] = nil 85 | _pjango_core_model_cache_time[_pjango_core_class_name] = nil 86 | } 87 | 88 | open func initialObjects() -> [PCModel]? { 89 | return nil 90 | } 91 | 92 | open class func queryObjects(_ type: T.Type, ext: (useCache: Bool, param: String)? = nil) -> [T]? { 93 | guard let meta = PjangoRuntime._pjango_runtime_models_name2meta[_pjango_core_class_name] else { 94 | return nil 95 | } 96 | let nowTime = Date.init() 97 | let records: [PCDataBaseRecord] 98 | if ext == nil, 99 | let cacheTime = cacheTime, 100 | let cache = _pjango_core_model_cache[_pjango_core_class_name], 101 | let lastCacheTime = _pjango_core_model_cache_time[_pjango_core_class_name], 102 | nowTime.timeIntervalSince1970 - lastCacheTime <= cacheTime { 103 | records = cache 104 | } else { 105 | guard let recordsFromDB = PjangoRuntime._pjango_runtime_database.selectTable(model: meta, ext: ext?.param) else { 106 | return nil 107 | } 108 | if ext?.useCache == true { 109 | _pjango_core_model_cache[_pjango_core_class_name] = recordsFromDB 110 | } 111 | _pjango_core_model_cache_time[_pjango_core_class_name] = nowTime.timeIntervalSince1970 112 | records = recordsFromDB 113 | } 114 | return records.compactMap { record in 115 | var record = record 116 | let idStr = record.removeFirst() 117 | guard let id = Int64(idStr) else { 118 | return nil 119 | } 120 | let model = self.init() 121 | model._pjango_core_model_id = id 122 | for i in 0.. Bool { 130 | if PjangoRuntime._pjango_runtime_database.insertModel(model) == true { 131 | cacheRemove() 132 | return true 133 | } else { 134 | return false 135 | } 136 | } 137 | 138 | @discardableResult 139 | open class func updateObject(_ model: PCModel) -> Bool { 140 | if PjangoRuntime._pjango_runtime_database.updateModel(model) == true { 141 | cacheRemove() 142 | return true 143 | } else { 144 | return false 145 | } 146 | } 147 | 148 | public static var meta: PCMetaModel { 149 | return self.init() 150 | } 151 | 152 | required public override init() { 153 | super.init() 154 | doRegisterFields(registerFields()) 155 | } 156 | 157 | open func registerFields() -> [PCDataBaseField] { 158 | return [] 159 | } 160 | 161 | fileprivate func doRegisterFields(_ fields: [PCDataBaseField]) { 162 | _pjango_core_model_fields = fields 163 | _pjango_core_model_fields_key = fields.map { 164 | $0.model = self 165 | return $0.name 166 | } 167 | fields.forEach { 168 | $0._pjango_core_set_model_block?() 169 | $0._pjango_core_set_model_block = nil 170 | _pjango_core_model_fields_type[$0.name] = $0.type 171 | } 172 | } 173 | 174 | open var viewParamPrefix: String { 175 | return "_pjango_param_table_\(tableName)_" 176 | } 177 | 178 | public func toViewParam() -> PCViewParam { 179 | var param = PCViewParam() 180 | _pjango_core_model_fields_value.forEach { (key, value) in 181 | param["\(viewParamPrefix)\(key)"] = value 182 | } 183 | return param 184 | } 185 | 186 | } 187 | 188 | -------------------------------------------------------------------------------- /Source/Pjango/DB/PCDataBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCDataBase.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/17. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum PCDataBaseState { 12 | case empty 13 | case inited 14 | case setuped 15 | case connected 16 | case disconnected 17 | } 18 | 19 | open class PCDataBase { 20 | 21 | open var schema: String? { 22 | return nil 23 | } 24 | 25 | public let config: PCDataBaseConfig 26 | open var state: PCDataBaseState 27 | 28 | public let _pjango_core_database_lock = NSLock.init() 29 | 30 | public init(config: PCDataBaseConfig) { 31 | self.config = config 32 | self.state = .inited 33 | } 34 | 35 | public static var empty: PCDataBase { 36 | let database = PCDataBase.init(config: PCDataBaseConfig.init()) 37 | database.state = .empty 38 | return database 39 | } 40 | 41 | open func setup() { 42 | _pjango_core_database_lock.lock() 43 | defer { 44 | _pjango_core_database_lock.unlock() 45 | } 46 | guard state != .empty else { 47 | return 48 | } 49 | doSetup() 50 | self.state = .setuped 51 | } 52 | 53 | open func doSetup() { } 54 | 55 | open func connect() { 56 | _pjango_core_database_lock.lock() 57 | defer { 58 | _pjango_core_database_lock.unlock() 59 | } 60 | guard state == .setuped || state == .disconnected else { 61 | return 62 | } 63 | doConnect() 64 | self.state = .connected 65 | } 66 | 67 | open func doConnect() { } 68 | 69 | @discardableResult 70 | open func query(_ sql: PCSqlStatement) -> [PCDataBaseRecord]? { 71 | _pjango_core_database_lock.lock() 72 | defer { 73 | _pjango_core_database_lock.unlock() 74 | } 75 | guard state == .connected else { 76 | return nil 77 | } 78 | _pjango_core_log.debug("Query: \(sql)") 79 | return doQuery(sql) 80 | } 81 | 82 | open func doQuery(_ sql: PCSqlStatement) -> [PCDataBaseRecord]? { return nil } 83 | 84 | open func isSchemaExist(_ schema: String) -> Bool { 85 | guard let ret = query(PCSqlUtility.selectSchema(schema)) else { 86 | return false 87 | } 88 | if ret.count >= 1 { 89 | return true 90 | } else { 91 | return false 92 | } 93 | } 94 | 95 | 96 | open func isTableExist(_ table: String) -> Bool { 97 | return query(PCSqlUtility.selectTable(schema, table, ext: "LIMIT 1")) != nil ? true : false 98 | } 99 | 100 | open func createSchema() { 101 | guard let schema = schema else { 102 | _pjango_core_log.error("Failed on creating schema. Schema value is nil.") 103 | return 104 | } 105 | guard query(PCSqlUtility.createSchema(schema)) != nil else { 106 | _pjango_core_log.error("Failed on creating schema `\(schema)`") 107 | return 108 | } 109 | _pjango_core_log.info("Success on creating schema `\(schema)`") 110 | } 111 | 112 | open func dropSchema() { 113 | guard let schema = schema else { 114 | _pjango_core_log.error("Failed on droping schema. Schema value is nil.") 115 | return 116 | } 117 | guard query(PCSqlUtility.dropSchema(schema)) != nil else { 118 | _pjango_core_log.error("Failed on droping schema `\(schema)`") 119 | return 120 | } 121 | _pjango_core_log.info("Sucess on droping schema `\(schema)`") 122 | } 123 | 124 | open func createTable(model: PCMetaModel) { 125 | guard query(PCSqlUtility.createTable(schema, model.tableName, model._pjango_core_model_fields)) != nil else { 126 | _pjango_core_log.error("Failed on creating table \(PCSqlUtility.schemaAndTableToStr(schema, model.tableName))") 127 | return 128 | } 129 | _pjango_core_log.info("Success on creating table \(PCSqlUtility.schemaAndTableToStr(schema, model.tableName))") 130 | } 131 | 132 | open func dropTable(model: PCMetaModel) { 133 | dropTable(model.tableName) 134 | } 135 | 136 | open func dropTable(_ table: String) { 137 | guard query(PCSqlUtility.dropTable(schema, table)) != nil else { 138 | _pjango_core_log.error("Failed on droping table \(PCSqlUtility.schemaAndTableToStr(schema, table))") 139 | return 140 | } 141 | _pjango_core_log.info("Success on droping table \(PCSqlUtility.schemaAndTableToStr(schema, table))") 142 | } 143 | 144 | open func selectTable(model: PCMetaModel, ext: String? = nil) -> [PCDataBaseRecord]? { 145 | return selectTable(table: model.tableName, ext: ext) 146 | } 147 | 148 | open func selectTable(table: String, ext: String? = nil) -> [PCDataBaseRecord]? { 149 | return query(PCSqlUtility.selectTable(schema, table, ext: ext)) 150 | } 151 | 152 | @discardableResult 153 | open func insertModel(_ model: PCModel) -> Bool { 154 | let record = model._pjango_core_model_fields.compactMap { (field) -> String? in 155 | switch field.type { 156 | case .string: return field.strValue 157 | case .int: return "\(field.intValue)" 158 | case .text: return field.strValue 159 | case .unknow: return nil 160 | } 161 | } 162 | guard record.count == model._pjango_core_model_fields.count else { 163 | return false 164 | } 165 | guard query(PCSqlUtility.insertRecord(schema, model.tableName, record)) != nil else { 166 | _pjango_core_log.error("Failed on insert model \(PCSqlUtility.schemaAndTableToStr(schema, model.tableName))") 167 | return false 168 | } 169 | return true 170 | } 171 | 172 | @discardableResult 173 | open func updateModel(_ model: PCModel) -> Bool { 174 | guard let id = model._pjango_core_model_id else { 175 | return false 176 | } 177 | let updateStr = model._pjango_core_model_fields_key.compactMap { (key) -> String? in 178 | guard let type = model._pjango_core_model_fields_type[key], let value = model._pjango_core_model_get_filed_data(key: key) else { 179 | return nil 180 | } 181 | switch type { 182 | case .string: 183 | guard let strValue = value as? String else { 184 | return nil 185 | } 186 | return strValue 187 | case .int: 188 | guard let intValue = value as? Int else { 189 | return nil 190 | } 191 | return "\(intValue)" 192 | case .text: 193 | guard let textValue = value as? String else { 194 | return nil 195 | } 196 | return textValue 197 | case .unknow: return nil 198 | } 199 | } 200 | guard updateStr.count == model._pjango_core_model_fields_key.count else { 201 | return false 202 | } 203 | guard query(PCSqlUtility.updateRecord(schema, model.tableName, id: id, fields: model._pjango_core_model_fields_key, record: updateStr)) != nil else { 204 | _pjango_core_log.error("Failed on update model \(PCSqlUtility.schemaAndTableToStr(schema, model.tableName))") 205 | return false 206 | } 207 | return true 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /Source/Pjango/Runtime/PjangoRuntime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PjangoRuntime.swift 3 | // Pjango 4 | // 5 | // Created by 郑宇琦 on 2017/6/20. 6 | // Copyright © 2017年 郑宇琦. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PerfectHTTP 11 | import PerfectHTTPServer 12 | 13 | public class PjangoRuntime { 14 | 15 | internal static let _pjango_runtime_log = PCLog.init(tag: "Pjango-Runtime") 16 | 17 | public static var _pjango_runtime_urls_name2config = Dictionary() 18 | 19 | public static var _pjango_runtime_urls_list = Array<(String, PCUrlConfig)>() 20 | 21 | public static var _pjango_runtime_models_name2meta = Dictionary() 22 | 23 | public static var _pjango_runtime_database = PCDataBase.empty 24 | 25 | public static var _pjango_runtime_server = HTTPServer.init() 26 | 27 | public static var _pjango_runtime_plugin = Array() 28 | 29 | public static func run(delegate: PjangoDelegate) { 30 | _pjango_runtime_run(delegate: delegate) 31 | } 32 | 33 | internal static func _pjango_runtime_run(delegate: PjangoDelegate) { 34 | // MARK: - Prepare 35 | print("Hello Pjango!") 36 | 37 | // MARK: - Configuration 38 | print("Configuring...") 39 | _pjango_runtime_setSettings(delegate: delegate) 40 | _pjango_runtime_setUrls(delegate: delegate) 41 | _pjango_runtime_setDB(delegate: delegate) 42 | _pjango_runtime_registerModels(delegate: delegate) 43 | _pjango_runtime_registerPlugins(delegate: delegate) 44 | 45 | // MARK: - Server 46 | _pjango_runtime_log.info("Starting...") 47 | _pjango_runtime_setServer(delegate: delegate) 48 | do { 49 | try _pjango_runtime_server.start() 50 | } catch { 51 | _pjango_runtime_log.error(error) 52 | } 53 | } 54 | 55 | internal static func _pjango_runtime_setSettings(delegate: PjangoDelegate) { 56 | 57 | delegate.setSettings() 58 | 59 | //Pjango 60 | 61 | PJANGO_TEMPLATES_DIR = "\(PJANGO_WORKSPACE_PATH)/\(PJANGO_TEMPLATES_DIR)" 62 | PJANGO_LOG_PATH = "\(PJANGO_WORKSPACE_PATH)/\(PJANGO_LOG_PATH)" 63 | 64 | //Django 65 | 66 | PJANGO_BASE_DIR = "\(PJANGO_WORKSPACE_PATH)/\(PJANGO_BASE_DIR)" 67 | PJANGO_STATIC_URL = "\(PJANGO_WORKSPACE_PATH)/\(PJANGO_STATIC_URL)" 68 | } 69 | 70 | internal static func _pjango_runtime_setUrls(delegate: PjangoDelegate) { 71 | 72 | guard let list = delegate.setUrls() else { 73 | return 74 | } 75 | 76 | list.forEach { (host, configList) in 77 | guard configList.count > 0 else { 78 | return 79 | } 80 | for config in configList { 81 | _pjango_runtime_urls_list.append((host, config)) 82 | if let name = config.name { 83 | _pjango_runtime_urls_name2config["\(host)@\(name)"] = config 84 | } 85 | } 86 | } 87 | } 88 | 89 | internal static func _pjango_runtime_registerPlugins(delegate: PjangoDelegate) { 90 | 91 | _pjango_runtime_plugin = delegate.registerPlugins() ?? [] 92 | 93 | _pjango_runtime_plugin.forEach { 94 | if let filter = $0 as? PCHTTPFilterPlugin { 95 | _pjango_runtime_server.setRequestFilters([(filter, filter.priority)]) 96 | _pjango_runtime_server.setResponseFilters([(filter, filter.priority)]) 97 | } else { 98 | $0.run() 99 | } 100 | } 101 | 102 | } 103 | 104 | internal static func _pjango_runtime_setDB(delegate: PjangoDelegate) { 105 | 106 | let database = delegate.setDB() ?? PCDataBase.empty 107 | 108 | _pjango_runtime_database = database 109 | 110 | guard database.state != .empty else { 111 | return 112 | } 113 | 114 | database.setup() 115 | database.connect() 116 | 117 | if let schema = database.schema { 118 | if !database.isSchemaExist(schema) { 119 | _pjango_runtime_log.info("Schema `\(schema)` is not exist! Create it.") 120 | database.createSchema() 121 | } 122 | } 123 | 124 | } 125 | 126 | internal static func _pjango_runtime_registerModels(delegate: PjangoDelegate) { 127 | 128 | let metas = delegate.registerModels() ?? [] 129 | 130 | metas.forEach { meta in 131 | if !_pjango_runtime_database.isTableExist(meta.tableName) { 132 | _pjango_runtime_log.info("Table `\(meta.tableName)` is not exist! Create it.") 133 | _pjango_runtime_database.createTable(model: meta) 134 | if let initialObjects = meta.initialObjects() { 135 | initialObjects.forEach { 136 | _pjango_runtime_database.insertModel($0) 137 | } 138 | } 139 | } 140 | _pjango_runtime_models_name2meta[meta._pjango_core_class_name] = meta 141 | } 142 | } 143 | 144 | internal static func _pjango_runtime_setServer(delegate: PjangoDelegate) { 145 | 146 | 147 | //url->config 148 | var defaultConfig = [String: PCUrlConfig]() 149 | 150 | //(posts, config) 151 | var userConfig = [(String, PCUrlConfig)]() 152 | 153 | _pjango_runtime_urls_list.forEach { (host, config) in 154 | if host == PJANGO_HOST_DEFAULT { 155 | defaultConfig[config.url] = config 156 | } else { 157 | userConfig.append((host, config)) 158 | } 159 | } 160 | 161 | //[host: [url: config]] -> [url: [(host, config)]] 162 | //[host: [url: config]] 163 | var hostUrlConfig = [String: [String: PCUrlConfig]]() 164 | 165 | userConfig.forEach { (host, config) in 166 | if hostUrlConfig[host] == nil { 167 | hostUrlConfig[host] = [String: PCUrlConfig]() 168 | } 169 | hostUrlConfig[host]![config.url] = config 170 | } 171 | 172 | //[url: [(host, config)]] 173 | var urlHostConfig = [String: [(String, PCUrlConfig)]]() 174 | 175 | for (host, urlConfig) in hostUrlConfig { 176 | for (url, config) in urlConfig { 177 | if urlHostConfig[url] == nil { 178 | urlHostConfig[url] = [(host, config)] 179 | } else { 180 | urlHostConfig[url]!.append((host, config)) 181 | } 182 | } 183 | } 184 | 185 | var routeList = defaultConfig.map { (url, config) in 186 | Route.init(uri: url) { req, res in 187 | config.handle(req, res) 188 | res.completed() 189 | } 190 | } 191 | 192 | for (url, configList) in urlHostConfig { 193 | let route = Route.init(uri: url) { req, res in 194 | guard let index = configList.index(where: { $0.0 == req.header(.host)?.split(separator: ":").first ?? "" }) else { 195 | if let dc = defaultConfig[url] { 196 | dc.handle(req, res) 197 | res.completed() 198 | return 199 | } else { 200 | res.status = .notFound 201 | res.completed() 202 | return 203 | } 204 | } 205 | let (_, config) = configList[index] 206 | config.handle(req, res) 207 | res.completed() 208 | } 209 | routeList.append(route) 210 | } 211 | 212 | let routes = Routes.init(routeList) 213 | _pjango_runtime_server.documentRoot = PJANGO_STATIC_URL 214 | _pjango_runtime_server.serverPort = PJANGO_SERVER_PORT 215 | _pjango_runtime_server.addRoutes(routes) 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /Source/Pjango/DB/PCFileDBDataBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PCFileDBDataBase.swift 3 | // Pjango-Dev 4 | // 5 | // Created by 郑宇琦 on 2017/7/2. 6 | // 7 | // 8 | 9 | import Foundation 10 | import PerfectLib 11 | import SwiftyJSON 12 | 13 | fileprivate let _pjango_filedb_init_content = "_pjango_empty_table" 14 | 15 | open class PCFileDBDataBase: PCDataBase { 16 | 17 | open override var schema: String? { 18 | return (self.config as! PCFileDBConfig).schema 19 | } 20 | 21 | open var path: String { 22 | return (self.config as! PCFileDBConfig).path 23 | } 24 | 25 | public convenience init?(param: [String: Any]) { 26 | guard let config = PCFileDBConfig.init(param: param) else { 27 | _pjango_core_log.debug("Oops! Faied on create database config!") 28 | return nil 29 | } 30 | self.init(config: config) 31 | } 32 | 33 | open override func isSchemaExist(_ schema: String) -> Bool { 34 | let dir = Dir.init(PCFileDBUtility.dirPathForSchema(path: path, schema: schema)) 35 | return dir.exists 36 | } 37 | 38 | open override func isTableExist(_ table: String) -> Bool { 39 | let file = File.init(PCFileDBUtility.filePathForTable(path: path, schema: schema!, table: table)) 40 | return file.exists 41 | } 42 | 43 | open override func createSchema() { 44 | _pjango_filedb_doWithLocked { 45 | let dir = Dir.init(PCFileDBUtility.dirPathForSchema(path: path, schema: schema!)) 46 | do { 47 | if dir.exists { 48 | try dir.delete() 49 | } 50 | try dir.create() 51 | } catch { 52 | _pjango_core_log.error("Failed on creating schema.") 53 | } 54 | } 55 | } 56 | 57 | open override func dropSchema() { 58 | _pjango_filedb_doWithLocked { 59 | let dir = Dir.init(PCFileDBUtility.dirPathForSchema(path: path, schema: schema!)) 60 | do { 61 | try dir.delete() 62 | } catch { 63 | _pjango_core_log.error("Failed on droping schema.") 64 | } 65 | } 66 | } 67 | 68 | open override func createTable(model: PCMetaModel) { 69 | _pjango_filedb_doWithLocked { 70 | let file = File.init(PCFileDBUtility.filePathForTable(path: path, schema: schema!, model: model)) 71 | if file.exists { 72 | file.delete() 73 | } 74 | do { 75 | try file.open(.readWrite) 76 | try file.write(string: _pjango_filedb_init_content) 77 | defer { 78 | file.close() 79 | } 80 | } catch { 81 | _pjango_core_log.error("Failed on creating table.") 82 | } 83 | } 84 | } 85 | 86 | open override func dropTable(model: PCMetaModel) { 87 | dropTable(model.tableName) 88 | } 89 | 90 | open override func dropTable(_ table: String) { 91 | _pjango_filedb_doWithLocked { 92 | let file = File.init(PCFileDBUtility.filePathForTable(path: path, schema: schema!, table: table)) 93 | if file.exists { 94 | file.delete() 95 | } 96 | } 97 | } 98 | 99 | open override func selectTable(model: PCMetaModel, ext: String? = nil) -> [PCDataBaseRecord]? { 100 | return selectTable(table: model.tableName, ext: ext) 101 | } 102 | 103 | open override func selectTable(table: String, ext: String? = nil) -> [PCDataBaseRecord]? { 104 | return _pjango_filedb_doWithLocked { 105 | let file = File.init(PCFileDBUtility.filePathForTable(path: path, schema: schema!, table: table)) 106 | guard file.exists else { 107 | _pjango_core_log.error("Failed on selecting table.") 108 | return nil 109 | } 110 | guard let list = _pjango_filedb_build_list_from_file(file) else { 111 | _pjango_core_log.error("Failed on selecting table.") 112 | return nil 113 | } 114 | return list 115 | } 116 | } 117 | 118 | 119 | @discardableResult 120 | open override func insertModel(_ model: PCModel) -> Bool { 121 | var record = model._pjango_core_model_fields.compactMap { (field) -> String? in 122 | switch field.type { 123 | case .string: return field.strValue 124 | case .int: return "\(field.intValue)" 125 | case .text: return field.strValue 126 | case .unknow: return nil 127 | } 128 | } 129 | guard record.count == model._pjango_core_model_fields.count else { 130 | return false 131 | } 132 | let file = File.init(PCFileDBUtility.filePathForTable(path: path, schema: schema!, model: model)) 133 | guard var records = _pjango_filedb_build_list_from_file(file) else { 134 | return false 135 | } 136 | record.insert("\(Int64(Date.init().timeIntervalSince1970 * 10000))", at: 0) 137 | records.append(record) 138 | guard _pjango_filedb_save_list_to_file(records, file: file) else { 139 | _pjango_core_log.error("Failed on inserting model.") 140 | return false 141 | } 142 | return true 143 | } 144 | 145 | @discardableResult 146 | open override func updateModel(_ model: PCModel) -> Bool { 147 | guard let id = model._pjango_core_model_id else { 148 | return false 149 | } 150 | let updateStr = model._pjango_core_model_fields_key.compactMap { (key) -> String? in 151 | guard let type = model._pjango_core_model_fields_type[key], let value = model._pjango_core_model_get_filed_data(key: key) else { 152 | return nil 153 | } 154 | switch type { 155 | case .string: 156 | guard let strValue = value as? String else { 157 | return nil 158 | } 159 | return strValue 160 | case .int: 161 | guard let intValue = value as? Int else { 162 | return nil 163 | } 164 | return "\(intValue)" 165 | case .text: 166 | guard let textValue = value as? String else { 167 | return nil 168 | } 169 | return textValue 170 | case .unknow: return nil 171 | } 172 | } 173 | guard updateStr.count == model._pjango_core_model_fields_key.count else { 174 | return false 175 | } 176 | let file = File.init(PCFileDBUtility.filePathForTable(path: path, schema: schema!, model: model)) 177 | guard var records = _pjango_filedb_build_list_from_file(file) else { 178 | return false 179 | } 180 | for i in 0..(_ foo: ()->T) -> T{ 198 | _pjango_core_database_lock.lock() 199 | defer { 200 | _pjango_core_database_lock.unlock() 201 | } 202 | return foo() 203 | } 204 | 205 | internal func _pjango_filedb_build_list_from_file(_ file: File) -> [PCDataBaseRecord]? { 206 | do { 207 | try file.open(.readWrite) 208 | defer { 209 | file.close() 210 | } 211 | let str = try file.readString() 212 | let json: JSON 213 | if str == _pjango_filedb_init_content { 214 | json = JSON.init([ 215 | "objs": [[String]]() 216 | ]) 217 | } else { 218 | json = JSON.init(parseJSON: str) 219 | } 220 | guard let jsonArray = json["objs"].array else { 221 | return nil 222 | } 223 | var result = [PCDataBaseRecord]() 224 | for jsonRecord in jsonArray { 225 | var record = PCDataBaseRecord() 226 | guard let jsonFieldList = jsonRecord.array else { 227 | continue 228 | } 229 | for jsonField in jsonFieldList { 230 | guard let value = jsonField.string else { 231 | break 232 | } 233 | record.append(value) 234 | } 235 | result.append(record) 236 | } 237 | return result 238 | } catch { 239 | return nil 240 | } 241 | } 242 | 243 | internal func _pjango_filedb_save_list_to_file(_ list: [PCDataBaseRecord], file: File) -> Bool { 244 | let json = JSON.init([ 245 | "objs": list 246 | ]) 247 | let str = json.description 248 | do { 249 | file.delete() 250 | try file.open(.readWrite) 251 | defer { 252 | file.close() 253 | } 254 | try file.write(string: str) 255 | return true 256 | } catch { 257 | return false 258 | } 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------