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