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 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
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ # rcov generated
5
+ coverage
6
+ lib/tcl_ext.so
7
+ ext/tcl_ext/mkmf.log
8
+ ext/tcl_ext/Makefile
9
+
10
+ # rdoc generated
11
+ rdoc
12
+
13
+ # yard generated
14
+ doc
15
+ .yardoc
16
+
17
+ tmp
18
+ pkg
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ plugins:
2
+ - rubocop-rake
3
+
4
+ AllCops:
5
+ NewCops: enable
6
+
7
+ Style/FrozenStringLiteralComment:
8
+ Enabled: false
9
+
10
+ # to be removed if/when done
11
+ Style/Documentation:
12
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tcl.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'bundler'
8
+ gem 'irb'
9
+ gem 'rake'
10
+ gem 'rake-compiler'
11
+ gem 'rubocop'
12
+ gem 'rubocop-rake'
13
+ gem 'test-unit'
14
+ end
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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tcl
4
+ module Utils
5
+ def _(*args)
6
+ interp.array_to_list(args)
7
+ end
8
+
9
+ def _!(*args)
10
+ interp.eval(_(*args))
11
+ end
12
+ end
13
+ end
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
@@ -0,0 +1,3 @@
1
+ module Tcl
2
+ VERSION = '0.1.2'.freeze
3
+ end
data/lib/tcl.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'tcl_ext'
2
+
3
+ require_relative 'tcl/utils'
4
+ require_relative 'tcl/delegator'
5
+ require_relative 'tcl/interp'
6
+ require_relative 'tcl/proc'
7
+ require_relative 'tcl/var'
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,6 @@
1
+ set a 0
2
+ set b(a) 1
3
+ set b(b) 2
4
+ proc c args return
5
+ proc d {a {b 0}} {return $b}
6
+ proc e {} {}
@@ -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
@@ -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
@@ -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: []