pbind 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ //
2
+ // PBLiveLoader.h
3
+ // Pbind
4
+ //
5
+ // Created by Galen Lin on 2016/12/9.
6
+ //
7
+
8
+ #include <targetconditionals.h>
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import <Foundation/Foundation.h>
13
+
14
+ /**
15
+ The live loader for Pbind.
16
+
17
+ @discussion while the application start, it will watch the project directories
18
+ to detect changes of all the *.plist files and all the *.json files under PBLocalhost
19
+ directory, by what we can instantly reload the related views.
20
+ */
21
+ @interface PBLiveLoader : NSObject
22
+
23
+ @end
24
+
25
+ #endif
@@ -0,0 +1,203 @@
1
+ //
2
+ // PBLiveLoader.m
3
+ // Pbind
4
+ //
5
+ // Created by Galen Lin on 2016/12/9.
6
+ //
7
+
8
+ #import "PBLiveLoader.h"
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import "PBDirectoryWatcher.h"
13
+ #import <Pbind/Pbind.h>
14
+
15
+ @implementation PBLiveLoader
16
+
17
+ static NSString *const PLIST_SUFFIX = @".plist";
18
+ static NSString *const JSON_SUFFIX = @".json";
19
+ static NSString *const IGNORES_SUFFIX = @"ignore.h";
20
+
21
+ static NSArray<NSString *> *kIgnoreAPIs;
22
+ static PBDirectoryWatcher *kResWatcher;
23
+ static PBDirectoryWatcher *kAPIWatcher;
24
+
25
+ static BOOL HasSuffix(NSString *src, NSString *tail)
26
+ {
27
+ NSInteger loc = [src rangeOfString:tail].location;
28
+ if (loc == NSNotFound) {
29
+ return NO;
30
+ }
31
+
32
+ return loc == src.length - tail.length;
33
+ }
34
+
35
+ + (void)load {
36
+ [super load];
37
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:UIApplicationDidFinishLaunchingNotification object:nil];
38
+ }
39
+
40
+ + (void)applicationDidFinishLaunching:(id)note {
41
+ [self watchPlist];
42
+ [self watchAPI];
43
+ }
44
+
45
+ + (void)watchPlist {
46
+ NSString *resPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBResourcesPath"];
47
+ if (resPath == nil) {
48
+ NSLog(@"PBPlayground: Please define PBResourcesPath in Info.plist with value '$(SRCROOT)/[path-to-resources]'!");
49
+ return;
50
+ }
51
+
52
+ if (![[NSFileManager defaultManager] fileExistsAtPath:resPath]) {
53
+ NSLog(@"PBPlayground: PBResourcesPath is not exists! (%@)", resPath);
54
+ return;
55
+ }
56
+
57
+ kResWatcher = [[PBDirectoryWatcher alloc] init];
58
+ [kResWatcher watchDir:resPath handler:^(NSString *path, BOOL initial, PBDirEvent event) {
59
+ switch (event) {
60
+ case PBDirEventNewFile:
61
+ if (HasSuffix(path, PLIST_SUFFIX)) {
62
+ NSBundle *updatedBundle = [NSBundle bundleWithPath:[path stringByDeletingLastPathComponent]];
63
+ [Pbind addResourcesBundle:updatedBundle];
64
+ }
65
+ break;
66
+
67
+ case PBDirEventModifyFile:
68
+ if (HasSuffix(path, PLIST_SUFFIX)) {
69
+ [Pbind reloadViewsOnPlistUpdate:path];
70
+ }
71
+ break;
72
+
73
+ case PBDirEventDeleteFile:
74
+ if (HasSuffix(path, PLIST_SUFFIX)) {
75
+ [Pbind reloadViewsOnPlistUpdate:path];
76
+ }
77
+ break;
78
+
79
+ default:
80
+ break;
81
+ }
82
+ }];
83
+ }
84
+
85
+ + (void)watchAPI {
86
+ NSString *serverPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBLocalhost"];
87
+ if (serverPath == nil) {
88
+ NSLog(@"PBPlayground: Please define PBLocalhost in Info.plist with value '$(SRCROOT)/[path-to-api]'!");
89
+ return;
90
+ }
91
+
92
+ if (![[NSFileManager defaultManager] fileExistsAtPath:serverPath]) {
93
+ NSLog(@"PBPlayground: PBLocalhost is not exists! (%@)", serverPath);
94
+ return;
95
+ }
96
+
97
+ kAPIWatcher = [[PBDirectoryWatcher alloc] init];
98
+ [kAPIWatcher watchDir:serverPath handler:^(NSString *path, BOOL initial, PBDirEvent event) {
99
+ switch (event) {
100
+ case PBDirEventNewFile:
101
+ if (HasSuffix(path, IGNORES_SUFFIX)) {
102
+ kIgnoreAPIs = [self ignoreAPIsWithContentsOfFile:path];
103
+ }
104
+ break;
105
+
106
+ case PBDirEventModifyFile:
107
+ if (HasSuffix(path, JSON_SUFFIX)) {
108
+ [self reloadViewsOnJSONChange:path deleted:NO];
109
+ } else if (HasSuffix(path, IGNORES_SUFFIX)) {
110
+ [self reloadViewsOnIgnoresChange:path deleted:NO];
111
+ }
112
+ break;
113
+
114
+ case PBDirEventDeleteFile:
115
+ if (HasSuffix(path, JSON_SUFFIX)) {
116
+ [self reloadViewsOnJSONChange:path deleted:YES];
117
+ } else if (HasSuffix(path, IGNORES_SUFFIX)) {
118
+ [self reloadViewsOnIgnoresChange:path deleted:YES];
119
+ }
120
+ break;
121
+
122
+ default:
123
+ break;
124
+ }
125
+ }];
126
+
127
+ [PBClient registerDebugServer:^id(PBClient *client, PBRequest *request) {
128
+ NSString *action = request.action;
129
+ if ([action characterAtIndex:0] == '/') {
130
+ action = [action substringFromIndex:1]; // bypass '/'
131
+ }
132
+
133
+ if (kIgnoreAPIs != nil && [kIgnoreAPIs containsObject:action]) {
134
+ return nil;
135
+ }
136
+
137
+ NSString *jsonName = [NSString stringWithFormat:@"%@/%@.json", [[client class] description], action];
138
+ NSString *jsonPath = [serverPath stringByAppendingPathComponent:jsonName];
139
+ if (![[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
140
+ NSLog(@"PBPlayground: Missing '%@', ignores!", jsonName);
141
+ return nil;
142
+ }
143
+ NSData *jsonData = [NSData dataWithContentsOfFile:jsonPath];
144
+ NSError *error = nil;
145
+ id response = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
146
+ if (error != nil) {
147
+ NSLog(@"PBPlayground: Invalid '%@', ignores!", jsonName);
148
+ return nil;
149
+ }
150
+
151
+ return response;
152
+ }];
153
+ }
154
+
155
+ + (NSArray *)ignoreAPIsWithContentsOfFile:(NSString *)path {
156
+ NSString *content = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:path] encoding:NSUTF8StringEncoding];
157
+ return [[content componentsSeparatedByString:@"\n"] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT (SELF BEGINSWITH '//') AND NOT (SELF == '')"]];
158
+ }
159
+
160
+ + (void)reloadViewsOnJSONChange:(NSString *)path deleted:(BOOL)deleted {
161
+ NSArray *components = [path componentsSeparatedByString:@"/"];
162
+ NSString *name = [components lastObject];
163
+ components = [name componentsSeparatedByString:@"."];
164
+ name = [components firstObject];
165
+ [Pbind reloadViewsOnAPIUpdate:name];
166
+ }
167
+
168
+ + (void)reloadViewsOnIgnoresChange:(NSString *)path deleted:(BOOL)deleted {
169
+ BOOL clear = deleted;
170
+ NSArray *oldIgnores = kIgnoreAPIs;
171
+ NSArray *newIgnores = [self ignoreAPIsWithContentsOfFile:path];
172
+ if (newIgnores.count == 0) {
173
+ clear = YES;
174
+ }
175
+ if (clear) {
176
+ kIgnoreAPIs = nil;
177
+ for (NSString *action in oldIgnores) {
178
+ [Pbind reloadViewsOnAPIUpdate:action];
179
+ }
180
+ return;
181
+ }
182
+
183
+ NSArray *changedIgnores;
184
+
185
+ if (oldIgnores != nil) {
186
+ NSArray *deletedIgnores = [oldIgnores filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT SELF IN %@", newIgnores]];
187
+ NSArray *addedIgnores = [newIgnores filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT SELF IN %@", oldIgnores]];
188
+ changedIgnores = [deletedIgnores arrayByAddingObjectsFromArray:addedIgnores];
189
+ } else {
190
+ changedIgnores = newIgnores;
191
+ }
192
+
193
+ kIgnoreAPIs = newIgnores;
194
+ if (changedIgnores.count > 0) {
195
+ for (NSString *action in changedIgnores) {
196
+ [Pbind reloadViewsOnAPIUpdate:action];
197
+ }
198
+ }
199
+ }
200
+
201
+ @end
202
+
203
+ #endif
@@ -1,2 +1,2 @@
1
- // Add API actions at each line to prevent it from local testing.
2
- // Comment|uncomment(`[Cmd]+/`) to switch whether ignores it or not.
1
+ // Add API actions at each line to prevent it from local mocking.
2
+ // You can use [⌘+/] to switch whether ignores or not.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pbind
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Galen Lin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-23 00:00:00.000000000 Z
11
+ date: 2016-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xcodeproj
@@ -60,12 +60,13 @@ files:
60
60
  - bin/pbind
61
61
  - lib/pbind.rb
62
62
  - lib/pbind/command.rb
63
+ - lib/pbind/command/mock.rb
63
64
  - lib/pbind/command/watch.rb
65
+ - source/PBLiveLoader/PBDirectoryWatcher.h
66
+ - source/PBLiveLoader/PBDirectoryWatcher.m
67
+ - source/PBLiveLoader/PBLiveLoader.h
68
+ - source/PBLiveLoader/PBLiveLoader.m
64
69
  - source/PBLocalhost/ignore.h
65
- - source/PBPlayground/PBPlayground.h
66
- - source/PBPlayground/PBPlayground.m
67
- - source/PBPlayground/SGDirWatchdog.h
68
- - source/PBPlayground/SGDirWatchdog.m
69
70
  homepage: http://rubygems.org/gems/pbind
70
71
  licenses:
71
72
  - MIT
@@ -1,19 +0,0 @@
1
- //
2
- // PBPlayground.h
3
- // Pbind
4
- //
5
- // Created by Galen Lin on 16/9/22.
6
- // Copyright © 2016年 galenlin. All rights reserved.
7
- //
8
-
9
- #include <targetconditionals.h>
10
-
11
- #if (DEBUG && TARGET_IPHONE_SIMULATOR)
12
-
13
- #import <Foundation/Foundation.h>
14
-
15
- @interface PBPlayground : NSObject
16
-
17
- @end
18
-
19
- #endif
@@ -1,237 +0,0 @@
1
- //
2
- // PBPlayground.m
3
- // Pbind
4
- //
5
- // Created by Galen Lin on 16/9/22.
6
- // Copyright © 2016年 galenlin. All rights reserved.
7
- //
8
-
9
- #import "PBPlayground.h"
10
-
11
- #if (DEBUG && TARGET_IPHONE_SIMULATOR)
12
-
13
- #import "SGDirWatchdog.h"
14
- #import <Pbind/Pbind.h>
15
-
16
- #include <stdio.h>
17
- #include <string.h>
18
- #include <sys/types.h>
19
- #include <dirent.h>
20
- #include <sys/stat.h>
21
-
22
- typedef void(^file_handler)(const char *parent, const char *current, const char *name);
23
-
24
- int walk_dir(const char *path, int depth, file_handler handler) {
25
- DIR *d;
26
- struct dirent *file;
27
- struct stat sb;
28
- char subdir[256];
29
-
30
- if (!(d = opendir(path))) {
31
- NSLog(@"error opendir %s!", path);
32
- return -1;
33
- }
34
-
35
- while ((file = readdir(d)) != NULL) {
36
- const char *name = file->d_name;
37
- if (*name == '.') {
38
- continue;
39
- }
40
-
41
- sprintf(subdir, "%s/%s", path, name);
42
- if (stat(subdir, &sb) < 0) {
43
- continue;
44
- }
45
-
46
- if (S_ISDIR(sb.st_mode)) {
47
- walk_dir(subdir, depth + 1, handler);
48
- continue;
49
- }
50
-
51
- handler(path, subdir, name);
52
- }
53
-
54
- closedir(d);
55
-
56
- return 0;
57
- }
58
-
59
- UIViewController *topcontroller(UIViewController *controller)
60
- {
61
- UIViewController *presentedController = [controller presentedViewController];
62
- if (presentedController != nil) {
63
- return topcontroller(controller);
64
- }
65
-
66
- if ([controller isKindOfClass:[UINavigationController class]]) {
67
- return topcontroller([(id)controller topViewController]);
68
- }
69
-
70
- if ([controller isKindOfClass:[UITabBarController class]]) {
71
- return topcontroller([(id)controller selectedViewController]);
72
- }
73
-
74
- return controller;
75
- }
76
-
77
- @implementation PBPlayground
78
-
79
- static NSMutableDictionary<NSString *, SGDirWatchdog *> *kPlistWatchdogs;
80
- static NSMutableDictionary<NSString *, SGDirWatchdog *> *kJsonWatchdogs;
81
-
82
- static SGDirWatchdog *kIgnoreAPIWatchdog;
83
- static NSArray *kIgnoreAPIs;
84
- static NSString *kIgnoresFile;
85
-
86
- static dispatch_block_t onPlistUpdate = ^{
87
- UIViewController *controller = [[[UIApplication sharedApplication].delegate window] rootViewController];
88
- controller = topcontroller(controller);
89
- [controller.view pb_reloadPlist];
90
- };
91
-
92
- static dispatch_block_t onJsonUpdate = ^{
93
- UIViewController *controller = [[[UIApplication sharedApplication].delegate window] rootViewController];
94
- controller = topcontroller(controller);
95
- [controller.view pb_reloadClient];
96
- };
97
-
98
- static dispatch_block_t onIgnoresUpdate = ^{
99
- if (![[NSFileManager defaultManager] fileExistsAtPath:kIgnoresFile]) {
100
- return;
101
- }
102
-
103
- NSString *content = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:kIgnoresFile] encoding:NSUTF8StringEncoding];
104
- kIgnoreAPIs = [[content componentsSeparatedByString:@"\n"] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT (SELF BEGINSWITH '//')"]];
105
- onJsonUpdate();
106
- };
107
-
108
-
109
- + (void)load {
110
- [super load];
111
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:UIApplicationDidFinishLaunchingNotification object:nil];
112
- }
113
-
114
- + (void)applicationDidFinishLaunching:(id)note {
115
- [self watchPlist];
116
- [self watchAPI];
117
- }
118
-
119
- + (void)watchPlist {
120
- NSString *resPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBResourcesPath"];
121
- if (resPath == nil) {
122
- NSLog(@"PBPlayground: Please define PBResourcesPath in Info.plist with value '$(SRCROOT)/[path-to-resources]'!");
123
- return;
124
- }
125
-
126
- if (![[NSFileManager defaultManager] fileExistsAtPath:resPath]) {
127
- NSLog(@"PBPlayground: PBResourcesPath is not exists! (%@)", resPath);
128
- return;
129
- }
130
-
131
- NSMutableArray *resPaths = [[NSMutableArray alloc] init];
132
-
133
- walk_dir([resPath UTF8String], 0, ^(const char *parent, const char *current, const char *name) {
134
- size_t len = strlen(name);
135
- if (len < 7) return;
136
-
137
- char *p = (char *)name + len - 6;
138
- if (strcmp(p, ".plist") != 0) return;
139
-
140
- NSString *parentPath = [[NSString alloc] initWithUTF8String:parent];
141
- if (![resPaths containsObject:parentPath]) {
142
- [resPaths addObject:parentPath];
143
- }
144
- });
145
-
146
- if (resPaths.count == 0) {
147
- NSLog(@"PBPlayground: Could not found any *.plist!");
148
- return;
149
- }
150
-
151
- kPlistWatchdogs = [NSMutableDictionary dictionaryWithCapacity:resPaths.count];
152
-
153
- for (NSString *path in resPaths) {
154
- [Pbind addResourcesBundle:[NSBundle bundleWithPath:path]];
155
-
156
- SGDirWatchdog *watchdog = [[SGDirWatchdog alloc] initWithPath:path update:onPlistUpdate];
157
- [kPlistWatchdogs setValue:watchdog forKey:path];
158
- [watchdog start];
159
- }
160
- }
161
-
162
- + (void)watchAPI {
163
- NSString *serverPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBLocalhost"];
164
- if (serverPath == nil) {
165
- NSLog(@"PBPlayground: Please define PBLocalhost in Info.plist with value '$(SRCROOT)/[path-to-api]'!");
166
- return;
167
- }
168
-
169
- if (![[NSFileManager defaultManager] fileExistsAtPath:serverPath]) {
170
- NSLog(@"PBPlayground: PBLocalhost is not exists! (%@)", serverPath);
171
- return;
172
- }
173
-
174
- kIgnoreAPIWatchdog = [[SGDirWatchdog alloc] initWithPath:serverPath update:onIgnoresUpdate];
175
- kIgnoresFile = [serverPath stringByAppendingPathComponent:@"ignore.h"];
176
- onIgnoresUpdate();
177
- [kIgnoreAPIWatchdog start];
178
-
179
- NSMutableArray *jsonPaths = [[NSMutableArray alloc] init];
180
-
181
- walk_dir([serverPath UTF8String], 0, ^(const char *parent, const char *current, const char *name) {
182
- size_t len = strlen(name);
183
- if (len < 6) return;
184
-
185
- char *p = (char *)name + len - 5;
186
- if (strcmp(p, ".json") != 0) return;
187
-
188
- NSString *parentPath = [[NSString alloc] initWithUTF8String:parent];
189
- if (![jsonPaths containsObject:parentPath]) {
190
- [jsonPaths addObject:parentPath];
191
- }
192
- });
193
-
194
- if (jsonPaths.count == 0) {
195
- NSLog(@"PBPlayground: Could not found any *.json!");
196
- return;
197
- }
198
-
199
- kJsonWatchdogs = [NSMutableDictionary dictionaryWithCapacity:jsonPaths.count];
200
-
201
- for (NSString *path in jsonPaths) {
202
- SGDirWatchdog *watchdog = [[SGDirWatchdog alloc] initWithPath:path update:onJsonUpdate];
203
- [kPlistWatchdogs setValue:watchdog forKey:path];
204
- [watchdog start];
205
- }
206
-
207
- [PBClient registerDebugServer:^id(PBClient *client, PBRequest *request) {
208
- NSString *action = request.action;
209
- if ([action characterAtIndex:0] == '/') {
210
- action = [action substringFromIndex:1]; // bypass '/'
211
- }
212
-
213
- if (kIgnoreAPIs != nil && [kIgnoreAPIs containsObject:action]) {
214
- return nil;
215
- }
216
-
217
- NSString *jsonName = [NSString stringWithFormat:@"%@/%@.json", [[client class] description], action];
218
- NSString *jsonPath = [serverPath stringByAppendingPathComponent:jsonName];
219
- if (![[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
220
- NSLog(@"PBPlayground: Missing '%@', ignores!", jsonName);
221
- return nil;
222
- }
223
- NSData *jsonData = [NSData dataWithContentsOfFile:jsonPath];
224
- NSError *error = nil;
225
- id response = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
226
- if (error != nil) {
227
- NSLog(@"PBPlayground: Invalid '%@', ignores!", jsonName);
228
- return nil;
229
- }
230
-
231
- return response;
232
- }];
233
- }
234
-
235
- @end
236
-
237
- #endif