yet-another-ruby-tcl 0.1.2
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/.document +11 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +14 -0
- data/LICENSE +22 -0
- data/README.md +13 -0
- data/Rakefile +49 -0
- data/ext/tcl_ext/extconf.rb +40 -0
- data/ext/tcl_ext/tcl.c +237 -0
- data/lib/tcl/delegator.rb +17 -0
- data/lib/tcl/interp.rb +48 -0
- data/lib/tcl/proc.rb +48 -0
- data/lib/tcl/utils.rb +13 -0
- data/lib/tcl/var.rb +63 -0
- data/lib/tcl/version.rb +3 -0
- data/lib/tcl.rb +7 -0
- data/tcl.gemspec +22 -0
- data/test/fixtures/test.tcl +6 -0
- data/test/interp_receive_test.rb +107 -0
- data/test/interp_test.rb +123 -0
- data/test/proc_test.rb +42 -0
- data/test/test_helper.rb +21 -0
- data/test/var_test.rb +59 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d953a7ef2388ec0c122d75ee8f0aa1d1e8015c4d45f186bae997d7349cbccf98
|
|
4
|
+
data.tar.gz: 6b43f241ffb82c7a465e41f5b5ef69377d90b7a3ce3fb075c6c48715640424ae
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5dbfc97d96dd4c8441a1f66b3e75f51b8fbac3d7848bbc8f5fd43443254c89d84bec7eca3bf05c37aa0e08ebd74608a335c66f35e085b1c9351fb51a6873ea1e
|
|
7
|
+
data.tar.gz: 919d94922034ab630f531cc6d89817b8e31c314a12734e10fe640e71c2a05a6963a7f8d167ee43696f6a1d8e9367707470cd54691fa033b4ad0bb8c6dae071cd
|
data/.document
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# .document is used by rdoc and yard to know how to generate documentation
|
|
2
|
+
# for example, it can be used to control how rdoc gets built when you do `gem install foo`
|
|
3
|
+
|
|
4
|
+
README.rdoc
|
|
5
|
+
lib/**/*.rb
|
|
6
|
+
bin/*
|
|
7
|
+
|
|
8
|
+
# Files below this - are treated as 'extra files', and aren't parsed for ruby code
|
|
9
|
+
-
|
|
10
|
+
features/**/*.feature
|
|
11
|
+
LICENSE
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2008-2011 Sam Stephenson and Mark J. Titorenko
|
|
4
|
+
Copyright (c) 2026 Pedro Miranda
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ruby-tcl
|
|
2
|
+
|
|
3
|
+
A minimal Ruby interface to libtcl. The library was originally written by Sam Stephenson. This edition was created by Mark J. Titorenko.
|
|
4
|
+
|
|
5
|
+
This fork uses the original C bindings written by Sam Stephenson in order to integrate with libtcl. For a FFI implementation, see 'https://github.com/factorypreset/ruby-tcl (which also contains some other additions/improvements to the library).
|
|
6
|
+
|
|
7
|
+
The original codebase is located at https://github.com/sstephenson/ruby-tcl
|
|
8
|
+
|
|
9
|
+
## Note on Patches/Pull Requests
|
|
10
|
+
|
|
11
|
+
* Fork the project.
|
|
12
|
+
* Make your feature addition or bug fix.
|
|
13
|
+
* Add tests for it.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'bundler/gem_tasks'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'bundler'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
warn 'You must install bundler - run `gem install bundler`'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
Bundler.setup
|
|
12
|
+
rescue Bundler::BundlerError => e
|
|
13
|
+
warn e.message
|
|
14
|
+
warn 'Run `bundle install` to install missing gems'
|
|
15
|
+
exit e.status_code
|
|
16
|
+
end
|
|
17
|
+
require 'rake'
|
|
18
|
+
|
|
19
|
+
require 'rake/testtask'
|
|
20
|
+
Rake::TestTask.new do |t|
|
|
21
|
+
t.libs << 'test'
|
|
22
|
+
t.test_files = FileList['test/*_test.rb']
|
|
23
|
+
t.verbose = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require 'rdoc/task'
|
|
27
|
+
RDoc::Task.new do |rdoc|
|
|
28
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ''
|
|
29
|
+
|
|
30
|
+
rdoc.main = 'README.md'
|
|
31
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
32
|
+
rdoc.title = "yet-another-yet-another-ruby-tcl #{version}"
|
|
33
|
+
rdoc.rdoc_files.include('README*')
|
|
34
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
RUBY_TCL_SPEC = Bundler.load_gemspec('tcl.gemspec')
|
|
38
|
+
|
|
39
|
+
require 'rubocop/rake_task'
|
|
40
|
+
|
|
41
|
+
RuboCop::RakeTask.new do |task|
|
|
42
|
+
task.fail_on_error = false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
require 'rake/extensiontask'
|
|
46
|
+
Rake::ExtensionTask.new('tcl_ext', RUBY_TCL_SPEC)
|
|
47
|
+
CLEAN.include %w[**/*.{o,bundle,jar,so,obj,pdb,lib,def,exp,log} pkg/ tmp/ rdoc/ doc/]
|
|
48
|
+
|
|
49
|
+
task default: %i[clean compile test rubocop rdoc build install]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'mkmf'
|
|
2
|
+
|
|
3
|
+
# To build against Tcl 8.5 on OS X Intel:
|
|
4
|
+
# RC_ARCHS=i386 ruby extconf.rb --with-tcl-dir=/path/to/tcl8.5
|
|
5
|
+
|
|
6
|
+
dir_config('tcl')
|
|
7
|
+
|
|
8
|
+
unless pkg_config('tcl')
|
|
9
|
+
|
|
10
|
+
# 3. Fallback: OS-Specific manual search
|
|
11
|
+
# Determine if we are compiling on Windows
|
|
12
|
+
is_windows = RUBY_PLATFORM.match?(/mingw|mswin/)
|
|
13
|
+
|
|
14
|
+
# Define the library names based on the OS convention
|
|
15
|
+
lib_names = if is_windows
|
|
16
|
+
%w[tcl86 tcl85 tcl] # MSYS2 usually drops the dot
|
|
17
|
+
else
|
|
18
|
+
%w[tcl8.6 tcl8.5 tcl] # Unix usually keeps the dot
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Check for the header file
|
|
22
|
+
unless have_header('tcl.h') || find_header('tcl.h',
|
|
23
|
+
'/usr/include/tcl8.6',
|
|
24
|
+
'/usr/include/tcl8.5',
|
|
25
|
+
'/usr/local/include/tcl8.6',
|
|
26
|
+
'/usr/local/include/tcl8.5',
|
|
27
|
+
'/opt/homebrew/include',
|
|
28
|
+
'/opt/homebrew/opt/tcl-tk/include')
|
|
29
|
+
abort "\n*** ERROR: Cannot find tcl.h. Please install Tcl development headers or use --with-tcl-include ***\n"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Try to link against the libraries, one by one.
|
|
33
|
+
# We test for 'Tcl_Init' because it's a core Tcl C function.
|
|
34
|
+
found_lib = lib_names.any? do |lib|
|
|
35
|
+
have_library(lib, 'Tcl_Init')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
abort "\n*** ERROR: Cannot find the Tcl library. Please install Tcl or use --with-tcl-lib ***\n" unless found_lib
|
|
39
|
+
end
|
|
40
|
+
create_makefile('tcl_ext')
|
data/ext/tcl_ext/tcl.c
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <tcl.h>
|
|
3
|
+
|
|
4
|
+
typedef struct {
|
|
5
|
+
Tcl_Interp *interp;
|
|
6
|
+
VALUE exit_exception;
|
|
7
|
+
} tcl_interp_struct;
|
|
8
|
+
|
|
9
|
+
static void rb_tcl_interp_destroy(void *ptr) {
|
|
10
|
+
tcl_interp_struct *tcl_interp = (tcl_interp_struct *)ptr;
|
|
11
|
+
Tcl_DeleteInterp(tcl_interp->interp);
|
|
12
|
+
Tcl_Release(tcl_interp->interp);
|
|
13
|
+
free(tcl_interp);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static const rb_data_type_t tcl_interp_type = {
|
|
17
|
+
"tcl_interp_struct", // Name of the type
|
|
18
|
+
{
|
|
19
|
+
0, // dmark function (or 0 if none)
|
|
20
|
+
rb_tcl_interp_destroy, // dfree function (replace with your actual free func)
|
|
21
|
+
0, // dsize function (or 0 if none)
|
|
22
|
+
},
|
|
23
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
static VALUE rb_value_to_s(VALUE value) {
|
|
28
|
+
return rb_funcall(value, rb_intern("to_s"), 0, 0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static VALUE rb_tcl_interp_send_begin(VALUE args) {
|
|
32
|
+
VALUE obj = rb_ary_entry(args, 0);
|
|
33
|
+
VALUE interp_receive_args = rb_ary_entry(args, 1);
|
|
34
|
+
|
|
35
|
+
VALUE result = rb_funcall2(obj, rb_intern("interp_receive"), RARRAY_LEN(interp_receive_args), RARRAY_PTR(interp_receive_args));
|
|
36
|
+
|
|
37
|
+
tcl_interp_struct *tcl_interp;
|
|
38
|
+
TypedData_Get_Struct(obj, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
39
|
+
|
|
40
|
+
char *tcl_result = strdup(RSTRING_PTR(rb_value_to_s(result)));
|
|
41
|
+
Tcl_SetResult(tcl_interp->interp, tcl_result, (Tcl_FreeProc *)free);
|
|
42
|
+
|
|
43
|
+
return Qtrue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static VALUE rb_tcl_interp_send_rescue(VALUE args, VALUE error_info) {
|
|
47
|
+
VALUE obj = rb_ary_entry(args, 0);
|
|
48
|
+
tcl_interp_struct *tcl_interp;
|
|
49
|
+
TypedData_Get_Struct(obj, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
50
|
+
|
|
51
|
+
char *tcl_result = strdup(RSTRING_PTR(rb_value_to_s(error_info)));
|
|
52
|
+
Tcl_SetResult(tcl_interp->interp, tcl_result, (Tcl_FreeProc *)free);
|
|
53
|
+
|
|
54
|
+
if (rb_obj_is_kind_of(error_info, rb_eSystemExit)) {
|
|
55
|
+
tcl_interp->exit_exception = error_info;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Qfalse;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static int rb_tcl_interp_send(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
|
|
62
|
+
VALUE interp_receive_args = rb_ary_new2(objc - 1);
|
|
63
|
+
int i;
|
|
64
|
+
|
|
65
|
+
for (i = 1; i < objc; i++) {
|
|
66
|
+
int element_length;
|
|
67
|
+
const char *element;
|
|
68
|
+
|
|
69
|
+
element = Tcl_GetStringFromObj(objv[i], &element_length);
|
|
70
|
+
rb_ary_push(interp_receive_args, rb_str_new2(element));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
VALUE args = rb_ary_new3(2, (VALUE) clientData, interp_receive_args);
|
|
74
|
+
|
|
75
|
+
if (rb_rescue2(rb_tcl_interp_send_begin, args, rb_tcl_interp_send_rescue, args, rb_eException) == Qtrue) {
|
|
76
|
+
return TCL_OK;
|
|
77
|
+
} else {
|
|
78
|
+
return TCL_ERROR;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static VALUE rb_tcl_interp_allocate(VALUE klass) {
|
|
83
|
+
tcl_interp_struct *tcl_interp;
|
|
84
|
+
VALUE obj = TypedData_Make_Struct(klass, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
85
|
+
|
|
86
|
+
tcl_interp->interp = Tcl_CreateInterp();
|
|
87
|
+
tcl_interp->exit_exception = Qnil;
|
|
88
|
+
Tcl_Init(tcl_interp->interp);
|
|
89
|
+
Tcl_Preserve(tcl_interp->interp);
|
|
90
|
+
|
|
91
|
+
Tcl_CreateObjCommand(tcl_interp->interp, "interp_send", (Tcl_ObjCmdProc *)rb_tcl_interp_send, (ClientData) obj, (Tcl_CmdDeleteProc *)NULL);
|
|
92
|
+
|
|
93
|
+
return obj;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static VALUE rb_tcl_safe_interp_allocate(VALUE klass) {
|
|
97
|
+
VALUE obj = rb_tcl_interp_allocate(klass);
|
|
98
|
+
|
|
99
|
+
tcl_interp_struct *tcl_interp;
|
|
100
|
+
TypedData_Get_Struct(obj, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
101
|
+
|
|
102
|
+
Tcl_MakeSafe(tcl_interp->interp);
|
|
103
|
+
|
|
104
|
+
return obj;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#ifdef TCL_LIMIT_TIME
|
|
108
|
+
static VALUE rb_tcl_interp_eval(VALUE self, VALUE args) {
|
|
109
|
+
VALUE script = rb_ary_entry(args, 0);
|
|
110
|
+
|
|
111
|
+
int timeout = 0;
|
|
112
|
+
if (RARRAY_LEN(args) == 2) {
|
|
113
|
+
timeout = NUM2INT(rb_ary_entry(args, 1));
|
|
114
|
+
}
|
|
115
|
+
#else
|
|
116
|
+
static VALUE rb_tcl_interp_eval(VALUE self, VALUE script) {
|
|
117
|
+
#endif
|
|
118
|
+
|
|
119
|
+
tcl_interp_struct *tcl_interp;
|
|
120
|
+
TypedData_Get_Struct(self, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
121
|
+
|
|
122
|
+
#ifdef TCL_LIMIT_TIME
|
|
123
|
+
if (timeout > 0) {
|
|
124
|
+
Tcl_Time timeout_time;
|
|
125
|
+
Tcl_GetTime(&timeout_time);
|
|
126
|
+
timeout_time.sec += (long) timeout / 1000;
|
|
127
|
+
timeout_time.usec += (long) (timeout % 1000) * 1000;
|
|
128
|
+
|
|
129
|
+
Tcl_LimitSetTime(tcl_interp->interp, &timeout_time);
|
|
130
|
+
Tcl_LimitTypeSet(tcl_interp->interp, TCL_LIMIT_TIME);
|
|
131
|
+
}
|
|
132
|
+
#endif
|
|
133
|
+
|
|
134
|
+
int result = Tcl_Eval(tcl_interp->interp, RSTRING_PTR(rb_value_to_s(script)));
|
|
135
|
+
|
|
136
|
+
VALUE error_class = rb_const_get(rb_const_get(rb_cObject, rb_intern("Tcl")), rb_intern("Error"));
|
|
137
|
+
|
|
138
|
+
#ifdef TCL_LIMIT_TIME
|
|
139
|
+
if (timeout > 0) {
|
|
140
|
+
if (Tcl_LimitTypeExceeded(tcl_interp->interp, TCL_LIMIT_TIME))
|
|
141
|
+
error_class = rb_const_get(rb_const_get(rb_cObject, rb_intern("Tcl")), rb_intern("Timeout"));
|
|
142
|
+
|
|
143
|
+
Tcl_LimitTypeReset(tcl_interp->interp, TCL_LIMIT_TIME);
|
|
144
|
+
}
|
|
145
|
+
#endif
|
|
146
|
+
|
|
147
|
+
switch (result) {
|
|
148
|
+
case TCL_OK:
|
|
149
|
+
return rb_str_new2(Tcl_GetStringResult(tcl_interp->interp));
|
|
150
|
+
case TCL_ERROR:
|
|
151
|
+
if (NIL_P(tcl_interp->exit_exception)) {
|
|
152
|
+
rb_raise(error_class, "%s", Tcl_GetStringResult(tcl_interp->interp));
|
|
153
|
+
} else {
|
|
154
|
+
rb_exit(NUM2INT(rb_iv_get(tcl_interp->exit_exception, "status")));
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
return Qnil;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static VALUE rb_tcl_interp_list_to_array(VALUE self, VALUE list) {
|
|
162
|
+
tcl_interp_struct *tcl_interp;
|
|
163
|
+
TypedData_Get_Struct(self, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
164
|
+
|
|
165
|
+
Tcl_Obj *string = Tcl_NewStringObj(RSTRING_PTR(rb_value_to_s(list)), -1);
|
|
166
|
+
Tcl_IncrRefCount(string);
|
|
167
|
+
|
|
168
|
+
int list_length, i;
|
|
169
|
+
Tcl_Obj **elements;
|
|
170
|
+
|
|
171
|
+
if (Tcl_ListObjGetElements(tcl_interp->interp, string, &list_length, &elements) != TCL_OK) {
|
|
172
|
+
Tcl_DecrRefCount(string);
|
|
173
|
+
return Qnil;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (i = 0; i < list_length; i++)
|
|
177
|
+
Tcl_IncrRefCount(elements[i]);
|
|
178
|
+
|
|
179
|
+
VALUE result = rb_ary_new2(list_length);
|
|
180
|
+
|
|
181
|
+
for (i = 0; i < list_length; i++) {
|
|
182
|
+
int element_length;
|
|
183
|
+
const char *element;
|
|
184
|
+
|
|
185
|
+
element = Tcl_GetStringFromObj(elements[i], &element_length);
|
|
186
|
+
rb_ary_push(result, element ? rb_str_new(element, element_length) : rb_str_new2(""));
|
|
187
|
+
Tcl_DecrRefCount(elements[i]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
Tcl_DecrRefCount(string);
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static VALUE rb_tcl_interp_array_to_list(VALUE self, VALUE array) {
|
|
196
|
+
tcl_interp_struct *tcl_interp;
|
|
197
|
+
TypedData_Get_Struct(self, tcl_interp_struct, &tcl_interp_type, tcl_interp);
|
|
198
|
+
|
|
199
|
+
int array_length = RARRAY_LEN(array), i;
|
|
200
|
+
|
|
201
|
+
Tcl_Obj *list = Tcl_NewObj();
|
|
202
|
+
Tcl_IncrRefCount(list);
|
|
203
|
+
|
|
204
|
+
for (i = 0; i < array_length; i++) {
|
|
205
|
+
VALUE element = rb_ary_entry(array, i);
|
|
206
|
+
Tcl_Obj *string = Tcl_NewStringObj(RSTRING_PTR(rb_value_to_s(element)), -1);
|
|
207
|
+
|
|
208
|
+
Tcl_IncrRefCount(string);
|
|
209
|
+
Tcl_ListObjAppendElement(tcl_interp->interp, list, string);
|
|
210
|
+
Tcl_DecrRefCount(string);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
VALUE result = rb_str_new2(Tcl_GetStringFromObj(list, NULL));
|
|
214
|
+
|
|
215
|
+
Tcl_DecrRefCount(list);
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
void Init_tcl_ext(void) {
|
|
221
|
+
VALUE tcl_module = rb_define_module("Tcl");
|
|
222
|
+
VALUE interp_class = rb_define_class_under(tcl_module, "Interp", rb_cObject);
|
|
223
|
+
VALUE safe_interp_class = rb_define_class_under(tcl_module, "SafeInterp", interp_class);
|
|
224
|
+
VALUE error_class = rb_define_class_under(tcl_module, "Error", rb_eStandardError);
|
|
225
|
+
|
|
226
|
+
rb_define_alloc_func(interp_class, rb_tcl_interp_allocate);
|
|
227
|
+
rb_define_alloc_func(safe_interp_class, rb_tcl_safe_interp_allocate);
|
|
228
|
+
rb_define_method(interp_class, "list_to_array", rb_tcl_interp_list_to_array, 1);
|
|
229
|
+
rb_define_method(interp_class, "array_to_list", rb_tcl_interp_array_to_list, 1);
|
|
230
|
+
|
|
231
|
+
#ifdef TCL_LIMIT_TIME
|
|
232
|
+
rb_define_class_under(tcl_module, "Timeout", error_class);
|
|
233
|
+
rb_define_method(interp_class, "eval", rb_tcl_interp_eval, -2);
|
|
234
|
+
#else
|
|
235
|
+
rb_define_method(interp_class, "eval", rb_tcl_interp_eval, 1);
|
|
236
|
+
#endif
|
|
237
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Tcl
|
|
2
|
+
module Delegator
|
|
3
|
+
def self.included(klass)
|
|
4
|
+
klass.class_eval do
|
|
5
|
+
attr_reader :interp
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def method_missing(name, ...)
|
|
10
|
+
interp.respond_to?(name) ? interp.send(name, ...) : super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def respond_to_missing?(name, include_private = false)
|
|
14
|
+
interp.respond_to?(name, include_private) || super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/tcl/interp.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Tcl
|
|
2
|
+
class Interp
|
|
3
|
+
include Utils
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def load_from_file(filename)
|
|
7
|
+
new.tap { _1.eval(File.read(filename)) }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def interp
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def interp_receive(method, *args)
|
|
16
|
+
send("tcl_#{method}", *args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def expose(name)
|
|
20
|
+
_!(:interp, :alias, nil, name, nil, :interp_send, name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def proc(name)
|
|
24
|
+
Tcl::Proc.new(self, name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def var(name)
|
|
28
|
+
Tcl::Var.find(self, name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def procs
|
|
32
|
+
list_to_array _!(:info, :procs)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def vars
|
|
36
|
+
list_to_array _!(:info, :vars)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_tcl
|
|
40
|
+
%w[var proc].each_with_object([]) do |type, lines|
|
|
41
|
+
send("#{type}s").sort.each do |name|
|
|
42
|
+
object = send(type, name)
|
|
43
|
+
lines << object.to_tcl unless object.builtin?
|
|
44
|
+
end
|
|
45
|
+
end.join("\n")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/tcl/proc.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Tcl
|
|
2
|
+
class Proc
|
|
3
|
+
BUILTINS = %w[
|
|
4
|
+
auto_execok auto_import auto_load auto_load_index
|
|
5
|
+
auto_qualify tclLog unknown
|
|
6
|
+
].freeze
|
|
7
|
+
include Utils
|
|
8
|
+
include Delegator
|
|
9
|
+
|
|
10
|
+
attr_reader :name
|
|
11
|
+
|
|
12
|
+
def initialize(interp, name)
|
|
13
|
+
@interp = interp
|
|
14
|
+
@name = name.to_s
|
|
15
|
+
to_tcl
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def arguments
|
|
19
|
+
list_to_array(_!(:info, :args, name)).map do |argument_name|
|
|
20
|
+
variable_name = "__Tcl_Proc_arguments_#{name}_#{argument_name}"
|
|
21
|
+
if _!(:info, :default, name, argument_name, variable_name) == '0'
|
|
22
|
+
argument_name
|
|
23
|
+
else
|
|
24
|
+
_(argument_name, var(variable_name).value)
|
|
25
|
+
end
|
|
26
|
+
ensure
|
|
27
|
+
_!(:unset, variable_name)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def body
|
|
32
|
+
_!(:info, :body, name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def call(*args)
|
|
36
|
+
_!(name, *args.map(&:to_s))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_tcl
|
|
40
|
+
_(:proc, name, _(*arguments), body)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def builtin?
|
|
44
|
+
# TODO: should also check to see if the definition has changed
|
|
45
|
+
BUILTINS.include?(name)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/tcl/utils.rb
ADDED
data/lib/tcl/var.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Tcl
|
|
2
|
+
class Var
|
|
3
|
+
BUILTINS = %w[
|
|
4
|
+
auto_index auto_oldpath auto_path env errorCode errorInfo
|
|
5
|
+
tcl_libPath tcl_library tcl_patchLevel tcl_pkgPath tcl_platform tcl_version
|
|
6
|
+
].freeze
|
|
7
|
+
|
|
8
|
+
include Utils
|
|
9
|
+
include Delegator
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def find(interp, name)
|
|
13
|
+
if interp._!(:array, :exists, name) == '1'
|
|
14
|
+
ArrayVar.new(interp, name)
|
|
15
|
+
elsif interp._!(:info, :exists, name) == '1'
|
|
16
|
+
StringVar.new(interp, name)
|
|
17
|
+
else
|
|
18
|
+
raise Tcl::Error, "can't read \"#{name}\": no such variable"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :name
|
|
24
|
+
|
|
25
|
+
def initialize(interp, name)
|
|
26
|
+
@interp = interp
|
|
27
|
+
@name = name.to_s
|
|
28
|
+
to_tcl
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def builtin?
|
|
32
|
+
BUILTINS.include?(name)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class StringVar < Var
|
|
37
|
+
def value
|
|
38
|
+
_!(:set, name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_tcl
|
|
42
|
+
_(:set, name, value)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class ArrayVar < Var
|
|
47
|
+
def value
|
|
48
|
+
_!(:array, :get, name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_tcl
|
|
52
|
+
_(:array, :set, name, value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_a
|
|
56
|
+
value.split.each_slice(2).to_a
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_h
|
|
60
|
+
to_a.to_h
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/tcl/version.rb
ADDED
data/lib/tcl.rb
ADDED
data/tcl.gemspec
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require_relative 'lib/tcl/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = 'yet-another-ruby-tcl'
|
|
5
|
+
s.version = Tcl::VERSION
|
|
6
|
+
s.platform = Gem::Platform::RUBY
|
|
7
|
+
s.authors = ['Sam Stephenson', 'Mark J. Titorenko', 'Pedro Miranda']
|
|
8
|
+
s.email = 'pedro.at.miranda@gmail.com'
|
|
9
|
+
s.homepage = 'http://github.com/p3t3ru5/yet-another-ruby-tcl'
|
|
10
|
+
s.metadata['homepage_uri'] = s.homepage
|
|
11
|
+
s.metadata['source_code_uri'] = s.homepage
|
|
12
|
+
s.metadata['rubygems_mfa_required'] = 'true'
|
|
13
|
+
s.summary = 'A minimal Ruby interface to libtcl.'
|
|
14
|
+
s.description = 'Bindings to the Tcl interpreter for use from within ruby.'
|
|
15
|
+
s.required_ruby_version = '>= 3.0.0'
|
|
16
|
+
s.extra_rdoc_files = %w[LICENSE README.md]
|
|
17
|
+
s.extensions = ['ext/tcl_ext/extconf.rb']
|
|
18
|
+
|
|
19
|
+
s.files = `git ls-files`.split("\n")
|
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
21
|
+
s.require_paths = ['lib']
|
|
22
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_helper'
|
|
4
|
+
|
|
5
|
+
class InterpWithNoReceiveMethod < Tcl::Interp
|
|
6
|
+
undef_method :interp_receive
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class InterpWithDefaultReceiveMethod < Tcl::Interp
|
|
10
|
+
def tcl_no_arguments
|
|
11
|
+
'hello'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def tcl_one_argument(arg)
|
|
15
|
+
arg
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tcl_variable_arguments(*args)
|
|
19
|
+
_(*args)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tcl_multiply_by5(num)
|
|
23
|
+
num.to_i * 5
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class InterpWithCustomReceiveMethod < Tcl::Interp
|
|
28
|
+
def interp_receive(method, *args)
|
|
29
|
+
_(method, *args)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class InterpWithExposedMethods < Tcl::Interp
|
|
34
|
+
def initialize
|
|
35
|
+
super
|
|
36
|
+
expose :hello
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def tcl_hello(who)
|
|
40
|
+
"hello, #{who}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class InterpWithExitMethod < Tcl::Interp
|
|
45
|
+
def tcl_exit
|
|
46
|
+
exit
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class InterpReceiveTest < Test::Unit::TestCase
|
|
51
|
+
def setup
|
|
52
|
+
@interp = InterpWithDefaultReceiveMethod.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_interp_send_on_interp_with_no_interp_receive_method_should_raise
|
|
56
|
+
@interp = InterpWithDefaultReceiveMethod.new
|
|
57
|
+
assert_raises(Tcl::Error) { @interp.eval('interp_send') }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_interp_send_with_no_arguments_should_raise
|
|
61
|
+
assert_raises(Tcl::Error) { @interp.eval('interp_send') }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_interp_send_returns_tcl_ok
|
|
65
|
+
assert_equal '0', @interp.eval('catch {interp_send no_arguments}')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_interp_send_to_method_with_no_arguments
|
|
69
|
+
assert_equal 'hello', @interp.eval('interp_send no_arguments')
|
|
70
|
+
assert_raises(Tcl::Error) { @interp.eval('interp_send no_arguments foo') }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_interp_send_to_method_with_one_argument
|
|
74
|
+
assert_raises(Tcl::Error) { @interp.eval('interp_send one_argument') }
|
|
75
|
+
assert_equal 'foo', @interp.eval('interp_send one_argument foo')
|
|
76
|
+
assert_raises(Tcl::Error) { @interp.eval('interp_send one_argument foo bar') }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_interp_send_to_method_with_variable_arguments
|
|
80
|
+
assert_equal '', @interp.eval('interp_send variable_arguments')
|
|
81
|
+
assert_equal 'foo', @interp.eval('interp_send variable_arguments foo')
|
|
82
|
+
assert_equal 'foo bar', @interp.eval('interp_send variable_arguments foo bar')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_interp_send_converts_non_string_results_to_string
|
|
86
|
+
assert_equal '0', @interp.eval('interp_send multiply_by5 0')
|
|
87
|
+
assert_equal '25', @interp.eval('interp_send multiply_by5 5')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_interp_send_with_custom_interp_receive_method
|
|
91
|
+
@interp = InterpWithCustomReceiveMethod.new
|
|
92
|
+
assert_raises(Tcl::Error) { @interp.eval('interp_send') }
|
|
93
|
+
assert_equal 'foo', @interp.eval('interp_send foo')
|
|
94
|
+
assert_equal 'foo bar', @interp.eval('interp_send foo bar')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_interp_expose
|
|
98
|
+
@interp = InterpWithExposedMethods.new
|
|
99
|
+
assert_equal 'hello, Sam', @interp.eval('interp_send hello Sam')
|
|
100
|
+
assert_equal 'hello, Sam', @interp.eval('hello Sam')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_interp_send_does_not_convert_system_exit_into_tcl_error
|
|
104
|
+
@interp = InterpWithExitMethod.new
|
|
105
|
+
assert_raises(SystemExit) { @interp.eval('interp_send exit') }
|
|
106
|
+
end
|
|
107
|
+
end
|
data/test/interp_test.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_helper'
|
|
4
|
+
|
|
5
|
+
warn Tcl::Interp.inspect
|
|
6
|
+
|
|
7
|
+
class InterpTest < Test::Unit::TestCase
|
|
8
|
+
def setup
|
|
9
|
+
@interp = Tcl::Interp.new
|
|
10
|
+
@interp.eval('rename clock ""') if @interp.procs.include?('clock')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_load_from_file
|
|
14
|
+
vars = @interp.vars
|
|
15
|
+
procs = @interp.procs
|
|
16
|
+
@interp = Tcl::Interp.load_from_file(path_to_fixture('test.tcl'))
|
|
17
|
+
@interp.eval('rename clock ""') if @interp.procs.include?('clock')
|
|
18
|
+
|
|
19
|
+
assert_equal %w[a b], (@interp.vars - vars).sort
|
|
20
|
+
assert_equal %w[c d e], (@interp.procs - procs).sort
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_eval
|
|
24
|
+
assert_equal '', @interp.eval('')
|
|
25
|
+
assert_equal '0', @interp.eval('return 0')
|
|
26
|
+
assert_equal '', @interp.eval('return ""')
|
|
27
|
+
assert_equal '', @interp.eval('return {}')
|
|
28
|
+
assert_equal ' ', @interp.eval('return " "')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_eval_raises_on_tcl_exception
|
|
32
|
+
assert_raises(Tcl::Error) { @interp.eval('nonexistent') }
|
|
33
|
+
assert_raises(Tcl::Error) { @interp.eval('{') }
|
|
34
|
+
assert_raises(Tcl::Error) { @interp.eval('error') }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_eval_with_timeout_argument
|
|
38
|
+
return unless defined?(Tcl::Timeout)
|
|
39
|
+
|
|
40
|
+
assert_raises(Tcl::Timeout) { @interp.eval('while 1 {}', 100) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_array_to_list
|
|
44
|
+
assert_equal '', @interp.array_to_list([])
|
|
45
|
+
assert_equal '{}', @interp.array_to_list([nil])
|
|
46
|
+
assert_equal '{}', @interp.array_to_list([''])
|
|
47
|
+
assert_equal 'one', @interp.array_to_list(['one'])
|
|
48
|
+
assert_equal 'one two', @interp.array_to_list(%w[one two])
|
|
49
|
+
assert_equal 'a { b} c', @interp.array_to_list(['a', ' b', 'c'])
|
|
50
|
+
assert_equal '\\{', @interp.array_to_list(['{'])
|
|
51
|
+
assert_equal '{"}', @interp.array_to_list(['"'])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_list_to_array_empty
|
|
55
|
+
assert_equal [], @interp.list_to_array('')
|
|
56
|
+
assert_equal [''], @interp.list_to_array('{}')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_list_to_array_simple_strings
|
|
60
|
+
assert_equal ['one'], @interp.list_to_array('one')
|
|
61
|
+
assert_equal %w[one two], @interp.list_to_array('one two')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_list_to_array_escaped_spaces
|
|
65
|
+
assert_equal ['a', ' b', 'c'], @interp.list_to_array('a { b} c')
|
|
66
|
+
assert_equal ['a', ' b', 'c'], @interp.list_to_array('a \\ b c')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_list_to_array_escaped_characters
|
|
70
|
+
assert_equal ['{'], @interp.list_to_array('\\{')
|
|
71
|
+
assert_equal ['['], @interp.list_to_array('\\[')
|
|
72
|
+
assert_equal ['"'], @interp.list_to_array('\"')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_procs
|
|
76
|
+
@interp.clear!
|
|
77
|
+
assert_equal [], @interp.procs
|
|
78
|
+
@interp.eval 'proc foo {} {}'
|
|
79
|
+
assert_equal ['foo'], @interp.procs
|
|
80
|
+
@interp.eval 'proc bar {} {}'
|
|
81
|
+
assert_equal %w[bar foo], @interp.procs.sort
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_vars
|
|
85
|
+
@interp.clear!
|
|
86
|
+
assert_equal [], @interp.vars
|
|
87
|
+
@interp.eval 'set a 0'
|
|
88
|
+
assert_equal ['a'], @interp.vars
|
|
89
|
+
@interp.eval 'set b(a) 0'
|
|
90
|
+
assert_equal %w[a b], @interp.vars.sort
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_proc
|
|
94
|
+
assert_raises(Tcl::Error) { @interp.proc('foo') }
|
|
95
|
+
@interp.eval 'proc foo {} {}'
|
|
96
|
+
proc = @interp.proc('foo')
|
|
97
|
+
assert proc.is_a?(Tcl::Proc)
|
|
98
|
+
assert_equal 'foo', proc.name
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_var
|
|
102
|
+
assert_raises(Tcl::Error) { @interp.var('foo') }
|
|
103
|
+
@interp.eval 'set foo bar'
|
|
104
|
+
var = @interp.var('foo')
|
|
105
|
+
assert var.is_a?(Tcl::Var)
|
|
106
|
+
assert_equal 'foo', var.name
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_to_tcl
|
|
110
|
+
@interp.eval File.read(path_to_fixture('test.tcl'))
|
|
111
|
+
assert_equal <<~TCL.chomp, @interp.to_tcl
|
|
112
|
+
set a 0
|
|
113
|
+
array set b {a 1 b 2}
|
|
114
|
+
proc c args return
|
|
115
|
+
proc d {a {b 0}} {return $b}
|
|
116
|
+
proc e {} {}
|
|
117
|
+
TCL
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_interp_helper_method_missing_super_passthrough
|
|
121
|
+
assert_raises(NoMethodError) { @interp.nonexistent }
|
|
122
|
+
end
|
|
123
|
+
end
|
data/test/proc_test.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative 'test_helper'
|
|
2
|
+
|
|
3
|
+
class ProcTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@interp = Tcl::Interp.load_from_file(path_to_fixture('test.tcl'))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_proc_arguments_for_proc_with_no_arguments
|
|
9
|
+
assert_equal [], @interp.proc('e').arguments
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_proc_arguments_for_proc_with_one_argument
|
|
13
|
+
assert_equal ['args'], @interp.proc('c').arguments
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_proc_arguments_for_proc_with_default_argument
|
|
17
|
+
assert_equal ['a', 'b 0'], @interp.proc('d').arguments
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_proc_body
|
|
21
|
+
assert_equal 'return', @interp.proc('c').body
|
|
22
|
+
assert_equal 'return $b', @interp.proc('d').body
|
|
23
|
+
assert_equal '', @interp.proc('e').body
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_proc_call
|
|
27
|
+
assert_equal '', @interp.proc('c').call
|
|
28
|
+
assert_equal '0', @interp.proc('d').call('a')
|
|
29
|
+
assert_equal 'b', @interp.proc('d').call('a', 'b')
|
|
30
|
+
assert_equal '', @interp.proc('e').call
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_proc_call_raises_on_missing_argument
|
|
34
|
+
assert_raises(Tcl::Error) { @interp.proc('d').call }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_proc_to_tcl
|
|
38
|
+
assert_equal 'proc c args return', @interp.proc('c').to_tcl
|
|
39
|
+
assert_equal 'proc d {a {b 0}} {return $b}', @interp.proc('d').to_tcl
|
|
40
|
+
assert_equal 'proc e {} {}', @interp.proc('e').to_tcl
|
|
41
|
+
end
|
|
42
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require_relative '../lib/tcl'
|
|
3
|
+
|
|
4
|
+
module Tcl
|
|
5
|
+
class Interp
|
|
6
|
+
def clear!
|
|
7
|
+
procs.each { |p| _! :rename, p, '' }
|
|
8
|
+
vars.each { |v| _! :unset, v }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Test
|
|
14
|
+
module Unit
|
|
15
|
+
class TestCase
|
|
16
|
+
def path_to_fixture(*path_pieces)
|
|
17
|
+
File.join(File.dirname(__FILE__), 'fixtures', *path_pieces)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/test/var_test.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative 'test_helper'
|
|
2
|
+
|
|
3
|
+
class VarTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@interp = Tcl::Interp.load_from_file(path_to_fixture('test.tcl'))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_var_find_raises_when_var_does_not_exist
|
|
9
|
+
assert_raises(Tcl::Error) { Tcl::Var.find(@interp, 'nonexistent') }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_var_find_returns_string_var
|
|
13
|
+
var = Tcl::Var.find(@interp, 'a')
|
|
14
|
+
assert_equal 'a', var.name
|
|
15
|
+
assert var.is_a?(Tcl::StringVar)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_var_find_returns_array_var
|
|
19
|
+
var = Tcl::Var.find(@interp, 'b')
|
|
20
|
+
assert_equal 'b', var.name
|
|
21
|
+
assert var.is_a?(Tcl::ArrayVar)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_string_var_value
|
|
25
|
+
assert_equal '0', @interp.var('a').value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_array_var_value
|
|
29
|
+
assert_equal 'a 1 b 2', @interp.var('b').value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_string_var_to_tcl
|
|
33
|
+
assert_equal 'set a 0', @interp.var('a').to_tcl
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_array_var_to_tcl
|
|
37
|
+
assert_equal 'array set b {a 1 b 2}', @interp.var('b').to_tcl
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_array_var_to_tcl_does_not_modify_error_info
|
|
41
|
+
assert_errorinfo ''
|
|
42
|
+
Tcl::Var.find(@interp, 'b')
|
|
43
|
+
assert_errorinfo ''
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_attempting_to_find_nonexistent_variable_does_not_modify_error_info
|
|
47
|
+
assert_errorinfo ''
|
|
48
|
+
assert_raises(Tcl::Error) { Tcl::Var.find(@interp, 'nonexistent') }
|
|
49
|
+
assert_errorinfo ''
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
protected
|
|
53
|
+
|
|
54
|
+
def assert_errorinfo(value)
|
|
55
|
+
return unless @interp.vars.include?('errorInfo')
|
|
56
|
+
|
|
57
|
+
assert_equal value, @interp.var('errorInfo').value
|
|
58
|
+
end
|
|
59
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: yet-another-ruby-tcl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sam Stephenson
|
|
8
|
+
- Mark J. Titorenko
|
|
9
|
+
- Pedro Miranda
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Bindings to the Tcl interpreter for use from within ruby.
|
|
15
|
+
email: pedro.at.miranda@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions:
|
|
18
|
+
- ext/tcl_ext/extconf.rb
|
|
19
|
+
extra_rdoc_files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
files:
|
|
23
|
+
- ".document"
|
|
24
|
+
- ".gitignore"
|
|
25
|
+
- ".rubocop.yml"
|
|
26
|
+
- Gemfile
|
|
27
|
+
- LICENSE
|
|
28
|
+
- README.md
|
|
29
|
+
- Rakefile
|
|
30
|
+
- ext/tcl_ext/extconf.rb
|
|
31
|
+
- ext/tcl_ext/tcl.c
|
|
32
|
+
- lib/tcl.rb
|
|
33
|
+
- lib/tcl/delegator.rb
|
|
34
|
+
- lib/tcl/interp.rb
|
|
35
|
+
- lib/tcl/proc.rb
|
|
36
|
+
- lib/tcl/utils.rb
|
|
37
|
+
- lib/tcl/var.rb
|
|
38
|
+
- lib/tcl/version.rb
|
|
39
|
+
- tcl.gemspec
|
|
40
|
+
- test/fixtures/test.tcl
|
|
41
|
+
- test/interp_receive_test.rb
|
|
42
|
+
- test/interp_test.rb
|
|
43
|
+
- test/proc_test.rb
|
|
44
|
+
- test/test_helper.rb
|
|
45
|
+
- test/var_test.rb
|
|
46
|
+
homepage: http://github.com/p3t3ru5/yet-another-ruby-tcl
|
|
47
|
+
licenses: []
|
|
48
|
+
metadata:
|
|
49
|
+
homepage_uri: http://github.com/p3t3ru5/yet-another-ruby-tcl
|
|
50
|
+
source_code_uri: http://github.com/p3t3ru5/yet-another-ruby-tcl
|
|
51
|
+
rubygems_mfa_required: 'true'
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 3.0.0
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 4.0.15
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: A minimal Ruby interface to libtcl.
|
|
69
|
+
test_files: []
|