molt 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +56 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +210 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/molt +6 -0
- data/bin/setup +7 -0
- data/bundled/models/Entity+CoreData.swift.liquid +15 -0
- data/bundled/models/Entity.swift.liquid +7 -0
- data/bundled/models/Model.swift.liquid +20 -0
- data/bundled/partials/_header.liquid +5 -0
- data/bundled/swift_helpers/ErrorTypes/APIError.swift +28 -0
- data/bundled/swift_helpers/ErrorTypes/ErrorTypes.swift +12 -0
- data/bundled/swift_helpers/ErrorTypes/PersistenceError.swift +28 -0
- data/bundled/swift_helpers/ISODateTransform.swift +26 -0
- data/bundled/swift_helpers/Identifiable.swift +29 -0
- data/bundled/swift_helpers/Loadable.swift +37 -0
- data/bundled/swift_helpers/Networking/APIRouter.swift +54 -0
- data/bundled/swift_helpers/StoryboardExtensions.swift +49 -0
- data/bundled/template_sets/viper_detail/Presenter.swift.liquid +17 -0
- data/bundled/template_sets/viper_detail/Protocols.swift.liquid +23 -0
- data/bundled/template_sets/viper_detail/View.swift.liquid +25 -0
- data/bundled/template_sets/viper_detail/WireFrame.swift.liquid +21 -0
- data/bundled/template_sets/viper_table/DataManagers/LocalDataManager.swift.liquid +45 -0
- data/bundled/template_sets/viper_table/DataManagers/RemoteDataManager.swift.liquid +26 -0
- data/bundled/template_sets/viper_table/Interactor.swift.liquid +48 -0
- data/bundled/template_sets/viper_table/Presenter.swift.liquid +49 -0
- data/bundled/template_sets/viper_table/Protocols.swift.liquid +58 -0
- data/bundled/template_sets/viper_table/View.swift.liquid +60 -0
- data/bundled/template_sets/viper_table/WireFrame.swift.liquid +43 -0
- data/lib/generamba/string-colorize.rb +31 -0
- data/lib/molt/cli/create_module.rb +76 -0
- data/lib/molt/cli/main.rb +74 -0
- data/lib/molt/cli/setup.rb +23 -0
- data/lib/molt/cli/template_sets.rb +18 -0
- data/lib/molt/configuration.rb +43 -0
- data/lib/molt/template.rb +9 -0
- data/lib/molt/version.rb +3 -0
- data/lib/molt.rb +22 -0
- data/molt.gemspec +32 -0
- data/sample_configs/global_config.yml.erb +7 -0
- metadata +218 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
//
|
2
|
+
// Identifiable.swift
|
3
|
+
// YATodo
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 2/3/18.
|
6
|
+
// Copyright © 2018 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import UIKit
|
10
|
+
import CoreData
|
11
|
+
|
12
|
+
// swiftlint:disable identifier_name
|
13
|
+
protocol Identifiable {
|
14
|
+
static var id: String { get }
|
15
|
+
}
|
16
|
+
|
17
|
+
extension Identifiable {
|
18
|
+
|
19
|
+
// return an introspective string representation of the invoking class, which is
|
20
|
+
// generally a shorter alternative to using R.swift's R.nib.viewClass.identifier
|
21
|
+
static var id: String {
|
22
|
+
return String(describing: self)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
// swiftlint:enable identifier_name
|
26
|
+
|
27
|
+
extension UIViewController: Identifiable { }
|
28
|
+
extension UIView: Identifiable { }
|
29
|
+
extension NSManagedObject: Identifiable { }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
//
|
2
|
+
// Loadable.swift
|
3
|
+
// YATodo
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 2/3/18.
|
6
|
+
// Copyright © 2018 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import UIKit
|
10
|
+
import PKHUD
|
11
|
+
|
12
|
+
protocol Loadable: class {
|
13
|
+
func show(error: Error)
|
14
|
+
func showLoadingIndicator()
|
15
|
+
func hideLoadingIndicator()
|
16
|
+
}
|
17
|
+
|
18
|
+
extension UIViewController: Loadable {
|
19
|
+
|
20
|
+
func show(error: Error) {
|
21
|
+
switch error {
|
22
|
+
case let error as DisplayableError:
|
23
|
+
HUD.flash(.labeledError(title: error.title, subtitle: error.message), delay: 5.0)
|
24
|
+
default:
|
25
|
+
// TODO: error is not displayable so log specifics of error instead
|
26
|
+
HUD.flash(.labeledError(title: "Ooops", subtitle: "Something is not cooperating"), delay: 5.0)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
func showLoadingIndicator() {
|
31
|
+
HUD.show(.progress)
|
32
|
+
}
|
33
|
+
|
34
|
+
func hideLoadingIndicator() {
|
35
|
+
HUD.hide()
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
//
|
2
|
+
// Endpoints.swift
|
3
|
+
// CapoRegime
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 18 Jan 2017.
|
6
|
+
// Copyright © 2017 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import Alamofire
|
10
|
+
|
11
|
+
struct Config {
|
12
|
+
static let API = "http://caporegime.rb/v1"
|
13
|
+
}
|
14
|
+
|
15
|
+
enum APIRouter: URLRequestConvertible {
|
16
|
+
case friends
|
17
|
+
|
18
|
+
private var path: String {
|
19
|
+
switch self {
|
20
|
+
case .friends:
|
21
|
+
return "/friends"
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
private var method: HTTPMethod {
|
26
|
+
switch self {
|
27
|
+
case .friends:
|
28
|
+
return .get
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
private var parameters: Parameters? {
|
33
|
+
return nil
|
34
|
+
}
|
35
|
+
|
36
|
+
private var headers: [String: String] {
|
37
|
+
return [ "Content-Type": "application/json" ]
|
38
|
+
}
|
39
|
+
|
40
|
+
func asURLRequest() throws -> URLRequest {
|
41
|
+
let baseURL = try Config.API.asURL()
|
42
|
+
var urlRequest = URLRequest(url: baseURL.appendingPathComponent(path))
|
43
|
+
|
44
|
+
urlRequest.httpMethod = method.rawValue
|
45
|
+
|
46
|
+
for (header, value) in headers {
|
47
|
+
urlRequest.setValue(value, forHTTPHeaderField: header)
|
48
|
+
}
|
49
|
+
|
50
|
+
urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters)
|
51
|
+
|
52
|
+
return urlRequest
|
53
|
+
}
|
54
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
//
|
2
|
+
// StoryboardExtensions.swift
|
3
|
+
// YATodo
|
4
|
+
//
|
5
|
+
// Created by Marco Cabazal on 18 Jan 2017.
|
6
|
+
// Copyright © 2018 The Chill Mill, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import UIKit
|
10
|
+
|
11
|
+
enum Storyboard: String {
|
12
|
+
case main = "Main"
|
13
|
+
|
14
|
+
var storyboard: UIStoryboard {
|
15
|
+
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
|
16
|
+
}
|
17
|
+
|
18
|
+
fileprivate func viewController<T: UIViewController>(as: T.Type, identifier: String,
|
19
|
+
function: String = #function, line: Int = #line, file: String = #file) -> T {
|
20
|
+
|
21
|
+
guard let scene = storyboard.instantiateViewController(withIdentifier: identifier) as? T else {
|
22
|
+
fatalError("ViewController with identifier \(identifier), not found. Reference: \(self.rawValue) Storyboard.\nFile : \(file) \nLine Number : \(line) \nFunction : \(function)")
|
23
|
+
}
|
24
|
+
|
25
|
+
return scene
|
26
|
+
}
|
27
|
+
|
28
|
+
func initialViewController() -> UIViewController? {
|
29
|
+
return storyboard.instantiateInitialViewController()
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
extension UIViewController {
|
34
|
+
|
35
|
+
static func instantiate(from storyboard: Storyboard) -> Self {
|
36
|
+
return storyboard.viewController(as: self, identifier: self.id)
|
37
|
+
}
|
38
|
+
|
39
|
+
static func instantiate(from storyboard: Storyboard, identifier: String) -> Self {
|
40
|
+
return storyboard.viewController(as: self, identifier: identifier)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
extension UITableViewCell {
|
45
|
+
|
46
|
+
static var nib: UINib {
|
47
|
+
return UINib(nibName: self.id, bundle: Bundle.main)
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Interactor.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
final class {{ module_name }}Presenter {
|
5
|
+
|
6
|
+
weak var view: {{ module_name }}ViewProtocol?
|
7
|
+
var wireFrame: {{ module_name }}WireFrameProtocol?
|
8
|
+
var dataSource: {{ model }}?
|
9
|
+
}
|
10
|
+
|
11
|
+
// MARK: CALLED BY VIEW
|
12
|
+
extension {{ module_name }}Presenter: {{ module_name }}PresenterProtocol {
|
13
|
+
|
14
|
+
func viewDidLoad() {
|
15
|
+
view?.render(content: dataSource)
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Protocols.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
// MARK: Called by VIEW -> Implemented BY PRESENTER
|
7
|
+
protocol {{ module_name }}PresenterProtocol: class {
|
8
|
+
weak var view: {{ module_name }}ViewProtocol? { get set }
|
9
|
+
weak var wireFrame: {{ module_name }}WireFrameProtocol? { get set }
|
10
|
+
var dataSource: {{ model }}? { get set }
|
11
|
+
|
12
|
+
func viewDidLoad()
|
13
|
+
}
|
14
|
+
|
15
|
+
// MARK: PRESENTER -> VIEW
|
16
|
+
protocol {{ module_name }}ViewProtocol: Loadable {
|
17
|
+
func render(content: {{ model }}?)
|
18
|
+
}
|
19
|
+
|
20
|
+
// MARK: PRESENTER -> WIREFRAME
|
21
|
+
protocol {{ module_name }}WireFrameProtocol: class {
|
22
|
+
static func prepareModule(with object: {{ model }}) -> UIViewController
|
23
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}View.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
class {{ module_name }}View: UIViewController {
|
7
|
+
|
8
|
+
var presenter: {{ module_name }}PresenterProtocol?
|
9
|
+
|
10
|
+
@IBOutlet weak var titleLabel: UILabel!
|
11
|
+
|
12
|
+
override func viewDidLoad() {
|
13
|
+
super.viewDidLoad()
|
14
|
+
presenter?.viewDidLoad()
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
// MARK: CALLED BY PRESENTER
|
19
|
+
extension {{ module_name }}View: {{ module_name }}ViewProtocol {
|
20
|
+
|
21
|
+
func render(content: {{ model }}?) {
|
22
|
+
guard let content = content else { return }
|
23
|
+
titleLabel?.text = content.title
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}WireFrame.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
final class {{ module_name }}WireFrame: {{ module_name }}WireFrameProtocol {
|
7
|
+
|
8
|
+
static func prepareModule(with object: {{ model }}) -> UIViewController {
|
9
|
+
let view = {{ module_name }}View.instantiate(from: .main)
|
10
|
+
|
11
|
+
let presenter: {{ module_name }}PresenterProtocol = {{ module_name }}Presenter()
|
12
|
+
let wireFrame = {{ module_name }}WireFrame()
|
13
|
+
|
14
|
+
view.presenter = presenter
|
15
|
+
presenter.view = view
|
16
|
+
presenter.dataSource = object
|
17
|
+
presenter.wireFrame = wireFrame
|
18
|
+
|
19
|
+
return view
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}LocalDataManager.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import CoreData
|
5
|
+
import Hydra
|
6
|
+
|
7
|
+
// MARK: CALLED BY INTERACTOR
|
8
|
+
class {{ module_name }}LocalDataManager: {{ module_name }}LocalDataManagerProtocol {
|
9
|
+
|
10
|
+
func retrieveDataFromStorage() -> Promise<[{{ entity }}]> {
|
11
|
+
return Promise<[{{ entity }}]>(in: .main) { resolve, reject, _ in
|
12
|
+
guard let moc = CoreDataStore.moc else {
|
13
|
+
reject(PersistenceError.mocNotFound)
|
14
|
+
return
|
15
|
+
}
|
16
|
+
do {
|
17
|
+
let request: NSFetchRequest<{{ entity }}> = NSFetchRequest(entityName: {{ entity }}.id)
|
18
|
+
resolve(try moc.fetch(request))
|
19
|
+
} catch {
|
20
|
+
reject(PersistenceError.objectNotFound)
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
func saveEntity(id: Int, title: String) -> Promise<Void> {
|
26
|
+
return Promise<Void>(in: .main) { resolve, reject, _ in
|
27
|
+
guard let moc = CoreDataStore.moc else {
|
28
|
+
reject(PersistenceError.mocNotFound)
|
29
|
+
return
|
30
|
+
}
|
31
|
+
|
32
|
+
do {
|
33
|
+
if let entity = NSEntityDescription.entity(forEntityName: {{ entity }}.id, in: moc) {
|
34
|
+
let object = {{ entity }}(entity: entity, insertInto: moc)
|
35
|
+
object.id = Int32(id)
|
36
|
+
object.title = title
|
37
|
+
try moc.save()
|
38
|
+
resolve(())
|
39
|
+
}
|
40
|
+
} catch {
|
41
|
+
reject(PersistenceError.couldNotSaveObject)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}RemoteDataManager.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import Alamofire
|
5
|
+
import AlamofireObjectMapper
|
6
|
+
import Hydra
|
7
|
+
|
8
|
+
// MARK: CALLED BY INTERACTOR
|
9
|
+
class {{ module_name }}RemoteDataManager: {{ module_name }}RemoteDataManagerProtocol {
|
10
|
+
|
11
|
+
func retrieveDataFromAPI() -> Promise<[{{ model }}]> {
|
12
|
+
return Promise<[{{ model }}]>(in: .background) { resolve, reject, _ in
|
13
|
+
Alamofire.request(APIRouter.{{ model | downcase }})
|
14
|
+
.validate()
|
15
|
+
.responseArray(keyPath: "{{ model | downcase }}") { (response: DataResponse<[{{ model }}]>) in
|
16
|
+
|
17
|
+
switch response.result {
|
18
|
+
case .success(let data):
|
19
|
+
resolve(data)
|
20
|
+
case .failure:
|
21
|
+
reject(APIError.unknown)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Interactor.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
final class {{ module_name }}Interactor {
|
5
|
+
weak var presenter: {{ module_name }}InteractorOutputProtocol?
|
6
|
+
var localDataManager: {{ module_name }}LocalDataManagerProtocol?
|
7
|
+
var remoteDataManager: {{ module_name }}RemoteDataManagerProtocol?
|
8
|
+
}
|
9
|
+
|
10
|
+
// MARK: CALLED BY PRESENTER
|
11
|
+
extension {{ module_name }}Interactor: {{ module_name }}InteractorProtocol {
|
12
|
+
|
13
|
+
func retrieveData() {
|
14
|
+
localDataManager?.retrieveDataFromStorage().then({ [weak self] persistedData in
|
15
|
+
let data: [{{ model }}] = persistedData.map {
|
16
|
+
return {{ model }}(id: Int($0.id), title: $0.title ?? "", dueDate: $0.dueDate)
|
17
|
+
}
|
18
|
+
|
19
|
+
if !data.isEmpty {
|
20
|
+
self?.presenter?.present(data: data)
|
21
|
+
} else {
|
22
|
+
self?.retrieveDataFromAPI()
|
23
|
+
}
|
24
|
+
}).catch({ [weak self] error in
|
25
|
+
self?.presenter?.present(error: error)
|
26
|
+
})
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
extension {{ module_name }}Interactor {
|
31
|
+
|
32
|
+
fileprivate func retrieveDataFromAPI() {
|
33
|
+
remoteDataManager?.retrieveDataFromAPI().then({ [weak self] data in
|
34
|
+
self?.persist(data: data)
|
35
|
+
self?.presenter?.present(data: data)
|
36
|
+
}).catch({ [weak self] error in
|
37
|
+
self?.presenter?.present(error: error)
|
38
|
+
})
|
39
|
+
}
|
40
|
+
|
41
|
+
fileprivate func persist(data: [{{ model }}]) {
|
42
|
+
for item in data {
|
43
|
+
localDataManager?.saveEntity(id: item.id, title: item.title).catch({ [weak self] error in
|
44
|
+
self?.presenter?.present(error: error)
|
45
|
+
})
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Presenter.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
final class {{ module_name }}Presenter {
|
5
|
+
|
6
|
+
weak var view: {{ module_name }}ViewProtocol?
|
7
|
+
var wireFrame: {{ module_name }}WireFrameProtocol?
|
8
|
+
var interactor: {{ module_name }}InteractorProtocol?
|
9
|
+
var dataSource: [{{ model }}] = []
|
10
|
+
}
|
11
|
+
|
12
|
+
// MARK: CALLED BY VIEW
|
13
|
+
extension {{ module_name }}Presenter: {{ module_name }}PresenterProtocol {
|
14
|
+
|
15
|
+
func viewDidLoad() {
|
16
|
+
view?.showLoadingIndicator()
|
17
|
+
interactor?.retrieveData()
|
18
|
+
}
|
19
|
+
|
20
|
+
var numberOfSection: Int { return self.dataSource.count + 1 }
|
21
|
+
|
22
|
+
func numberOfRows(in section: Int) -> Int {
|
23
|
+
return dataSource.count
|
24
|
+
}
|
25
|
+
|
26
|
+
func content(at row: Int) -> {{ model }}? {
|
27
|
+
return dataSource[row]
|
28
|
+
}
|
29
|
+
|
30
|
+
func process(object: {{ model }}) {
|
31
|
+
guard let view = view else { return }
|
32
|
+
wireFrame?.navigate(to: object, from: view)
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
// MARK: CALLED BY INTERACTOR
|
37
|
+
extension {{ module_name }}Presenter: {{ module_name }}InteractorOutputProtocol {
|
38
|
+
|
39
|
+
func present(data: [{{ model }}]) {
|
40
|
+
self.dataSource = data
|
41
|
+
view?.hideLoadingIndicator()
|
42
|
+
view?.render()
|
43
|
+
}
|
44
|
+
|
45
|
+
func present(error: Error) {
|
46
|
+
view?.hideLoadingIndicator()
|
47
|
+
view?.show(error: error)
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}Protocols.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
import Hydra
|
6
|
+
|
7
|
+
// MARK: Called by VIEW -> Implemented BY PRESENTER
|
8
|
+
protocol {{ module_name }}PresenterProtocol: class {
|
9
|
+
var view: {{ module_name }}ViewProtocol? { get set }
|
10
|
+
var wireFrame: {{ module_name }}WireFrameProtocol? { get set }
|
11
|
+
var interactor: {{ module_name }}InteractorProtocol? { get set }
|
12
|
+
|
13
|
+
func viewDidLoad()
|
14
|
+
|
15
|
+
// datasource
|
16
|
+
var numberOfSection: Int { get }
|
17
|
+
func numberOfRows(in section: Int) -> Int
|
18
|
+
func content(at row: Int) -> <{{ model }}>?
|
19
|
+
|
20
|
+
func process(object: {{ model }})
|
21
|
+
}
|
22
|
+
|
23
|
+
// MARK: PRESENTER -> INTERACTOR
|
24
|
+
protocol {{ module_name }}InteractorProtocol: class {
|
25
|
+
var presenter: {{ module_name }}InteractorOutputProtocol? { get set }
|
26
|
+
var localDataManager: {{ module_name }}LocalDataManagerProtocol? { get set }
|
27
|
+
var remoteDataManager: {{ module_name }}RemoteDataManagerProtocol? { get set }
|
28
|
+
|
29
|
+
func retrieveData()
|
30
|
+
}
|
31
|
+
|
32
|
+
// MARK: INTERACTOR -> REMOTEDATAMANAGER
|
33
|
+
protocol {{ module_name }}RemoteDataManagerProtocol: class {
|
34
|
+
func retrieveDataFromAPI() -> Promise<[{{ model }}]>
|
35
|
+
}
|
36
|
+
|
37
|
+
// MARK: INTERACTOR -> LOCALDATAMANAGER
|
38
|
+
protocol {{ module_name }}LocalDataManagerProtocol: class {
|
39
|
+
func retrieveDataFromStorage() -> Promise<[{{ entity }}]>
|
40
|
+
func saveEntity(id: Int, title: String) -> Promise<Void>
|
41
|
+
}
|
42
|
+
|
43
|
+
// MARK: INTERACTOR -> PRESENTER
|
44
|
+
protocol {{ module_name }}InteractorOutputProtocol: class {
|
45
|
+
func present(data: [{{ model }}])
|
46
|
+
func present(error: Error)
|
47
|
+
}
|
48
|
+
|
49
|
+
// MARK: PRESENTER -> VIEW
|
50
|
+
protocol {{ module_name }}ViewProtocol: Loadable {
|
51
|
+
func render()
|
52
|
+
}
|
53
|
+
|
54
|
+
// MARK: PRESENTER -> WIREFRAME
|
55
|
+
protocol {{ module_name }}WireFrameProtocol: class {
|
56
|
+
static func prepareModule() -> UIViewController
|
57
|
+
func navigate(to object: {{ model }}, from view: {{ module_name }}ViewProtocol)
|
58
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}View.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
class {{ module_name }}View: UIViewController {
|
7
|
+
|
8
|
+
var presenter: {{ module_name }}PresenterProtocol?
|
9
|
+
|
10
|
+
@IBOutlet weak var tableView: UITableView!
|
11
|
+
|
12
|
+
override func viewDidLoad() {
|
13
|
+
super.viewDidLoad()
|
14
|
+
tableView.tableFooterView = UIView()
|
15
|
+
tableView.register({{ entity}}Cell.nib, forCellReuseIdentifier: {{ entity}}Cell.id)
|
16
|
+
presenter?.viewDidLoad()
|
17
|
+
}
|
18
|
+
|
19
|
+
override func viewWillAppear(_ animated: Bool) {
|
20
|
+
super.viewWillAppear(animated)
|
21
|
+
if let selectedRow = tableView.indexPathForSelectedRow {
|
22
|
+
tableView.deselectRow(at: selectedRow, animated: true)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
// MARK: CALLED BY PRESENTER
|
28
|
+
extension {{ module_name }}View: {{ module_name }}ViewProtocol {
|
29
|
+
|
30
|
+
func render() {
|
31
|
+
tableView.reloadData()
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
// MARK: UITABLEVIEW DATASOURCE
|
36
|
+
extension {{ module_name }}View: UITableViewDataSource {
|
37
|
+
|
38
|
+
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
39
|
+
return presenter?.numberOfRows(in: section) ?? 0
|
40
|
+
}
|
41
|
+
|
42
|
+
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
43
|
+
guard let content = presenter?.content(at: indexPath.row),
|
44
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: {{ entity}}Cell.id, for: indexPath) as? {{ entity}}Cell else {
|
45
|
+
return tableView.dequeueReusableCell(withIdentifier: {{ entity}}Cell.id, for: indexPath)
|
46
|
+
}
|
47
|
+
|
48
|
+
cell.configure(with: content)
|
49
|
+
return cell
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// MARK: UITABLEVIEW DELEGATE
|
54
|
+
extension {{ module_name }}View: UITableViewDelegate {
|
55
|
+
|
56
|
+
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
57
|
+
guard let content = presenter?.content(at: indexPath.row) else { return }
|
58
|
+
presenter?.process(object: content)
|
59
|
+
}
|
60
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
//
|
2
|
+
// {{ module_name }}WireFrame.swift
|
3
|
+
{% include 'header' %}
|
4
|
+
import UIKit
|
5
|
+
|
6
|
+
final class {{ module_name }}WireFrame {
|
7
|
+
|
8
|
+
static func prepareModule() -> UIViewController {
|
9
|
+
let navController = UINavigationController.instantiate(from: .main, identifier: "mainNavigationController")
|
10
|
+
|
11
|
+
guard let view = navController.childViewControllers.first as? {{ module_name }}View else {
|
12
|
+
return UIViewController()
|
13
|
+
}
|
14
|
+
|
15
|
+
let presenter: {{ module_name }}PresenterProtocol & {{ module_name }}InteractorOutputProtocol = {{ module_name }}Presenter()
|
16
|
+
let interactor: {{ module_name }}InteractorProtocol = {{ module_name }}Interactor()
|
17
|
+
let wireFrame: {{ module_name }}WireFrameProtocol = {{ module_name }}WireFrame()
|
18
|
+
let localDataManager: {{ module_name }}LocalDataManagerProtocol = {{ module_name }}LocalDataManager()
|
19
|
+
let remoteDataManager: {{ module_name }}RemoteDataManagerProtocol = {{ module_name }}RemoteDataManager()
|
20
|
+
|
21
|
+
// INTEGRATION
|
22
|
+
view.presenter = presenter
|
23
|
+
presenter.view = view
|
24
|
+
presenter.wireFrame = wireFrame
|
25
|
+
presenter.interactor = interactor
|
26
|
+
interactor.presenter = presenter
|
27
|
+
interactor.localDataManager = localDataManager
|
28
|
+
interactor.remoteDataManager = remoteDataManager
|
29
|
+
|
30
|
+
return navController
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
// MARK: NAVIGATION
|
35
|
+
extension {{ module_name }}WireFrame: {{ module_name }}WireFrameProtocol {
|
36
|
+
|
37
|
+
func navigate(to object: {{ model }}, from view: {{ module_name }}ViewProtocol) {
|
38
|
+
guard let sourceView = view as? UIViewController else { return }
|
39
|
+
|
40
|
+
let detailVC = {{ module_name }}DetailWireFrame.prepareModule(with: object)
|
41
|
+
sourceView.navigationController?.pushViewController(detailVC, animated: true)
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Borrowed from Egor Tolstoy, Beniamin Sarkisyan, Andrey Zarembo et al (Generamba/Rambler)
|
2
|
+
class String
|
3
|
+
|
4
|
+
def colorize(color_code)
|
5
|
+
"\e[#{color_code}m#{self}\e[0m"
|
6
|
+
end
|
7
|
+
|
8
|
+
def red
|
9
|
+
colorize(31)
|
10
|
+
end
|
11
|
+
|
12
|
+
def green
|
13
|
+
colorize(32)
|
14
|
+
end
|
15
|
+
|
16
|
+
def yellow
|
17
|
+
colorize(33)
|
18
|
+
end
|
19
|
+
|
20
|
+
def blue
|
21
|
+
colorize(34)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pink
|
25
|
+
colorize(35)
|
26
|
+
end
|
27
|
+
|
28
|
+
def light_blue
|
29
|
+
colorize(36)
|
30
|
+
end
|
31
|
+
end
|