ruboto-core 0.0.3 → 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.
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +42 -3
- data/Rakefile +5 -0
- data/assets/Rakefile +72 -7
- data/assets/assets/scripts/ruboto.rb +124 -71
- data/assets/res/drawable-hdpi/icon.png +0 -0
- data/assets/res/drawable-ldpi/icon.png +0 -0
- data/assets/res/drawable-mdpi/icon.png +0 -0
- data/assets/samples/sample_activity.rb +3 -3
- data/assets/samples/sample_activity_test.rb +21 -0
- data/assets/samples/sample_broadcast_receiver.rb +1 -1
- data/assets/samples/sample_broadcast_receiver_test.rb +1 -0
- data/assets/samples/sample_service.rb +2 -1
- data/assets/samples/sample_service_test.rb +1 -0
- data/assets/src/InheritingActivity.java +6 -4
- data/assets/src/RubotoActivity.java +17 -5
- data/assets/src/org/ruboto/Script.java +30 -17
- data/assets/src/org/ruboto/test/ActivityTest.java +58 -0
- data/assets/src/org/ruboto/test/InstrumentationTestRunner.java +102 -0
- data/assets/test/assets/scripts/test_helper.rb +9 -0
- data/bin/ruboto +3 -920
- data/lib/java_class_gen/android_api.xml +1 -1
- data/lib/ruboto.rb +15 -0
- data/lib/ruboto/api.rb +21 -0
- data/lib/ruboto/commands/base.rb +373 -0
- data/lib/ruboto/core_ext/array.rb +6 -0
- data/lib/ruboto/core_ext/object.rb +10 -0
- data/lib/ruboto/util/asset_copier.rb +20 -0
- data/lib/ruboto/util/build.rb +183 -0
- data/lib/ruboto/util/code_formatting.rb +22 -0
- data/lib/ruboto/util/log_action.rb +20 -0
- data/lib/ruboto/util/main_fix.rb +13 -0
- data/lib/ruboto/util/objectspace.rb +8 -0
- data/lib/ruboto/util/scan_in_api.rb +40 -0
- data/lib/ruboto/util/update.rb +102 -0
- data/lib/ruboto/util/verify.rb +59 -0
- data/lib/ruboto/util/xml_element.rb +197 -0
- data/test/app_test.rb +44 -0
- data/test/test_helper.rb +4 -0
- metadata +41 -14
Binary file
|
Binary file
|
Binary file
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'ruboto
|
1
|
+
require 'ruboto'
|
2
2
|
|
3
3
|
ruboto_import_widgets :TextView, :LinearLayout, :Button
|
4
4
|
|
@@ -7,8 +7,8 @@ $activity.handle_create do |bundle|
|
|
7
7
|
|
8
8
|
setup_content do
|
9
9
|
linear_layout :orientation => LinearLayout::VERTICAL do
|
10
|
-
@text_view = text_view :text => "What hath Matz wrought?"
|
11
|
-
button :text => "M-x butterfly", :width => :wrap_content
|
10
|
+
@text_view = text_view :text => "What hath Matz wrought?", :id => 42
|
11
|
+
button :text => "M-x butterfly", :width => :wrap_content, :id => 43
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
activity Java::THE_PACKAGE.SampleActivity
|
2
|
+
|
3
|
+
setup do |activity|
|
4
|
+
start = Time.now
|
5
|
+
loop do
|
6
|
+
@text_view = activity.findViewById(42)
|
7
|
+
break if @text_view || (Time.now - start > 60)
|
8
|
+
sleep 1
|
9
|
+
end
|
10
|
+
assert @text_view
|
11
|
+
end
|
12
|
+
|
13
|
+
test('initial setup') do |activity|
|
14
|
+
assert_equal "What hath Matz wrought?", @text_view.text
|
15
|
+
end
|
16
|
+
|
17
|
+
test('button changes text') do |activity|
|
18
|
+
button = activity.findViewById(43)
|
19
|
+
button.performClick
|
20
|
+
assert_equal "What hath Matz wrought!", @text_view.text
|
21
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# TODO
|
@@ -0,0 +1 @@
|
|
1
|
+
# TODO
|
@@ -2,9 +2,11 @@ package THE_PACKAGE;
|
|
2
2
|
|
3
3
|
public class InheritingActivity extends org.ruboto.RubotoActivity {
|
4
4
|
public void onCreate(android.os.Bundle arg0) {
|
5
|
+
try {
|
6
|
+
setSplash(Class.forName("THE_PACKAGE.R$layout").getField("splash").getInt(null));
|
7
|
+
} catch (Exception e) {}
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
setScriptName("start.rb");
|
10
|
+
super.onCreate(arg0);
|
11
|
+
}
|
10
12
|
}
|
@@ -13,6 +13,7 @@ import android.os.Handler;
|
|
13
13
|
public class THE_RUBOTO_CLASS THE_ACTION THE_ANDROID_CLASS {
|
14
14
|
private Ruby __ruby__;
|
15
15
|
private String scriptName;
|
16
|
+
private int splash = 0;
|
16
17
|
private String remoteVariable = "";
|
17
18
|
public Object[] args;
|
18
19
|
private ProgressDialog loadingDialog;
|
@@ -40,6 +41,10 @@ THE_CONSTANTS
|
|
40
41
|
return this;
|
41
42
|
}
|
42
43
|
|
44
|
+
public void setSplash(int a_res){
|
45
|
+
splash = a_res;
|
46
|
+
}
|
47
|
+
|
43
48
|
public void setScriptName(String name){
|
44
49
|
scriptName = name;
|
45
50
|
}
|
@@ -57,10 +62,16 @@ THE_CONSTANTS
|
|
57
62
|
super.onCreate(arg0);
|
58
63
|
|
59
64
|
if (Script.getRuby() != null) {
|
65
|
+
backgroundCreate();
|
60
66
|
finishCreate();
|
61
67
|
} else {
|
68
|
+
if (splash == 0) {
|
69
|
+
loadingDialog = ProgressDialog.show(this, null, "Loading...", true, false);
|
70
|
+
} else {
|
71
|
+
requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
|
72
|
+
setContentView(splash);
|
73
|
+
}
|
62
74
|
loadingThread.start();
|
63
|
-
loadingDialog = ProgressDialog.show(this, null, "Loading...", true, false);
|
64
75
|
}
|
65
76
|
}
|
66
77
|
|
@@ -69,27 +80,28 @@ THE_CONSTANTS
|
|
69
80
|
private final Thread loadingThread = new Thread() {
|
70
81
|
public void run(){
|
71
82
|
Script.setUpJRuby(null);
|
83
|
+
backgroundCreate();
|
72
84
|
loadingHandler.post(loadingComplete);
|
73
85
|
}
|
74
86
|
};
|
75
87
|
|
76
88
|
private final Runnable loadingComplete = new Runnable(){
|
77
89
|
public void run(){
|
78
|
-
loadingDialog.dismiss();
|
90
|
+
if (loadingDialog != null) loadingDialog.dismiss();
|
79
91
|
finishCreate();
|
80
92
|
onStart();
|
81
93
|
onResume();
|
82
94
|
}
|
83
95
|
};
|
84
96
|
|
85
|
-
private void
|
97
|
+
private void backgroundCreate() {
|
86
98
|
Script.copyScriptsIfNeeded(getFilesDir().getAbsolutePath() + "/scripts", getAssets());
|
87
|
-
|
88
99
|
getRuby();
|
89
|
-
|
90
100
|
Script.defineGlobalVariable("$activity", this);
|
91
101
|
Script.defineGlobalVariable("$bundle", args[0]);
|
102
|
+
}
|
92
103
|
|
104
|
+
private void finishCreate() {
|
93
105
|
android.os.Bundle configBundle = getIntent().getBundleExtra("RubotoActivity Config");
|
94
106
|
|
95
107
|
if (configBundle != null) {
|
@@ -21,6 +21,7 @@ import org.jruby.RubyInstanceConfig;
|
|
21
21
|
import org.jruby.exceptions.RaiseException;
|
22
22
|
import org.jruby.javasupport.JavaUtil;
|
23
23
|
import org.jruby.parser.EvalStaticScope;
|
24
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
24
25
|
import org.jruby.runtime.DynamicScope;
|
25
26
|
import org.jruby.runtime.ThreadContext;
|
26
27
|
import org.jruby.runtime.scope.ManyVarsDynamicScope;
|
@@ -62,6 +63,7 @@ public class Script {
|
|
62
63
|
|
63
64
|
public static synchronized Ruby setUpJRuby(PrintStream out) {
|
64
65
|
if (ruby == null) {
|
66
|
+
System.setProperty("jruby.interfaces.useProxy", "true");
|
65
67
|
RubyInstanceConfig config = new RubyInstanceConfig();
|
66
68
|
config.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
|
67
69
|
|
@@ -89,13 +91,17 @@ public class Script {
|
|
89
91
|
public static String execute(String code) {
|
90
92
|
if (!initialized) return null;
|
91
93
|
try {
|
92
|
-
return
|
94
|
+
return exec(code).inspect().asJavaString();
|
93
95
|
} catch (RaiseException re) {
|
94
96
|
re.printStackTrace(ruby.getErrorStream());
|
95
97
|
return null;
|
96
98
|
}
|
97
99
|
}
|
98
100
|
|
101
|
+
public static IRubyObject exec(String code) throws RaiseException {
|
102
|
+
return ruby.evalScriptlet(code, scope);
|
103
|
+
}
|
104
|
+
|
99
105
|
public static void defineGlobalConstant(String name, Object object) {
|
100
106
|
ruby.defineGlobalConstant(name, JavaUtil.convertJavaToRuby(ruby, object));
|
101
107
|
}
|
@@ -146,30 +152,37 @@ public class Script {
|
|
146
152
|
for (String f : assets.list(from)) {
|
147
153
|
File dest = new File(to, f);
|
148
154
|
|
149
|
-
if (dest.exists())
|
155
|
+
if (dest.exists()) {
|
150
156
|
continue;
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
157
|
+
}
|
158
|
+
|
159
|
+
Log.d(TAG, "copying file from " + from + "/" + f + " to " + dest);
|
160
|
+
|
161
|
+
if (assets.list(from + "/" + f).length == 0) {
|
162
|
+
InputStream is = assets.open(from + "/" + f);
|
163
|
+
OutputStream fos = new BufferedOutputStream(new FileOutputStream(dest), 8192);
|
164
|
+
|
165
|
+
int n;
|
166
|
+
while ((n = is.read(buffer, 0, buffer.length)) != -1) {
|
167
|
+
fos.write(buffer, 0, n);
|
168
|
+
}
|
169
|
+
is.close();
|
170
|
+
fos.close();
|
171
|
+
} else {
|
172
|
+
dest.mkdir();
|
173
|
+
copyScripts(from + "/" + f, dest, assets);
|
174
|
+
}
|
163
175
|
}
|
164
176
|
} catch (IOException iox) {
|
165
|
-
Log.e(TAG, "error copying
|
177
|
+
Log.e(TAG, "error copying scripts", iox);
|
166
178
|
}
|
167
179
|
}
|
168
180
|
|
169
181
|
public static void copyScriptsIfNeeded(String to, AssetManager assets) {
|
170
182
|
/* the if makes sure we only do this the first time */
|
171
|
-
if (configDir(to))
|
183
|
+
if (configDir(to)) {
|
172
184
|
copyScripts("scripts", scriptsDirFile, assets);
|
185
|
+
}
|
173
186
|
}
|
174
187
|
|
175
188
|
|
@@ -208,7 +221,7 @@ public class Script {
|
|
208
221
|
}
|
209
222
|
|
210
223
|
public String getContents() throws IOException {
|
211
|
-
BufferedReader buffer = new BufferedReader(new FileReader(getFile()));
|
224
|
+
BufferedReader buffer = new BufferedReader(new FileReader(getFile()), 8192);
|
212
225
|
StringBuilder source = new StringBuilder();
|
213
226
|
while (true) {
|
214
227
|
String line = buffer.readLine();
|
@@ -0,0 +1,58 @@
|
|
1
|
+
package org.ruboto.test;
|
2
|
+
|
3
|
+
import android.app.Activity;
|
4
|
+
import android.app.ProgressDialog;
|
5
|
+
import android.test.ActivityInstrumentationTestCase2;
|
6
|
+
import android.util.Log;
|
7
|
+
import java.io.BufferedReader;
|
8
|
+
import java.io.FileReader;
|
9
|
+
import java.io.InputStream;
|
10
|
+
import java.io.InputStreamReader;
|
11
|
+
import java.io.IOException;
|
12
|
+
import junit.framework.AssertionFailedError;
|
13
|
+
import junit.framework.Test;
|
14
|
+
import junit.framework.TestResult;
|
15
|
+
import junit.framework.TestSuite;
|
16
|
+
import org.jruby.exceptions.RaiseException;
|
17
|
+
import org.jruby.javasupport.JavaUtil;
|
18
|
+
import org.jruby.javasupport.util.RuntimeHelpers;
|
19
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
20
|
+
import org.ruboto.Script;
|
21
|
+
|
22
|
+
public class ActivityTest extends ActivityInstrumentationTestCase2 {
|
23
|
+
private final IRubyObject setup;
|
24
|
+
private final IRubyObject block;
|
25
|
+
|
26
|
+
public ActivityTest(Class activityClass, IRubyObject setup, String name, IRubyObject block) {
|
27
|
+
super(activityClass);
|
28
|
+
setName(name);
|
29
|
+
this.setup = setup;
|
30
|
+
this.block = block;
|
31
|
+
Log.d(getClass().getName(), "Instance: " + name);
|
32
|
+
}
|
33
|
+
|
34
|
+
public void runTest() throws Exception {
|
35
|
+
Log.d(getClass().getName(), "runTest");
|
36
|
+
Log.d(getClass().getName(), "runTest: " + getName());
|
37
|
+
Script.setUpJRuby(null);
|
38
|
+
Log.d(getClass().getName(), "ruby ok");
|
39
|
+
try {
|
40
|
+
final Activity activity = getActivity();
|
41
|
+
Log.d(getClass().getName(), "activity ok");
|
42
|
+
runTestOnUiThread(new Runnable() {
|
43
|
+
public void run() {
|
44
|
+
Log.d(getClass().getName(), "calling setup");
|
45
|
+
RuntimeHelpers.invoke(setup.getRuntime().getCurrentContext(), setup, "call",
|
46
|
+
JavaUtil.convertJavaToRuby(Script.getRuby(), activity));
|
47
|
+
Log.d(getClass().getName(), "setup ok");
|
48
|
+
RuntimeHelpers.invoke(block.getRuntime().getCurrentContext(), block, "call",
|
49
|
+
JavaUtil.convertJavaToRuby(Script.getRuby(), activity));
|
50
|
+
}
|
51
|
+
});
|
52
|
+
} catch (Throwable t) {
|
53
|
+
throw new AssertionFailedError(t.getMessage());
|
54
|
+
}
|
55
|
+
Log.d(getClass().getName(), "runTest ok");
|
56
|
+
}
|
57
|
+
|
58
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
package org.ruboto.test;
|
2
|
+
|
3
|
+
import android.test.ActivityInstrumentationTestCase2;
|
4
|
+
import android.util.Log;
|
5
|
+
import java.io.BufferedReader;
|
6
|
+
import java.io.File;
|
7
|
+
import java.io.InputStream;
|
8
|
+
import java.io.InputStreamReader;
|
9
|
+
import java.io.IOException;
|
10
|
+
import java.io.UnsupportedEncodingException;
|
11
|
+
import java.net.JarURLConnection;
|
12
|
+
import java.net.URL;
|
13
|
+
import java.net.URLDecoder;
|
14
|
+
import java.util.ArrayList;
|
15
|
+
import java.util.Arrays;
|
16
|
+
import java.util.Collections;
|
17
|
+
import java.util.Enumeration;
|
18
|
+
import java.util.jar.JarFile;
|
19
|
+
import java.util.jar.JarEntry;
|
20
|
+
import java.util.List;
|
21
|
+
import junit.framework.Test;
|
22
|
+
import junit.framework.TestCase;
|
23
|
+
import junit.framework.TestSuite;
|
24
|
+
import org.jruby.exceptions.RaiseException;
|
25
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
26
|
+
import org.jruby.RubyClass;
|
27
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
28
|
+
import org.ruboto.Script;
|
29
|
+
|
30
|
+
public class InstrumentationTestRunner extends android.test.InstrumentationTestRunner {
|
31
|
+
private Class activityClass;
|
32
|
+
private IRubyObject setup;
|
33
|
+
private TestSuite suite;
|
34
|
+
|
35
|
+
public TestSuite getAllTests() {
|
36
|
+
Log.i(getClass().getName(), "Finding test scripts");
|
37
|
+
suite = new TestSuite("Sweet");
|
38
|
+
|
39
|
+
try {
|
40
|
+
Script.setUpJRuby(null);
|
41
|
+
Script.defineGlobalVariable("$runner", this);
|
42
|
+
Script.defineGlobalVariable("$test", this);
|
43
|
+
Script.defineGlobalVariable("$suite", suite);
|
44
|
+
loadScript("test_helper.rb");
|
45
|
+
String[] scripts = getContext().getResources().getAssets().list("scripts");
|
46
|
+
for (String f : scripts) {
|
47
|
+
if (f.equals("test_helper.rb")) continue;
|
48
|
+
Log.i(getClass().getName(), "Found script: " + f);
|
49
|
+
loadScript(f);
|
50
|
+
}
|
51
|
+
} catch (IOException e) {
|
52
|
+
addError(suite, e);
|
53
|
+
} catch (RaiseException e) {
|
54
|
+
addError(suite, e);
|
55
|
+
}
|
56
|
+
return suite;
|
57
|
+
}
|
58
|
+
|
59
|
+
public void activity(Class activityClass) {
|
60
|
+
this.activityClass = activityClass;
|
61
|
+
}
|
62
|
+
|
63
|
+
public void setup(IRubyObject block) {
|
64
|
+
this.setup = block;
|
65
|
+
}
|
66
|
+
|
67
|
+
public void test(String name, IRubyObject block) {
|
68
|
+
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.FROYO) {
|
69
|
+
name ="runTest";
|
70
|
+
}
|
71
|
+
Test test = new ActivityTest(activityClass, setup, name, block);
|
72
|
+
suite.addTest(test);
|
73
|
+
Log.d(getClass().getName(), "Made test instance: " + test);
|
74
|
+
}
|
75
|
+
|
76
|
+
private void addError(TestSuite suite, final Throwable t) {
|
77
|
+
Log.e(getClass().getName(), "Exception loading tests: " + t);
|
78
|
+
suite.addTest(new TestCase(t.getMessage()) {
|
79
|
+
public void runTest() throws java.lang.Throwable {
|
80
|
+
throw t;
|
81
|
+
}
|
82
|
+
});
|
83
|
+
}
|
84
|
+
|
85
|
+
private void loadScript(String f) throws IOException {
|
86
|
+
InputStream is = getContext().getResources().getAssets().open("scripts/" + f);
|
87
|
+
BufferedReader buffer = new BufferedReader(new InputStreamReader(is));
|
88
|
+
StringBuilder source = new StringBuilder();
|
89
|
+
while (true) {
|
90
|
+
String line = buffer.readLine();
|
91
|
+
if (line == null) break;
|
92
|
+
source.append(line).append("\n");
|
93
|
+
}
|
94
|
+
buffer.close();
|
95
|
+
|
96
|
+
Log.d(getClass().getName(), "Loading test script: " + f);
|
97
|
+
Script.defineGlobalVariable("$script_code", source.toString());
|
98
|
+
Script.exec("$test.instance_eval($script_code)");
|
99
|
+
Log.d(getClass().getName(), "Test script loaded");
|
100
|
+
}
|
101
|
+
|
102
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
def assert(value, message = "#{value.inspect} expected to be true")
|
4
|
+
raise message unless value
|
5
|
+
end
|
6
|
+
|
7
|
+
def assert_equal(expected, actual, message = "'#{expected}' expected, but got '#{actual}'")
|
8
|
+
raise message unless expected == actual
|
9
|
+
end
|
data/bin/ruboto
CHANGED
@@ -1,922 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require 'ruboto/commands/base'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
JRuby.objectspace = true
|
6
|
-
rescue LoadError
|
7
|
-
end
|
8
|
-
|
9
|
-
require 'rubygems'
|
10
|
-
require 'main'
|
11
|
-
require 'fileutils'
|
12
|
-
require 'rexml/document'
|
13
|
-
require 'jruby-jars'
|
14
|
-
|
15
|
-
# fix main (to an extent)
|
16
|
-
module Main
|
17
|
-
class Program
|
18
|
-
module InstanceMethods
|
19
|
-
def setup_finalizers
|
20
|
-
@finalizers ||= []
|
21
|
-
ObjectSpace.define_finalizer(self) do
|
22
|
-
while((f = @finalizers.pop)); f.call; end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
$gem_root = File.expand_path(__FILE__+ "/../..")
|
30
|
-
$assets = File.expand_path(__FILE__ + "/../../assets")
|
31
|
-
|
32
|
-
class AssetCopier
|
33
|
-
def initialize(from, to)
|
34
|
-
@from = from
|
35
|
-
@to = to
|
36
|
-
end
|
37
|
-
|
38
|
-
def copy(from, to='')
|
39
|
-
FileUtils.mkdir_p(File.join(@to, to))
|
40
|
-
FileUtils.cp_r(Dir[File.join(@from, from)], File.join(@to, to))
|
41
|
-
end
|
42
|
-
|
43
|
-
def copy_from_absolute_path(from, to='')
|
44
|
-
FileUtils.mkdir_p(File.join(@to, to))
|
45
|
-
FileUtils.cp_r(Dir[from], File.join(@to, to))
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
###########################################################################
|
50
|
-
#
|
51
|
-
# log_action: put text to stdout around the execution of a block
|
52
|
-
#
|
53
|
-
|
54
|
-
def log_action(initial_text, final_text="Done.", &block)
|
55
|
-
$stdout.sync = true
|
56
|
-
print initial_text, "..."
|
57
|
-
yield
|
58
|
-
puts final_text
|
59
|
-
end
|
60
|
-
|
61
|
-
###########################################################################
|
62
|
-
#
|
63
|
-
# XMLElement:
|
64
|
-
# Extends Hash to simulate a REXML::Element (but much faster) and provides
|
65
|
-
# information in the necessary format to generate Java code.
|
66
|
-
#
|
67
|
-
|
68
|
-
class XMLElement < Hash
|
69
|
-
def root
|
70
|
-
$api
|
71
|
-
end
|
72
|
-
|
73
|
-
def name
|
74
|
-
self["name"]
|
75
|
-
end
|
76
|
-
|
77
|
-
def attribute(name)
|
78
|
-
self["values"][name]
|
79
|
-
end
|
80
|
-
|
81
|
-
def add_element(name, attributes)
|
82
|
-
new_element = XMLElement.new
|
83
|
-
new_element["name"] = name
|
84
|
-
new_element["values"] = attributes
|
85
|
-
|
86
|
-
self[name] = [] unless self[name]
|
87
|
-
self[name] << new_element
|
88
|
-
|
89
|
-
new_element
|
90
|
-
end
|
91
|
-
|
92
|
-
def get_elements(name)
|
93
|
-
self[name] or []
|
94
|
-
end
|
95
|
-
|
96
|
-
def find_class_or_interface(klass, a_type)
|
97
|
-
abort "ERROR: Can't parse package from #{klass}" unless klass.match(/([a-z.]+)\.([A-Z][A-Za-z.]+)/)
|
98
|
-
package = self["package"].find{|i| i.attribute("name") == $1}
|
99
|
-
abort "ERROR: Can't find package #{$1}" unless package
|
100
|
-
if a_type == "either"
|
101
|
-
package["class"].find{|i| i.attribute("name") == $2} or package["interface"].find{|i| i.attribute("name") == $2}
|
102
|
-
else
|
103
|
-
package[a_type].find{|i| i.attribute("name") == $2}
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def find_class(package_and_class)
|
108
|
-
find_class_or_interface(package_and_class, "class")
|
109
|
-
end
|
110
|
-
|
111
|
-
def find_interface(package_and_interface)
|
112
|
-
find_class_or_interface(package_and_interface, "interface")
|
113
|
-
end
|
114
|
-
|
115
|
-
def all_methods(method_base="all", method_include="", method_exclude="", implements="")
|
116
|
-
# get all the methogs
|
117
|
-
all_methods = get_elements("method")
|
118
|
-
|
119
|
-
# establish the base set of methods
|
120
|
-
working_methods = case method_base
|
121
|
-
when "all"
|
122
|
-
all_methods
|
123
|
-
when "none"
|
124
|
-
[]
|
125
|
-
when "abstract"
|
126
|
-
all_methods.select{|i| i.attribute("abstract") == "true"}
|
127
|
-
when "on"
|
128
|
-
all_methods.select{|i| i.attribute("name").match(/^on[A-Z]/)}
|
129
|
-
end
|
130
|
-
|
131
|
-
# make sure to include requested methods
|
132
|
-
include_methods = method_include.split(",") if method_include.is_a?(String)
|
133
|
-
all_methods.each{|i| working_methods << i if include_methods.include?(i.attribute("name"))}
|
134
|
-
|
135
|
-
# make sure to exclude rejected methods
|
136
|
-
exclude_methods = method_exclude.split(",") if method_exclude.is_a?(String)
|
137
|
-
working_methods = working_methods.select{|i| not exclude_methods.include?(i.attribute("name"))}
|
138
|
-
|
139
|
-
# remove methods marked final
|
140
|
-
working_methods = working_methods.select{|i| (not i.attribute("final")) or i.attribute("final") == "false"}
|
141
|
-
|
142
|
-
# get additional methods from parent
|
143
|
-
if name =="class" and attribute("extends")
|
144
|
-
parent = root.find_class(attribute("extends"))
|
145
|
-
parent_methods = parent.all_methods(method_base, method_include, method_exclude)
|
146
|
-
working_signatures = working_methods.map(&:method_signature)
|
147
|
-
working_methods += parent_methods.select{|i| not working_signatures.include?(i.method_signature)}
|
148
|
-
end
|
149
|
-
|
150
|
-
# get additional methods from interfaces
|
151
|
-
if name =="class" and implements != ""
|
152
|
-
implements.split(",").each do |i|
|
153
|
-
interface = root.find_interface(i)
|
154
|
-
abort("Unkown interface: #{i}") unless interface
|
155
|
-
working_signatures = working_methods.map(&:method_signature)
|
156
|
-
working_methods += interface.all_methods.select{|j| not working_signatures.include?(j.method_signature)}
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
working_methods
|
161
|
-
end
|
162
|
-
|
163
|
-
def parameters
|
164
|
-
get_elements("parameter").map {|p| [p.attribute("name"), p.attribute("type").gsub("<", "<").gsub(">", ">")]}
|
165
|
-
end
|
166
|
-
|
167
|
-
def method_signature
|
168
|
-
"#{attribute("name")}(#{parameters.map{|i| i[1]}.join(',')})"
|
169
|
-
end
|
170
|
-
|
171
|
-
def constant_string
|
172
|
-
"CB_" + attribute("name").gsub(/[A-Z]/) {|i| "_#{i}"}.upcase.gsub(/^ON_/, "")
|
173
|
-
end
|
174
|
-
|
175
|
-
def super_string
|
176
|
-
if attribute("api_added") and
|
177
|
-
attribute("api_added").to_i > verify_min_sdk.to_i and
|
178
|
-
attribute("api_added").to_i <= verify_target_sdk.to_i
|
179
|
-
nil
|
180
|
-
elsif attribute("abstract") == "true"
|
181
|
-
nil
|
182
|
-
elsif name == "method"
|
183
|
-
"super.#{attribute("name")}(#{parameters.map{|i| i[0]}.join(", ")});"
|
184
|
-
elsif name == "constructor"
|
185
|
-
"super(#{parameters.map{|i| i[0]}.join(", ")});"
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def default_return
|
190
|
-
return nil unless attribute("return")
|
191
|
-
case attribute("return")
|
192
|
-
when "boolean": "return false;"
|
193
|
-
when "int": "return 0;"
|
194
|
-
when "void": nil
|
195
|
-
else "return null;"
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
def super_return
|
200
|
-
rv = super_string
|
201
|
-
return rv unless attribute("return")
|
202
|
-
rv ? "return #{rv}" : default_return
|
203
|
-
end
|
204
|
-
|
205
|
-
def ruby_call
|
206
|
-
rv = []
|
207
|
-
|
208
|
-
params = parameters
|
209
|
-
args = ""
|
210
|
-
if params.size > 3
|
211
|
-
args = ", args"
|
212
|
-
rv << "IRubyObject[] args = {" + params.map{|i| "JavaUtil.convertJavaToRuby(getRuby(), #{i[0]})"}.join(", ") + "};"
|
213
|
-
elsif params.size > 0
|
214
|
-
args = ", " + params.map{|i| "JavaUtil.convertJavaToRuby(getRuby(), #{i[0]})"}.join(", ")
|
215
|
-
end
|
216
|
-
|
217
|
-
return_cast = ""
|
218
|
-
if attribute("return") and (attribute("return").include?(".") or attribute("return") == "int[]")
|
219
|
-
return_cast = "return (#{attribute("return")})"
|
220
|
-
elsif attribute("return") and attribute("return") == "int"
|
221
|
-
return_cast = "return (Integer)"
|
222
|
-
elsif attribute("return") and attribute("return") != "void"
|
223
|
-
return_cast = "return (#{attribute("return").capitalize})"
|
224
|
-
end
|
225
|
-
return_cast = return_cast.gsub("<", "<").gsub(">", ">")
|
226
|
-
|
227
|
-
convert_return = ""
|
228
|
-
if attribute("return") and attribute("return") != "void"
|
229
|
-
convert_return = ".toJava(#{attribute("return")}.class)"
|
230
|
-
end
|
231
|
-
|
232
|
-
rv << "#{return_cast}RuntimeHelpers.invoke(getRuby().getCurrentContext(), callbackProcs[#{constant_string}], \"call\" #{args})#{convert_return};"
|
233
|
-
rv
|
234
|
-
end
|
235
|
-
|
236
|
-
def method_definition
|
237
|
-
method_call((attribute("return") ? attribute("return") : "void"), attribute("name"), parameters,
|
238
|
-
if_else("callbackProcs[#{constant_string}] != null",
|
239
|
-
[super_string] + try_catch(ruby_call, ["re.printStackTrace();", default_return]),
|
240
|
-
[super_return])).indent.join("\n")
|
241
|
-
end
|
242
|
-
|
243
|
-
def constructor_definition(class_name)
|
244
|
-
method_call("", class_name, parameters, [super_string]).indent.join("\n")
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
###########################################################################
|
249
|
-
#
|
250
|
-
# Methods for formatting code
|
251
|
-
#
|
252
|
-
|
253
|
-
def method_call(return_type=nil, method_name="", parameters=[], body_clause=[])
|
254
|
-
["public #{return_type || ""} #{method_name}(" + parameters.map{|i| "#{i[1]} #{i[0]}"}.join(", ") + ") {",
|
255
|
-
body_clause.indent, "}"]
|
256
|
-
end
|
257
|
-
|
258
|
-
def if_else(condition, if_clause, else_clause)
|
259
|
-
["if (#{condition}) {", if_clause.indent, else_clause.compact.empty? ? nil : "} else {", else_clause.indent, "}"]
|
260
|
-
end
|
261
|
-
|
262
|
-
def try_catch(try_clause, catch_clause)
|
263
|
-
["try {", try_clause.indent, "} catch (RaiseException re) {", catch_clause.indent, "}"]
|
264
|
-
end
|
265
|
-
|
266
|
-
class Array
|
267
|
-
def indent
|
268
|
-
flatten.compact.map{|i| " " + i}
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
###########################################################################
|
273
|
-
#
|
274
|
-
# Build Subclass or Interface:
|
275
|
-
#
|
276
|
-
|
277
|
-
#
|
278
|
-
# build_file: Reads the src from the appropriate location,
|
279
|
-
# uses the substitutions hash to modify the contents,
|
280
|
-
# and writes to the new location
|
281
|
-
#
|
282
|
-
def build_file(src, package, name, substitutions, dest='.')
|
283
|
-
to = File.join(dest, "src/#{package.gsub('.', '/')}")
|
284
|
-
Dir.mkdir(to) unless File.directory?(to)
|
285
|
-
|
286
|
-
text = File.read(File.expand_path($gem_root + "/assets/src/#{src}.java"))
|
287
|
-
substitutions.each {|k,v| text.gsub!(k, v)}
|
288
|
-
|
289
|
-
File.open(File.join(to, "#{name}.java"), 'w') {|f| f << text}
|
290
|
-
end
|
291
|
-
|
292
|
-
#
|
293
|
-
# get_class_or_interface: Opens the xml file and locates the specified class.
|
294
|
-
# Aborts if the class is not found or if it is not available for
|
295
|
-
# all api levels
|
296
|
-
#
|
297
|
-
def get_class_or_interface(klass, force=false)
|
298
|
-
element = verify_api.find_class_or_interface(klass, "either")
|
299
|
-
|
300
|
-
abort "ERROR: #{klass} not found" unless element
|
301
|
-
|
302
|
-
unless force
|
303
|
-
abort "#{klass} not available in minSdkVersion, added in #{element.attribute('api_added')}; use --force to create it" if
|
304
|
-
element.attribute('api_added') and element.attribute('api_added').to_i > verify_min_sdk.to_i
|
305
|
-
abort "#{klass} deprecated for targetSdkVersion, deprecatrd in #{element.attribute('deprecated')}; use --force to create it" if
|
306
|
-
element.attribute('deprecated') and element.attribute('deprecated').to_i <= verify_target_sdk.to_i
|
307
|
-
end
|
308
|
-
|
309
|
-
abort "#{klass} removed for targetSdkVersion, removed in #{element.attribute('api_removed')}" if
|
310
|
-
element.attribute('api_removed') and element.attribute('api_removed').to_i <= verify_target_sdk.to_i
|
311
|
-
|
312
|
-
element
|
313
|
-
end
|
314
|
-
|
315
|
-
#
|
316
|
-
# check_methods: Checks the methods to see if they are available for all api levels
|
317
|
-
#
|
318
|
-
def check_methods(methods, force=false)
|
319
|
-
min_api = verify_min_sdk.to_i
|
320
|
-
target_api = verify_target_sdk.to_i
|
321
|
-
|
322
|
-
# Remove methods changed outside of the scope of the sdk versions
|
323
|
-
methods = methods.select{|i| not i.attribute('api_added') or i.attribute('api_added').to_i <= target_api}
|
324
|
-
methods = methods.select{|i| not i.attribute('deprecated') or i.attribute('deprecated').to_i > min_api}
|
325
|
-
methods = methods.select{|i| not i.attribute('api_removed') or i.attribute('api_removed').to_i > min_api}
|
326
|
-
|
327
|
-
# Inform and remove methods that do not exist in one of the sdk versions
|
328
|
-
methods = methods.select do |i|
|
329
|
-
if i.attribute('api_removed') and i.attribute('api_removed').to_i <= target_api
|
330
|
-
puts "Can't create #{i.method_signature} -- removed in #{i.attribute('api_removed')}"
|
331
|
-
false
|
332
|
-
else
|
333
|
-
true
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
new_methods = methods
|
338
|
-
unless force
|
339
|
-
# Inform and remove methods changed inside the scope of the sdk versions
|
340
|
-
new_methods = methods.select do |i|
|
341
|
-
if i.attribute('api_added') and i.attribute('api_added').to_i > min_api
|
342
|
-
puts "Can't create #{i.method_signature} -- added in #{i.attribute('api_added')} -- exclude or force"
|
343
|
-
false
|
344
|
-
elsif i.attribute('deprecated') and i.attribute('deprecated').to_i <= target_api
|
345
|
-
puts "Can't create #{i.method_signature} -- deprecated in #{i.attribute('deprecated')} -- exclude or force"
|
346
|
-
false
|
347
|
-
else
|
348
|
-
true
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
|
-
abort("Aborting!") if methods.count != new_methods.count
|
353
|
-
end
|
354
|
-
|
355
|
-
new_methods
|
356
|
-
end
|
357
|
-
|
358
|
-
#
|
359
|
-
# generate_subclass_or_interface: Creates a subclass or interface based on the specifications.
|
360
|
-
#
|
361
|
-
def generate_subclass_or_interface(params)
|
362
|
-
defaults = {:template => "InheritingClass", :method_base => "all", :method_include => "", :method_exclude => "", :force => false, :implements => ""}
|
363
|
-
params = defaults.merge(params)
|
364
|
-
params[:package] = verify_package unless params[:package]
|
365
|
-
|
366
|
-
class_desc = get_class_or_interface(params[:class] || params[:interface], params[:force])
|
367
|
-
|
368
|
-
puts "Generating methods for #{params[:name]}..."
|
369
|
-
methods = class_desc.all_methods(params[:method_base], params[:method_include], params[:method_exclude], params[:implements])
|
370
|
-
methods = check_methods(methods, params[:force])
|
371
|
-
puts "Done. Methods created: #{methods.count}"
|
372
|
-
|
373
|
-
# Remove any duplicate constants (use *args handle multiple parameter lists)
|
374
|
-
constants = methods.map(&:constant_string).uniq
|
375
|
-
|
376
|
-
build_file params[:template], params[:package], params[:name], {
|
377
|
-
"THE_PACKAGE" => params[:package],
|
378
|
-
"THE_ACTION" => class_desc.name == "class" ? "extends" : "implements",
|
379
|
-
"THE_ANDROID_CLASS" => (params[:class] || params[:interface]) +
|
380
|
-
(params[:implements] == "" ? "" : (" implements " + params[:implements].split(",").join(", "))),
|
381
|
-
"THE_RUBOTO_CLASS" => params[:name],
|
382
|
-
"THE_CONSTANTS" => constants.map {|i| "public static final int #{i} = #{constants.index(i)};"}.indent.join("\n"),
|
383
|
-
"CONSTANTS_COUNT" => methods.count.to_s,
|
384
|
-
"THE_CONSTRUCTORS" => class_desc.name == "class" ?
|
385
|
-
class_desc.get_elements("constructor").map{|i| i.constructor_definition(params[:name])}.join("\n\n") : "",
|
386
|
-
"THE_METHODS" => methods.map{|i| i.method_definition}.join("\n\n")
|
387
|
-
}
|
388
|
-
end
|
389
|
-
|
390
|
-
#
|
391
|
-
# generate_core_classe: generates RubotoActivity, RubotoService, etc. based
|
392
|
-
# on the API specifications.
|
393
|
-
#
|
394
|
-
def generate_core_classes(params)
|
395
|
-
%w(android.view.View.OnClickListener android.widget.AdapterView.OnItemClickListener).each do |i|
|
396
|
-
name = i.split(".")[-1]
|
397
|
-
if(params[:class] == name or params[:class] == "all")
|
398
|
-
generate_subclass_or_interface({:package => "org.ruboto.callbacks", :class => i, :name => "Ruboto#{name}"})
|
399
|
-
end
|
400
|
-
end
|
401
|
-
|
402
|
-
hash = {:package => "org.ruboto"}
|
403
|
-
%w(method_base method_include implements force).inject(hash) {|h, i| h[i.to_sym] = params[i.to_sym]; h}
|
404
|
-
hash[:method_exclude] = params[:method_exclude].split(",").push("onCreate").push("onReceive").join(",")
|
405
|
-
|
406
|
-
%w(android.app.Activity android.app.Service android.content.BroadcastReceiver android.view.View).each do |i|
|
407
|
-
name = i.split(".")[-1]
|
408
|
-
if(params[:class] == name or params[:class] == "all")
|
409
|
-
generate_subclass_or_interface(
|
410
|
-
hash.merge({:template => name == "View" ? "InheritingClass" : "Ruboto#{name}", :class => i, :name => "Ruboto#{name}"}))
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
# Activities that can be created, but only directly (i.e., not included in all)
|
415
|
-
%w(android.preference.PreferenceActivity android.app.TabActivity).each do |i|
|
416
|
-
name = i.split(".")[-1]
|
417
|
-
if params[:class] == name
|
418
|
-
generate_subclass_or_interface(hash.merge({:template => "RubotoActivity", :class => i, :name => "Ruboto#{name}"}))
|
419
|
-
end
|
420
|
-
end
|
421
|
-
end
|
422
|
-
|
423
|
-
###########################################################################
|
424
|
-
#
|
425
|
-
# Updating components
|
426
|
-
#
|
427
|
-
|
428
|
-
def update_jruby(force=nil)
|
429
|
-
jruby_core = Dir.glob("libs/jruby-core-*.jar")[0]
|
430
|
-
jruby_stdlib = Dir.glob("libs/jruby-stdlib-*.jar")[0]
|
431
|
-
new_jruby_version = JRubyJars::core_jar_path.split('/')[-1][11..-5]
|
432
|
-
|
433
|
-
unless force
|
434
|
-
abort "cannot find existing jruby jars in libs. Make sure you're in the root directory of your app" if
|
435
|
-
(not jruby_core or not jruby_stdlib)
|
436
|
-
|
437
|
-
current_jruby_version = jruby_core ? jruby_core[16..-5] : "None"
|
438
|
-
abort "both jruby versions are #{new_jruby_version}. Nothing to update. Make sure you 'gem update jruby-jars' if there is a new version" if
|
439
|
-
current_jruby_version == new_jruby_version
|
440
|
-
|
441
|
-
puts "Current jruby version: #{current_jruby_version}"
|
442
|
-
puts "New jruby version: #{new_jruby_version}"
|
443
|
-
end
|
444
|
-
|
445
|
-
copier = AssetCopier.new $assets, File.expand_path(".")
|
446
|
-
log_action("Removing #{jruby_core}") {File.delete jruby_core} if jruby_core
|
447
|
-
log_action("Removing #{jruby_stdlib}") {File.delete jruby_stdlib} if jruby_stdlib
|
448
|
-
log_action("Copying #{JRubyJars::core_jar_path} to libs") {copier.copy_from_absolute_path JRubyJars::core_jar_path, "libs"}
|
449
|
-
log_action("Copying #{JRubyJars::stdlib_jar_path} to libs") {copier.copy_from_absolute_path JRubyJars::stdlib_jar_path, "libs"}
|
450
|
-
|
451
|
-
reconfigure_jruby_libs
|
452
|
-
|
453
|
-
puts "JRuby version is now: #{new_jruby_version}"
|
454
|
-
end
|
455
|
-
|
456
|
-
def update_ruboto(force=nil)
|
457
|
-
verify_manifest
|
458
|
-
|
459
|
-
from = File.expand_path($gem_root + "/assets/assets/scripts/ruboto.rb")
|
460
|
-
to = File.expand_path("./assets/scripts/ruboto.rb")
|
461
|
-
|
462
|
-
from_text = File.read(from)
|
463
|
-
to_text = File.read(to) if File.exists?(to)
|
464
|
-
|
465
|
-
unless force
|
466
|
-
puts "New version: #{from_text[/\$RUBOTO_VERSION = (\d+)/, 1]}"
|
467
|
-
puts "Old version: #{to_text ? to_text[/\$RUBOTO_VERSION = (\d+)/, 1] : 'none'}"
|
468
|
-
|
469
|
-
abort "The ruboto.rb verion has not changed. Use --force to force update." if
|
470
|
-
from_text[/\$RUBOTO_VERSION = (\d+)/, 1] == to_text[/\$RUBOTO_VERSION = (\d+)/, 1]
|
471
|
-
end
|
472
|
-
|
473
|
-
log_action("Copying ruboto.rb and setting the package name") do
|
474
|
-
File.open(to, 'w') {|f| f << from_text.gsub("THE_PACKAGE", verify_package).gsub("ACTIVITY_NAME", verify_activity)}
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
#
|
479
|
-
# reconfigure_jruby_libs:
|
480
|
-
# - removes unneeded code from jruby-core
|
481
|
-
# - moves ruby stdlib to the root of the ruby-stdlib jar
|
482
|
-
#
|
483
|
-
|
484
|
-
def reconfigure_jruby_libs
|
485
|
-
jruby_core = JRubyJars::core_jar_path.split('/')[-1]
|
486
|
-
log_action("Removing unneeded classes from #{jruby_core}") do
|
487
|
-
Dir.mkdir "libs/tmp"
|
488
|
-
Dir.chdir "libs/tmp"
|
489
|
-
FileUtils.move "../#{jruby_core}", "."
|
490
|
-
`jar -xf #{jruby_core}`
|
491
|
-
File.delete jruby_core
|
492
|
-
['jni', 'org/jruby/ant', 'org/jruby/compiler/ir', 'org/jruby/demo', 'org/jruby/embed/bsf',
|
493
|
-
'org/jruby/embed/jsr223', 'org/jruby/ext/ffi','org/jruby/javasupport/bsf'
|
494
|
-
].each {|i| FileUtils.remove_dir i, true}
|
495
|
-
`jar -cf ../#{jruby_core} .`
|
496
|
-
Dir.chdir "../.."
|
497
|
-
FileUtils.remove_dir "libs/tmp", true
|
498
|
-
end
|
499
|
-
|
500
|
-
jruby_stdlib = JRubyJars::stdlib_jar_path.split('/')[-1]
|
501
|
-
log_action("Reformatting #{jruby_stdlib}") do
|
502
|
-
Dir.mkdir "libs/tmp"
|
503
|
-
Dir.chdir "libs/tmp"
|
504
|
-
FileUtils.move "../#{jruby_stdlib}", "."
|
505
|
-
`jar -xf #{jruby_stdlib}`
|
506
|
-
File.delete jruby_stdlib
|
507
|
-
FileUtils.move "META-INF/jruby.home/lib/ruby/1.8", ".."
|
508
|
-
Dir.chdir "../1.8"
|
509
|
-
FileUtils.remove_dir "../tmp", true
|
510
|
-
`jar -cf ../#{jruby_stdlib} .`
|
511
|
-
Dir.chdir "../.."
|
512
|
-
FileUtils.remove_dir "libs/1.8", true
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
###########################################################################
|
517
|
-
#
|
518
|
-
# Verify the presence of important components
|
519
|
-
#
|
520
|
-
|
521
|
-
def verify_manifest
|
522
|
-
abort "cannot find your AndroidManifest.xml to extract info from it. Make sure you're in the root directory of your app" unless
|
523
|
-
File.exists? 'AndroidManifest.xml'
|
524
|
-
@manifest ||= REXML::Document.new(File.read('AndroidManifest.xml')).root
|
525
|
-
end
|
526
|
-
|
527
|
-
def verify_package
|
528
|
-
verify_manifest
|
529
|
-
@package ||= @manifest.attribute('package').value
|
530
|
-
end
|
531
|
-
|
532
|
-
def verify_activity
|
533
|
-
verify_manifest
|
534
|
-
@activity ||= @manifest.elements['application/activity'].attribute('android:name').value
|
535
|
-
end
|
536
|
-
|
537
|
-
def verify_sdk_versions
|
538
|
-
verify_manifest
|
539
|
-
@uses_sdk ||= @manifest.elements["uses-sdk"]
|
540
|
-
abort "you must specify your sdk level in the manifest (e.g., <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='8' />)" unless @uses_sdk
|
541
|
-
@uses_sdk
|
542
|
-
end
|
543
|
-
|
544
|
-
def verify_min_sdk
|
545
|
-
verify_sdk_versions
|
546
|
-
@min_sdk ||= @uses_sdk.attribute('android:minSdkVersion').value
|
547
|
-
abort "you must specify a minimum sdk level in the manifest (e.g., <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='8' />)" unless @min_sdk
|
548
|
-
@min_sdk
|
549
|
-
end
|
550
|
-
|
551
|
-
def verify_target_sdk
|
552
|
-
verify_sdk_versions
|
553
|
-
@target_sdk ||= @uses_sdk.attribute('android:targetSdkVersion').value
|
554
|
-
abort "you must specify a target sdk level in the manifest (e.g., <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='8' />)" unless @target_sdk
|
555
|
-
@target_sdk
|
556
|
-
end
|
557
|
-
|
558
|
-
def verify_api
|
559
|
-
unless $api
|
560
|
-
api = File.expand_path($gem_root + "/lib/java_class_gen/android_api.xml")
|
561
|
-
abort "cannot find android_api.xml to extract info from it." unless File.exists? api
|
562
|
-
log_action("Loading Android API") {$api = scan_in_api(File.read(api))["api"][0]}
|
563
|
-
end
|
564
|
-
$api
|
565
|
-
end
|
566
|
-
|
567
|
-
###########################################################################
|
568
|
-
#
|
569
|
-
# Scan the XML file. Much faster than using REXML.
|
570
|
-
#
|
571
|
-
|
572
|
-
def scan_in_api(file)
|
573
|
-
require 'strscan'
|
574
|
-
doc = StringScanner.new(file)
|
575
|
-
$api = XMLElement.new
|
576
|
-
parents = [$api]
|
577
|
-
|
578
|
-
while not doc.eos?
|
579
|
-
doc.scan(/</)
|
580
|
-
if doc.scan(/\/\w+>/)
|
581
|
-
parents.pop
|
582
|
-
else
|
583
|
-
name = doc.scan(/\w+/)
|
584
|
-
doc.scan(/\s+/)
|
585
|
-
values = {}
|
586
|
-
while not (term = doc.scan(/[\/>]/))
|
587
|
-
key = doc.scan(/\w+/)
|
588
|
-
doc.scan(/='/)
|
589
|
-
value = doc.scan(/[^']*/)
|
590
|
-
doc.scan(/'\s*/)
|
591
|
-
values[key] = value
|
592
|
-
end
|
593
|
-
element = parents[-1].add_element(name, values)
|
594
|
-
parents.push(element) if term == ">"
|
595
|
-
doc.scan(/>/) if term == "/"
|
596
|
-
end
|
597
|
-
end
|
598
|
-
$api
|
599
|
-
end
|
600
|
-
|
601
|
-
###########################################################################
|
602
|
-
#
|
603
|
-
# generate_inheriting_file:
|
604
|
-
# Builds a script based subclass of Activity, Service, or BroadcastReceiver
|
605
|
-
#
|
606
|
-
|
607
|
-
def generate_inheriting_file(klass, name, package, script_name, dest='.')
|
608
|
-
to = File.join(dest, "src/#{package.gsub('.', '/')}")
|
609
|
-
|
610
|
-
FileUtils.cp(File.expand_path(__FILE__ + "/../../assets/src/Inheriting#{klass}.java"), to)
|
611
|
-
FileUtils.move(File.join(to, "Inheriting#{klass}.java"), File.join(to, "#{name}.java"))
|
612
|
-
|
613
|
-
file = File.join(to, "#{name}.java")
|
614
|
-
text = File.read(file)
|
615
|
-
File.open(file, 'w') do |f|
|
616
|
-
f << text.gsub("THE_PACKAGE", package).gsub("Inheriting#{klass}", name).gsub("start.rb", script_name)
|
617
|
-
end
|
618
|
-
|
619
|
-
sample_source = File.read(File.join($assets, "samples/sample_#{underscore klass}.rb"))
|
620
|
-
File.open File.join(dest, "assets/scripts/#{script_name}"), "a" do |f|
|
621
|
-
f << sample_source
|
622
|
-
end
|
623
|
-
end
|
624
|
-
|
625
|
-
# active_support/inflector.rb
|
626
|
-
def underscore(camel_cased_word)
|
627
|
-
camel_cased_word.to_s.gsub(/::/, '/').
|
628
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
629
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
630
|
-
tr("-", "_").
|
631
|
-
downcase
|
632
|
-
end
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
Main {
|
637
|
-
mode "gen" do
|
638
|
-
mode "app" do
|
639
|
-
option("name"){
|
640
|
-
required
|
641
|
-
argument :required
|
642
|
-
description "Name of your app"
|
643
|
-
}
|
644
|
-
option("target") {
|
645
|
-
required
|
646
|
-
argument :required
|
647
|
-
defaults 'android-9'
|
648
|
-
description "android version to target. must begin with 'android-' (e.g., 'android-8' for froyo)"
|
649
|
-
}
|
650
|
-
option("min_sdk") {
|
651
|
-
required
|
652
|
-
argument :required
|
653
|
-
defaults 'android-7'
|
654
|
-
description "minimum android version supported. must begin with 'android-'. (default 'android-3')"
|
655
|
-
}
|
656
|
-
option("path"){
|
657
|
-
required
|
658
|
-
argument :required
|
659
|
-
description "path to where you want your app."
|
660
|
-
}
|
661
|
-
option("package"){
|
662
|
-
required
|
663
|
-
argument :required
|
664
|
-
defaults 'org.ruboto.example'
|
665
|
-
description "Name of package. Must be unique for every app. A common pattern is yourtld.yourdomain.appname (Ex. org.ruboto.irb)"
|
666
|
-
}
|
667
|
-
option("activity"){
|
668
|
-
required
|
669
|
-
argument :required
|
670
|
-
defaults 'Main'
|
671
|
-
description "name of your primary Activity"
|
672
|
-
}
|
673
|
-
|
674
|
-
def run
|
675
|
-
path = params['path'].value
|
676
|
-
name = params['name'].value
|
677
|
-
target = params['target'].value
|
678
|
-
min_sdk = params['min_sdk'].value
|
679
|
-
package = params['package'].value
|
680
|
-
activity = params['activity'].value
|
681
|
-
|
682
|
-
abort "path must be to a directory that does not yet exist. it will be created" if
|
683
|
-
File.exists?(path)
|
684
|
-
|
685
|
-
root = File.expand_path(path)
|
686
|
-
print "\nGenerating Android app #{name} in #{root}..."
|
687
|
-
`android create project -n #{name} -t #{target} -p #{path} -k #{package} -a #{activity}`
|
688
|
-
puts "Done"
|
689
|
-
|
690
|
-
puts "\nCopying files:"
|
691
|
-
copier = AssetCopier.new $assets, root
|
692
|
-
|
693
|
-
%w{Rakefile .gitignore assets}.each do |f|
|
694
|
-
log_action(f) {copier.copy f}
|
695
|
-
end
|
696
|
-
|
697
|
-
log_action("Ruboto java classes"){copier.copy "src/org/ruboto/*.java", "src/org/ruboto"}
|
698
|
-
|
699
|
-
# Remember the current directory and chdir to the new app directory
|
700
|
-
current_dir = Dir.pwd
|
701
|
-
Dir.chdir root
|
702
|
-
|
703
|
-
update_jruby true
|
704
|
-
|
705
|
-
log_action("\nAdding activities (RubotoActivity and RubotoDialog) and SDK versions to the manifest") do
|
706
|
-
verify_manifest.elements['application'].add_element 'activity', {"android:name" => "org.ruboto.RubotoActivity"}
|
707
|
-
verify_manifest.elements['application'].add_element 'activity', {"android:name" => "org.ruboto.RubotoDialog",
|
708
|
-
"android:theme" => "@android:style/Theme.Dialog"}
|
709
|
-
verify_manifest.add_element 'uses-sdk', {"android:minSdkVersion" => min_sdk[/\d+/], "android:targetSdkVersion" => target[/\d+/]}
|
710
|
-
File.open("AndroidManifest.xml", 'w') {|f| verify_manifest.document.write(f, 4)}
|
711
|
-
end
|
712
|
-
|
713
|
-
update_ruboto true
|
714
|
-
|
715
|
-
generate_core_classes(:class => "all", :method_base => "on", :method_include => "", :method_exclude => "", :force => true, :implements => "")
|
716
|
-
|
717
|
-
# Go back to whence we came
|
718
|
-
Dir.chdir current_dir
|
719
|
-
|
720
|
-
log_action("Generating the default Activity and script") do
|
721
|
-
generate_inheriting_file "Activity", activity, package, "#{underscore(activity)}.rb", path
|
722
|
-
end
|
723
|
-
|
724
|
-
puts "\nHello, #{name}\n"
|
725
|
-
end
|
726
|
-
end
|
727
|
-
|
728
|
-
mode "class" do
|
729
|
-
argument("class"){
|
730
|
-
required
|
731
|
-
description "the Android Class that you want."
|
732
|
-
}
|
733
|
-
|
734
|
-
option("script_name"){
|
735
|
-
argument :required
|
736
|
-
description "name of the ruby script in assets/scripts/ that this class will execute. should end in .rb. optional"
|
737
|
-
}
|
738
|
-
|
739
|
-
option("name"){
|
740
|
-
required
|
741
|
-
argument :required
|
742
|
-
description "name of the class (and file). Should be CamelCase"
|
743
|
-
}
|
744
|
-
|
745
|
-
|
746
|
-
def run
|
747
|
-
name = params['name'].value
|
748
|
-
script_name = params['script_name'].value || "#{underscore(name)}.rb"
|
749
|
-
klass = params['class'].value
|
750
|
-
|
751
|
-
generate_inheriting_file klass, name, verify_package, script_name
|
752
|
-
end
|
753
|
-
end
|
754
|
-
|
755
|
-
mode "subclass" do
|
756
|
-
argument("class"){
|
757
|
-
required
|
758
|
-
description "the Android Class that you want to subclass (e.g., package.Class)."
|
759
|
-
}
|
760
|
-
|
761
|
-
option("name"){
|
762
|
-
required
|
763
|
-
argument :required
|
764
|
-
description "name of the class (and file). Should be CamelCase"
|
765
|
-
}
|
766
|
-
|
767
|
-
option("method_base"){
|
768
|
-
required
|
769
|
-
validate {|i| %w(all on none abstract).include?(i)}
|
770
|
-
argument :required
|
771
|
-
description "the base set of methods to generate (adjusted with method_include and method_exclude): all, none, abstract, on (e.g., onClick)"
|
772
|
-
}
|
773
|
-
|
774
|
-
option("method_include"){
|
775
|
-
argument :required
|
776
|
-
defaults ""
|
777
|
-
description "additional methods to add to the base list"
|
778
|
-
}
|
779
|
-
|
780
|
-
option("method_exclude"){
|
781
|
-
argument :required
|
782
|
-
defaults ""
|
783
|
-
description "methods to remove from the base list"
|
784
|
-
}
|
785
|
-
|
786
|
-
option("implements"){
|
787
|
-
required
|
788
|
-
argument :required
|
789
|
-
defaults ""
|
790
|
-
description "comma separated list interfaces to implement"
|
791
|
-
}
|
792
|
-
|
793
|
-
option("force"){
|
794
|
-
cast :boolean
|
795
|
-
description "force added and deprecated methods not excluded to be create"
|
796
|
-
}
|
797
|
-
|
798
|
-
def run
|
799
|
-
generate_subclass_or_interface(
|
800
|
-
%w(class name method_base method_include method_exclude implements force).inject({}) {|h, i| h[i.to_sym] = params[i].value; h})
|
801
|
-
end
|
802
|
-
end
|
803
|
-
|
804
|
-
mode "interface" do
|
805
|
-
argument("interface"){
|
806
|
-
required
|
807
|
-
description "the Android Interface that you want to implement (e.g., package.Interface)."
|
808
|
-
}
|
809
|
-
|
810
|
-
option("name"){
|
811
|
-
required
|
812
|
-
argument :required
|
813
|
-
description "name of the class (and file) that will implement the interface. Should be CamelCase"
|
814
|
-
}
|
815
|
-
|
816
|
-
option("force"){
|
817
|
-
cast :boolean
|
818
|
-
description "force added and deprecated interfaces to be create"
|
819
|
-
}
|
820
|
-
|
821
|
-
def run
|
822
|
-
generate_subclass_or_interface %w(interface name force).inject({}) {|h, i| h[i.to_sym] = params[i].value; h}
|
823
|
-
end
|
824
|
-
end
|
825
|
-
|
826
|
-
mode "core" do
|
827
|
-
argument("class"){
|
828
|
-
required
|
829
|
-
validate {|i| %w(Activity Service BroadcastReceiver View PreferenceActivity TabActivity OnClickListener OnItemClickListener all).include?(i)}
|
830
|
-
description "Activity, Service, BroadcastReceiver, View, OnClickListener, OnItemClickListener, or all (default = all); Other activities not included in 'all': PreferenceActivity, TabActivity"
|
831
|
-
}
|
832
|
-
|
833
|
-
option("method_base"){
|
834
|
-
required
|
835
|
-
argument :required
|
836
|
-
validate {|i| %w(all on none).include?(i)}
|
837
|
-
defaults "on"
|
838
|
-
description "the base set of methods to generate (adjusted with method_include and method_exclude): all, none, on (e.g., onClick)"
|
839
|
-
}
|
840
|
-
|
841
|
-
option("method_include"){
|
842
|
-
required
|
843
|
-
argument :required
|
844
|
-
defaults ""
|
845
|
-
description "additional methods to add to the base list"
|
846
|
-
}
|
847
|
-
|
848
|
-
option("method_exclude"){
|
849
|
-
required
|
850
|
-
argument :required
|
851
|
-
defaults ""
|
852
|
-
description "methods to remove from the base list"
|
853
|
-
}
|
854
|
-
|
855
|
-
option("implements"){
|
856
|
-
required
|
857
|
-
argument :required
|
858
|
-
defaults ""
|
859
|
-
description "for classes only, interfaces to implement (cannot be used with 'gen core all')"
|
860
|
-
}
|
861
|
-
|
862
|
-
option("force"){
|
863
|
-
cast :boolean
|
864
|
-
description "force added and deprecated methods not excluded to be create"
|
865
|
-
}
|
866
|
-
|
867
|
-
def run
|
868
|
-
abort("specify 'implements' only for Activity, Service, BroadcastReceiver, PreferenceActivity, or TabActivity") unless
|
869
|
-
%w(Activity Service BroadcastReceiver PreferenceActivity TabActivity).include?(params["class"].value) or params["implements"].value == ""
|
870
|
-
generate_core_classes [:class, :method_base, :method_include, :method_exclude, :implements, :force].inject({}) {|h, i| h[i] = params[i.to_s].value; h}
|
871
|
-
end
|
872
|
-
end
|
873
|
-
|
874
|
-
mode "key" do
|
875
|
-
option("keystore"){
|
876
|
-
default "~/.android/production.keystore"
|
877
|
-
description "path to where the keystore will be saved. defaults to ~/.android/production.keystore"
|
878
|
-
}
|
879
|
-
|
880
|
-
option("alias"){
|
881
|
-
required
|
882
|
-
description "The 'alias' for the key. Identifies the key within the keystore. Required"
|
883
|
-
}
|
884
|
-
|
885
|
-
def run
|
886
|
-
keystore = params['keystore'].value
|
887
|
-
key_alias = params['alias'].value
|
888
|
-
|
889
|
-
`keytool -genkey -keyalg rsa -keysize 4096 -validity 1000000 -keystore #{keystore} -alias #{key_alias}`
|
890
|
-
end
|
891
|
-
end
|
892
|
-
end
|
893
|
-
|
894
|
-
mode "update-jruby" do
|
895
|
-
argument("what"){
|
896
|
-
required
|
897
|
-
validate {|i| %w(jruby ruboto).include?(i)}
|
898
|
-
description "What do you want to update: 'jruby' or 'ruboto'"
|
899
|
-
}
|
900
|
-
|
901
|
-
option("force"){
|
902
|
-
description "force and update even if the version hasn't changed"
|
903
|
-
}
|
904
|
-
|
905
|
-
def run
|
906
|
-
case params['what'].value
|
907
|
-
when "jruby": update_jruby(params['force'].value);
|
908
|
-
when "ruboto": update_ruboto(params['force'].value);
|
909
|
-
end
|
910
|
-
end
|
911
|
-
end
|
912
|
-
|
913
|
-
# just running `ruboto`
|
914
|
-
def run
|
915
|
-
puts %Q{
|
916
|
-
Ruboto -- Ruby for Android
|
917
|
-
Execute `ruboto gen app --help` for instructions on how to generate a fresh Ruby-enabled Android app
|
918
|
-
Execute `ruboto --help` for other options
|
919
|
-
}
|
920
|
-
end
|
921
|
-
}
|
922
|
-
|
4
|
+
# Run Base, which will handle actual commands
|
5
|
+
Ruboto::Commands::Base.main
|