pbind 0.1.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b1e4fc51921052509464b90b73e739278ea397f5
4
+ data.tar.gz: 870f80bf09baa2fd496be63832384670021cdb6f
5
+ SHA512:
6
+ metadata.gz: c6def51b03be6d197676e9f95e8e23f85c35eb70af527eb217c77a53500103cf80f922978ad4693d64823b732d57d0f89068e7cdbdd8f3c063d9df991e057c84
7
+ data.tar.gz: 90b2e4bc7751110ccedad5bcf935cf01e59769984d9d87d5741c02831729f626130a4cf7f951f60571a54b0e92268030e99387775c96531eb2eaab6b61e597cd
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+
6
+ require 'pbind'
7
+
8
+ ENV['PBIND_SOURCE'] = File.expand_path('../../source', __FILE__)
9
+
10
+ if profile_filename = ENV['PROFILE']
11
+ require 'ruby-prof'
12
+ reporter =
13
+ case (profile_extname = File.extname(profile_filename))
14
+ when '.txt'
15
+ RubyProf::FlatPrinterWithLineNumbers
16
+ when '.html'
17
+ RubyProf::GraphHtmlPrinter
18
+ when '.callgrind'
19
+ RubyProf::CallTreePrinter
20
+ else
21
+ raise "Unknown profiler format indicated by extension: #{profile_extname}"
22
+ end
23
+ File.open(profile_filename, 'w') do |io|
24
+ reporter.new(RubyProf.profile { Pbind::Command.run(ARGV) }).print(io)
25
+ end
26
+ else
27
+ Pbind::Command.run(ARGV)
28
+ end
@@ -0,0 +1,93 @@
1
+ require 'rubygems'
2
+ require 'xcodeproj'
3
+
4
+ module Pbind
5
+ require_relative 'pbind/command'
6
+ end
7
+
8
+ # class Pbind
9
+
10
+ # install_dir = 'PBPlayground'
11
+
12
+ # # project.targets.each do |target|
13
+ # # puts target.name
14
+ # # end
15
+
16
+ # # target = project.targets.first
17
+ # # files = target.source_build_phase.files.to_a.map do |pbx_build_file|
18
+ # # pbx_build_file.file_ref.real_path.to_s
19
+
20
+ # # end.select do |path|
21
+ # # path.end_with?(".m", ".mm", ".swift")
22
+
23
+ # # end.select do |path|
24
+ # # File.exists?(path)
25
+ # # end
26
+
27
+ # # puts files
28
+
29
+ # define_method :downloadSource do
30
+ # if File.exists?(install_dir)
31
+ # return true
32
+ # end
33
+
34
+ # `curl -OL https://github.com/wequick/Pbind/releases/download/0.6.0/PBPlayground.zip && tar -xvf PBPlayground.zip && rm PBPlayground.zip`
35
+ # return File.exists?(install_dir)
36
+ # end
37
+
38
+ # define_method :addReferences do |project_path|
39
+ # project = Xcodeproj::Project.open(project_path)
40
+ # target = project.targets.first
41
+
42
+ # group = project.main_group.find_subpath(install_dir, true)
43
+ # group.set_source_tree('SOURCE_ROOT')
44
+ # group.clear
45
+
46
+ # file_refs = Array.new
47
+ # Dir.foreach(install_dir) do |file|
48
+ # if !File.directory?(file)
49
+ # file_refs << group.new_reference(File.join(install_dir, file))
50
+ # end
51
+ # end
52
+
53
+ # target.add_file_references(file_refs)
54
+ # project.save
55
+ # end
56
+
57
+ # def logSuccess
58
+ # puts "[ \e[32mOK\e[0m ]"
59
+ # end
60
+
61
+ # def logFailed
62
+ # puts "[\e[31mFAILED\e[0m]"
63
+ # end
64
+
65
+ # define_method :install do
66
+ # project_paths = Dir.glob("*.xcodeproj")
67
+ # if project_paths.empty?
68
+ # puts "Failed to find any xcodeproj!"
69
+ # return
70
+ # end
71
+
72
+ # project_path = project_paths[0]
73
+ # print '%-64s' % 'Downloading PBPlayground source...'
74
+ # ret = downloadSource
75
+ # if !ret
76
+ # logFailed
77
+ # return
78
+ # end
79
+ # logSuccess
80
+
81
+ # print '%-64s' % 'Add PBPlayground reference to project...'
82
+ # addReferences project_path
83
+ # logSuccess
84
+ # end
85
+
86
+ # def self.hi
87
+ # puts "Hello world!"
88
+ # end
89
+
90
+ # def self.exec argv
91
+ # puts "Hello #{argv[0]}"
92
+ # end
93
+ # end
@@ -0,0 +1,35 @@
1
+ require 'colored'
2
+ require 'claide'
3
+
4
+ module Pbind
5
+ class Command < CLAide::Command
6
+
7
+ require_relative 'command/watch'
8
+
9
+ self.abstract_command = true
10
+ self.command = 'pbind'
11
+ self.version = version
12
+ self.description = 'Pbind, the Pbind xcode project helper.'
13
+ self.plugin_prefixes = %w(claide pbind)
14
+
15
+ def self.report_error(exception)
16
+ case exception
17
+ when Interrupt
18
+ puts ''
19
+ puts '[!] Cancelled'.red
20
+ # Config.instance.verbose? ? raise : exit(1)
21
+ when SystemExit
22
+ raise
23
+ else
24
+ # if ENV['PBIND_ENV'] != 'development'
25
+ # puts UI::ErrorReport.report(exception)
26
+ # UI::ErrorReport.search_for_exceptions(exception)
27
+ # exit 1
28
+ # else
29
+ raise exception
30
+ # end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,165 @@
1
+ require 'xcodeproj'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+
5
+ module Pbind
6
+ class Command
7
+ class Watch < Command
8
+ self.summary = 'Enable Pbind can watch sources and apply instant changes at runtime.'
9
+ self.description = <<-DESC
10
+ 1. Download `PBPlayground` sources from GitHub, create a new group named `PBPlayground` and add it to the current project.
11
+
12
+ 2. Modify the project `Info.plist` and set `PBResourcesPath` to `$(SRCROOT)/[project_name]`, `PBLocalhost` to `$(SRCROOT)/PBLocalhost`.
13
+
14
+ 3. At runtime: `[PBPlayground load]` observe the application lifecycle and watch the above paths to apply instant changes.
15
+ DESC
16
+
17
+ self.arguments = [
18
+ CLAide::Argument.new('XCODEPROJ', true),
19
+ ]
20
+
21
+ def initialize(argv)
22
+ @project_path = argv.shift_argument
23
+ end
24
+
25
+ def validate!
26
+ # super
27
+ help! 'A path for the xcodeproj is required.' unless @project_path
28
+ @project_path = @project_path.chomp('/')
29
+ help! 'The xcodeproj path should ends with ".xcodeproj"' unless @project_path.end_with?('.xcodeproj')
30
+ help! "The xcodeproj path '#{@project_path}' is not exists." unless File.exists?(@project_path)
31
+ end
32
+
33
+ def run
34
+ @src_name = 'PBPlayground'
35
+ @api_name = 'PBLocalhost'
36
+ @src_key = 'PBResourcesPath'
37
+ @project_root = File.dirname(@project_path)
38
+ @src_install_dir = File.absolute_path(File.join(@project_root, @src_name))
39
+ @api_install_dir = File.absolute_path(File.join(@project_root, @api_name))
40
+ @project = Xcodeproj::Project.open(@project_path)
41
+ @changed = false
42
+
43
+ install_sources
44
+ add_plist_entries
45
+ add_group_references
46
+
47
+ if !@changed
48
+ puts 'All are UP-TO-DATE.'
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ #----------------------------------------#
55
+
56
+ # !@group Private helpers
57
+
58
+ # Install the `PBPlayground`, `PBLocalhost` sources
59
+ #
60
+ # @return [void]
61
+ #
62
+ def install_sources
63
+ if File.exists?(@src_install_dir)
64
+ return
65
+ end
66
+
67
+ source_dir = ENV['PBIND_SOURCE']
68
+ UI.section("Copying `#{source_dir}` into `#{@project_root}`.") do
69
+ FileUtils.cp_r File.join(source_dir, @src_name), @project_root
70
+ FileUtils.cp_r File.join(source_dir, @api_name), @project_root
71
+ @changed = true
72
+ end
73
+ end
74
+
75
+ # Add the source path to `Info.plist`
76
+ #
77
+ # @return [Bool] something changed
78
+ #
79
+ def add_plist_entries
80
+ project = @project
81
+ target = project.targets.first
82
+
83
+ source_root = File.join('$(SRCROOT)', target.name)
84
+ api_root = File.join('$(SRCROOT)', @api_name)
85
+
86
+ debug_cfg = target.build_configurations.detect { |e| e.name == 'Debug' }
87
+ info_plist = debug_cfg.build_settings['INFOPLIST_FILE']
88
+
89
+ info_plist_path = File.join(@project_root, info_plist)
90
+ if !File.exists?(info_plist_path)
91
+ puts "Failed to find `#{info_plist_path}`".red
92
+ return
93
+ end
94
+
95
+ changed = false
96
+ plist = Xcodeproj::Plist.read_from_path(info_plist_path)
97
+ if (plist[@src_key] != source_root)
98
+ plist[@src_key] = source_root
99
+ changed = true
100
+ end
101
+ if (plist[@api_name] != api_root)
102
+ plist[@api_name] = api_root
103
+ changed = true
104
+ end
105
+
106
+ if !changed
107
+ return
108
+ end
109
+
110
+ UI.section("Add plist entires to `#{@project_path}`.") do
111
+ Xcodeproj::Plist.write_to_path(plist, info_plist_path)
112
+ @changed = true
113
+ end
114
+ end
115
+
116
+ # Add `PBPlayground`, `PBLocalhost` group references to the project
117
+ #
118
+ # @return [Bool] something changed
119
+ #
120
+ def add_group_references
121
+ project = @project
122
+ target = project.targets.first
123
+ changed = false
124
+
125
+ # Add PBPlayground group
126
+ group = project.main_group.find_subpath(@src_name, true)
127
+ if group.empty?
128
+ group.set_source_tree('SOURCE_ROOT')
129
+ file_refs = Array.new
130
+ Dir.foreach(@src_install_dir) do |file|
131
+ if !File.directory?(file)
132
+ file_refs << group.new_reference(File.join(@src_install_dir, file))
133
+ end
134
+ end
135
+ target.add_file_references(file_refs)
136
+ changed = true
137
+ end
138
+
139
+ # Add PBLocalhost group
140
+ group = project.main_group.find_subpath(@api_name, true)
141
+ if group.empty?
142
+ group.clear
143
+ file_refs = Array.new
144
+ Dir.foreach(@api_install_dir) do |file|
145
+ if !File.directory?(file)
146
+ file_refs << group.new_reference(File.join(@api_install_dir, file))
147
+ end
148
+ end
149
+ target.add_file_references(file_refs)
150
+ changed = true
151
+ end
152
+
153
+ # Save
154
+ if !changed
155
+ return
156
+ end
157
+
158
+ UI.section("Add group [`#{@src_name}`, `#{@api_name}`] to `#{@project_path}`.") do
159
+ project.save
160
+ @changed = true
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +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.
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,237 @@
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
@@ -0,0 +1,29 @@
1
+ //
2
+ // SGDirObserver.h
3
+ // DirectoryObserver
4
+ //
5
+ // Copyright (c) 2011 Simon Grätzer.
6
+ //
7
+
8
+ #include <targetconditionals.h>
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import <Foundation/Foundation.h>
13
+
14
+ @interface SGDirWatchdog : NSObject
15
+
16
+ @property (readonly, nonatomic) NSString *path;
17
+ @property (copy, nonatomic) void (^update)(void);
18
+
19
+ + (NSString *)documentsPath;
20
+ + (id)watchtdogOnDocumentsDir:(void (^)(void))update;
21
+
22
+ - (id)initWithPath:(NSString *)path update:(void (^)(void))update;
23
+
24
+ - (void)start;
25
+ - (void)stop;
26
+
27
+ @end
28
+
29
+ #endif
@@ -0,0 +1,163 @@
1
+ //
2
+ // SGDirObserver.m
3
+ // DirectoryObserver
4
+ //
5
+ // Copyright (c) 2011 Simon Grätzer.
6
+ //
7
+
8
+ #import "SGDirWatchdog.h"
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import <fcntl.h>
13
+ #import <unistd.h>
14
+ #import <sys/event.h>
15
+
16
+ @interface SGDirWatchdog ()
17
+ @property (nonatomic, readonly) CFFileDescriptorRef kqRef;
18
+ - (void)kqueueFired;
19
+ @end
20
+
21
+
22
+ static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) {
23
+ // Pick up the object passed in the "info" member of the CFFileDescriptorContext passed to CFFileDescriptorCreate
24
+ SGDirWatchdog* obj = (__bridge SGDirWatchdog*) info;
25
+
26
+ if ([obj isKindOfClass:[SGDirWatchdog class]] && // If we can call back to the proper sort of object ...
27
+ (kqRef == obj.kqRef) && // and the FD that issued the CB is the expected one ...
28
+ (callBackTypes == kCFFileDescriptorReadCallBack) ) // and we're processing the proper sort of CB ...
29
+ {
30
+ [obj kqueueFired]; // Invoke the instance's CB handler
31
+ }
32
+ }
33
+
34
+ @implementation SGDirWatchdog {
35
+ int _dirFD;
36
+ CFFileDescriptorRef _kqRef;
37
+ }
38
+
39
+ + (NSString *)documentsPath {
40
+ NSArray *documentsPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
41
+
42
+ return documentsPaths[0]; // Path to the application's "Documents" directory
43
+ }
44
+
45
+ + (id)watchtdogOnDocumentsDir:(void (^)(void))update; {
46
+ return [[SGDirWatchdog alloc]initWithPath:[self documentsPath] update:update];
47
+ }
48
+
49
+
50
+ - (id)initWithPath:(NSString *)path update:(void (^)(void))update; {
51
+ if ((self = [super init])) {
52
+ _path = path;
53
+ _update = [update copy];
54
+ }
55
+ return self;
56
+ }
57
+
58
+ - (void)dealloc {
59
+ [self stop];
60
+
61
+
62
+ }
63
+
64
+ #pragma mark -
65
+ #pragma mark Extension methods
66
+
67
+ - (void)kqueueFired {
68
+ // Pull the native FD around which the CFFileDescriptor was wrapped
69
+ int kq = CFFileDescriptorGetNativeDescriptor(_kqRef);
70
+ if (kq < 0) return;
71
+
72
+ // If we pull a single available event out of the queue, assume the directory was updated
73
+ struct kevent event;
74
+ struct timespec timeout = {0, 0};
75
+ if (kevent(kq, NULL, 0, &event, 1, &timeout) == 1 && _update) {
76
+ _update();
77
+ }
78
+
79
+ // (Re-)Enable a one-shot (the only kind) callback
80
+ CFFileDescriptorEnableCallBacks(_kqRef, kCFFileDescriptorReadCallBack);
81
+ }
82
+
83
+
84
+ - (void)start {
85
+ // One ping only
86
+ if (_kqRef != NULL) return;
87
+
88
+ // Fetch pathname of the directory to monitor
89
+ NSString* docPath = self.path;
90
+ if (!docPath) return;
91
+
92
+ // Open an event-only file descriptor associated with the directory
93
+ int dirFD = open([docPath fileSystemRepresentation], O_EVTONLY);
94
+ if (dirFD < 0) return;
95
+
96
+ // Create a new kernel event queue
97
+ int kq = kqueue();
98
+ if (kq < 0)
99
+ {
100
+ close(dirFD);
101
+ return;
102
+ }
103
+
104
+ // Set up a kevent to monitor
105
+ struct kevent eventToAdd; // Register an (ident, filter) pair with the kqueue
106
+ eventToAdd.ident = dirFD; // The object to watch (the directory FD)
107
+ eventToAdd.filter = EVFILT_VNODE; // Watch for certain events on the VNODE spec'd by ident
108
+ eventToAdd.flags = EV_ADD | EV_CLEAR; // Add a resetting kevent
109
+ eventToAdd.fflags = NOTE_WRITE; // The events to watch for on the VNODE spec'd by ident (writes)
110
+ eventToAdd.data = 0; // No filter-specific data
111
+ eventToAdd.udata = NULL; // No user data
112
+
113
+ // Add a kevent to monitor
114
+ if (kevent(kq, &eventToAdd, 1, NULL, 0, NULL)) {
115
+ close(kq);
116
+ close(dirFD);
117
+ return;
118
+ }
119
+
120
+ // Wrap a CFFileDescriptor around a native FD
121
+ CFFileDescriptorContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
122
+ _kqRef = CFFileDescriptorCreate(NULL, // Use the default allocator
123
+ kq, // Wrap the kqueue
124
+ true, // Close the CFFileDescriptor if kq is invalidated
125
+ KQCallback, // Fxn to call on activity
126
+ &context); // Supply a context to set the callback's "info" argument
127
+ if (_kqRef == NULL) {
128
+ close(kq);
129
+ close(dirFD);
130
+ return;
131
+ }
132
+
133
+ // Spin out a pluggable run loop source from the CFFileDescriptorRef
134
+ // Add it to the current run loop, then release it
135
+ CFRunLoopSourceRef rls = CFFileDescriptorCreateRunLoopSource(NULL, _kqRef, 0);
136
+ if (rls == NULL) {
137
+ CFRelease(_kqRef); _kqRef = NULL;
138
+ close(kq);
139
+ close(dirFD);
140
+ return;
141
+ }
142
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
143
+ CFRelease(rls);
144
+
145
+ // Store the directory FD for later closing
146
+ _dirFD = dirFD;
147
+
148
+ // Enable a one-shot (the only kind) callback
149
+ CFFileDescriptorEnableCallBacks(_kqRef, kCFFileDescriptorReadCallBack);
150
+ }
151
+
152
+ - (void)stop {
153
+ if (_kqRef) {
154
+ close(_dirFD);
155
+ CFFileDescriptorInvalidate(_kqRef);
156
+ CFRelease(_kqRef);
157
+ _kqRef = NULL;
158
+ }
159
+ }
160
+
161
+ @end
162
+
163
+ #endif
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pbind
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Galen Lin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xcodeproj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: claide
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.0.1
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.0.1
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.0'
53
+ description: A toolkit to create a xcode project with Pbind.
54
+ email: oolgloo.2012@gmail.com
55
+ executables:
56
+ - pbind
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - bin/pbind
61
+ - lib/pbind.rb
62
+ - lib/pbind/command.rb
63
+ - lib/pbind/command/watch.rb
64
+ - 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
+ homepage: http://rubygems.org/gems/pbind
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.6.6
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Pbind xcodeproj helper
93
+ test_files: []
94
+ has_rdoc: