pbind 0.1.0 → 0.2.0

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.
@@ -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