pbind 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/pbind +28 -0
- data/lib/pbind.rb +93 -0
- data/lib/pbind/command.rb +35 -0
- data/lib/pbind/command/watch.rb +165 -0
- data/source/PBLocalhost/ignore.h +2 -0
- data/source/PBPlayground/PBPlayground.h +19 -0
- data/source/PBPlayground/PBPlayground.m +237 -0
- data/source/PBPlayground/SGDirWatchdog.h +29 -0
- data/source/PBPlayground/SGDirWatchdog.m +163 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -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
|
data/bin/pbind
ADDED
@@ -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
|
data/lib/pbind.rb
ADDED
@@ -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,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:
|