gvars 0.9.8 → 0.9.10

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -14
  3. data/ext/gvars/gvars.c +72 -24
  4. data/lib/gvars/version.rb +1 -1
  5. data/tmp.rb +34 -0
  6. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 837ffc167909e2abe16ff54fdcd7bfbe4cb6dc0346f1c3e9037b17933f3b89a7
4
- data.tar.gz: 9e36c6847244b578e63032468c2d94e7fff4f4e05c68cd3a1c126fc257a02b7c
3
+ metadata.gz: 6230a213eea87d923eaf4e8196bb168d3d52342023fda70adf189b336a4fea36
4
+ data.tar.gz: ba9b1449a5fe00221ebb389e903bf19d10d29f264351d60e7f6369318190582b
5
5
  SHA512:
6
- metadata.gz: 7e71e7a19371437156d14f1831045a6229b4fcd055cc82680309266c5ed20781ec030b78e591e38e4a7adb62633ceb98d5284ca69b8e40eb475ee4ea829ee269
7
- data.tar.gz: f5cba137bad5f8d9db885f468df9bc147f89b7c0106653a409fd8aa5d32767a8fceba372b4a4c778bdf4161ae59c215dd2081cff250aeccfcf4c2c161658c1a9
6
+ metadata.gz: d0fe558f8ee687fb1c02429c52a1ccc0745c1f33c04f521dbdce03b987ab44910c274726a48f2283b01f13973489e3d71c9ceacaa93ff1f9aa3d570403bd75fd
7
+ data.tar.gz: e39330d0bdb1800bf58d45877da5af404f1f599e4f7f77ad8f5f02ac715da2ec97f7cce83c7b13da5cb9d70d17ccab047fb395bea894a05b200944f77bbb0d2f
data/README.md CHANGED
@@ -5,29 +5,57 @@ Adds in missing utilities relating to global variables in Ruby.
5
5
  Ever been bothered how there's `instance_variable_get`, `binding.local_variable_get`, `class_variable_get`, even `const_get`, but no `global_variable_get`? Ever resorted to `eval("$#{name} = value")` to assign a global variable? Look no further!
6
6
 
7
7
  ## Features
8
- ```ruby
9
- $foo = 34
10
-
11
- # Dynamically lookup globals
12
- puts GVars.get(:$foo) #=> 34
13
8
 
14
- # Dynamically assign globals!
9
+ `GVars` adds support for getting, setting, and aliasing global variables dynamically. (Checking for definition is coming!)
10
+ ```ruby
11
+ # Dynamically interact with globals:
15
12
  GVars.set(:$foo, 99)
16
- puts $foo #=> 99
13
+ puts GVars.get(:$foo) #=> 99
17
14
 
18
- # How about dynamically `alias`ing globals?
19
- GBars.alias :$bar, :$foo
15
+ GVars.alias(:$bar, :$foo)
20
16
  $bar = 12
21
17
  puts $foo #=> 12
22
18
 
23
- # You can also mixin `GVars` to get the methods that should be
24
- # defined on Kernel:
25
- include GVars
19
+ # Mixin `GVars` to get methods that should've been defined
20
+ # on `Kernel` all along
21
+ Kernel.include GVars
26
22
  puts global_variable_get(:$bar) #=> 12
27
23
  puts global_variable_set(:$bar, 19) #=> 19
28
24
  ```
29
25
 
30
- Up next: virtual variables, and collection methods!
26
+ It also supports virtual variables, which evaluate a `proc` each time:
27
+ ```ruby
28
+ # Get the current time whenever you access `$TIME`
29
+ GVars.virtual(:$TIME) { Time.now }
30
+ puts $TIME.monday? #=> true
31
+
32
+ # You can also provide a custom setter
33
+ GVars.virtual(:$PWD,
34
+ getter: ->{ Dir.pwd },
35
+ setter: ->new{ Dir.chdir(new) })
36
+ $PWD = '/tmp'
37
+ p $PWD #=> "/private/tmp" (macOS symlinks `/tmp` to `/private/tmp`)
38
+ ```
39
+
40
+ And, hooked variables:
41
+ ```ruby
42
+ # Initial values compute their "backing" value based on the getter's
43
+ # return value.
44
+ GVars.hooked(:$COUNTER, initial: 0) { it + 1 }
45
+ p [$COUNTER, $COUNTER, $COUNTER] #=> [1, 2, 3]
46
+
47
+ # If you don't supply a setter, it defaults to just the backing value.
48
+ $COUNTER = 10
49
+ p $COUNTER #=> 11
50
+
51
+ # You can also specify a state, which is given to the getter (and setter),
52
+ # but isn't updated by the getter's return value.
53
+ GVars.hooked(:$RAND,
54
+ state: Random.new,
55
+ getter: ->random{ random.rand },
56
+ setter: ->(value,random){ random.srand(value) }) # doesn't actually work.. `Random#srand` isn't a thing
57
+ ```
31
58
 
32
59
  ## Known problems
33
- Unfortunately, Ruby's C-level `rb_gv_get` / `rb_gv_set` methods only let you manipulate ASCII identifiers... A fix _may_ be possible, but it'll have to resort to the `eval` hack.
60
+ 1. Unfortunately, Ruby's C-level `rb_gv_get` / `rb_gv_set` methods only let you manipulate ASCII identifiers... A fix _may_ be possible, but it'll have to resort to the `eval` hack.
61
+ 2. Because `$_`, and regex variables (`$~`, ``$` ``, etc) are all local to the function they're called within, you can't actually access them from within procs; As a workaround, you can pass a string that'll be `eval`d each time, but it's not terribly efficient. I don't know if there's a better solution
data/ext/gvars/gvars.c CHANGED
@@ -141,12 +141,33 @@ static const rb_data_type_t gvars_type = {
141
141
  static VALUE gvars_virtual_var_getter(ID id, VALUE *data) {
142
142
  struct gvars_virtual_var *gv;
143
143
  TypedData_Get_Struct(*data, struct gvars_virtual_var, &gvars_type, gv);
144
+
145
+ // If it's a string, eval it in the current context. Not great for ZJIT...
146
+ if (RB_TYPE_P(gv->getter, T_STRING)) {
147
+ return rb_funcallv(rb_binding_new(), rb_intern("eval"), 1, &gv->getter);
148
+ }
149
+
144
150
  return rb_proc_call(gv->getter, rb_ary_new());
145
151
  }
146
152
 
147
153
  static void gvars_virtual_var_setter(VALUE val, ID id, VALUE *data) {
148
154
  struct gvars_virtual_var *gv;
149
155
  TypedData_Get_Struct(*data, struct gvars_virtual_var, &gvars_type, gv);
156
+
157
+ // If it's a string, eval it in the current context. Not great for ZJIT...
158
+ if (RB_TYPE_P(gv->setter, T_STRING)) {
159
+ static VALUE virtual_setter = Qundef;
160
+ // Don't actually register the variable until we're doing a virtual
161
+ // string setter.
162
+ if (virtual_setter == Qundef) {
163
+ rb_define_variable("$__value__", &virtual_setter);
164
+ }
165
+ virtual_setter = val;
166
+
167
+ rb_funcallv(rb_binding_new(), rb_intern("eval"), 1, &gv->setter);
168
+ return;
169
+ }
170
+
150
171
  rb_proc_call(gv->setter, rb_ary_new_from_args(1, val));
151
172
  }
152
173
 
@@ -227,15 +248,24 @@ gvars_define_virtual_method(VALUE self, VALUE *name, VALUE backing, VALUE getter
227
248
  }
228
249
 
229
250
  enum virtual_method_keywords {
251
+ VIRTUAL_METHOD_KEYWORDS_GETTER,
252
+ VIRTUAL_METHOD_KEYWORDS_SETTER,
253
+ VIRTUAL_METHOD_KEYWORDS_READONLY,
230
254
  VIRTUAL_METHOD_KEYWORDS_INITIAL,
231
255
  VIRTUAL_METHOD_KEYWORDS_STATE,
232
- VIRTUAL_METHOD_KEYWORDS_READONLY,
233
- LENGTH_OF_VIRTUAL_METHOD_KEYWORDS,
256
+ LENGTH_OF_VIRTUAL_METHOD_KEYWORDS_HOOKED = VIRTUAL_METHOD_KEYWORDS_STATE+1,
257
+ LENGTH_OF_VIRTUAL_METHOD_KEYWORDS_VIRTUAL = VIRTUAL_METHOD_KEYWORDS_READONLY+1,
234
258
  };
235
259
 
236
- static ID virtual_method_keyword_ids[LENGTH_OF_VIRTUAL_METHOD_KEYWORDS];
260
+ static ID virtual_method_keyword_ids[LENGTH_OF_VIRTUAL_METHOD_KEYWORDS_HOOKED];
261
+
262
+ static VALUE convert_to_proc(const char *name, VALUE input, bool is_hooked) {
263
+ // Convert getter to a proc, unless it's a string (to support the "always eval" case)
264
+ if (RB_TYPE_P(input, T_STRING) && !is_hooked) {
265
+ // Virtual functions can be strings, which are evaluated.
266
+ return input;
267
+ }
237
268
 
238
- static VALUE convert_to_proc(const char *name, VALUE input) {
239
269
  VALUE proc = rb_convert_type(input, T_DATA, "Proc", "to_proc");
240
270
 
241
271
  if (NIL_P(proc) || !rb_obj_is_proc(proc)) {
@@ -246,43 +276,45 @@ static VALUE convert_to_proc(const char *name, VALUE input) {
246
276
  }
247
277
 
248
278
  static VALUE
249
- gvars_f_define_virtual_method(int argc, VALUE *argv, VALUE self)
279
+ gvars_f_define_virtual_method_foo(int argc, VALUE *argv, VALUE self, bool is_hooked)
250
280
  {
251
- VALUE name, backing, getter, setter, opts_hash, opts[LENGTH_OF_VIRTUAL_METHOD_KEYWORDS];
281
+ VALUE name, backing, getter, setter, opts_hash, opts[LENGTH_OF_VIRTUAL_METHOD_KEYWORDS_HOOKED] = {
282
+ Qundef, Qundef, Qundef, Qundef, Qundef };
252
283
 
253
- switch (rb_scan_args(argc, argv, "12:", &name, &getter, &setter, &opts_hash)) {
254
- case 1:
255
- getter = (rb_need_block(), rb_block_proc());
256
- setter = Qnil;
257
- break;
284
+ rb_scan_args(argc, argv, "10:", &name, &opts_hash);
285
+ rb_get_kwargs(opts_hash, virtual_method_keyword_ids, 0,
286
+ (is_hooked ? LENGTH_OF_VIRTUAL_METHOD_KEYWORDS_HOOKED : LENGTH_OF_VIRTUAL_METHOD_KEYWORDS_VIRTUAL),
287
+ opts);
258
288
 
259
- case 2:
260
- setter = Qnil;
289
+ getter = opts[VIRTUAL_METHOD_KEYWORDS_GETTER];
290
+ setter = opts[VIRTUAL_METHOD_KEYWORDS_SETTER];
261
291
 
262
- case 3:
263
- if (rb_block_given_p()) {
264
- rb_warn("given block not used");
265
- }
266
- break;
267
- default:
268
- rb_bug("oops");
292
+ if (getter == Qundef) {
293
+ getter = (rb_need_block(), rb_block_proc());
294
+ } else {
295
+ if (rb_block_given_p()) rb_warn("block parameter unused when getter is supplied");
296
+ getter = convert_to_proc("getter", getter, is_hooked);
269
297
  }
270
298
 
271
- getter = convert_to_proc("getter", getter);
272
- if (!NIL_P(setter)) setter = convert_to_proc("setter", setter);
299
+ if (setter == Qundef) {
300
+ setter = Qnil;
301
+ } else {
302
+ setter = convert_to_proc("setter", setter, is_hooked);
303
+ }
273
304
 
274
305
  enum virtual_kind kind = GVARS_VIRTUAL_KIND_VIRTUAL;
275
- rb_get_kwargs(opts_hash, virtual_method_keyword_ids, 0, LENGTH_OF_VIRTUAL_METHOD_KEYWORDS, opts);
276
306
 
277
307
  if (opts[VIRTUAL_METHOD_KEYWORDS_INITIAL] != Qundef) {
278
308
  if (opts[VIRTUAL_METHOD_KEYWORDS_STATE] != Qundef) {
279
- rb_raise(rb_eArgError, "only one of 'initial' or 'state' may be supplied");
309
+ rb_raise(rb_eArgError, "exactly one of 'initial' or 'state' may be supplied");
280
310
  }
281
311
  backing = opts[VIRTUAL_METHOD_KEYWORDS_INITIAL];
282
312
  kind = GVARS_VIRTUAL_KIND_INITIAL;
283
313
  } else if (opts[VIRTUAL_METHOD_KEYWORDS_STATE] != Qundef) {
284
314
  backing = opts[VIRTUAL_METHOD_KEYWORDS_STATE];
285
315
  kind = GVARS_VIRTUAL_KIND_STATE;
316
+ } else if (is_hooked) {
317
+ rb_raise(rb_eArgError, "exactly one of 'initial' or 'state' may be supplied");
286
318
  } else {
287
319
  backing = Qnil;
288
320
  }
@@ -301,6 +333,18 @@ gvars_f_define_virtual_method(int argc, VALUE *argv, VALUE self)
301
333
  gvars_define_virtual_method(self, &name, backing, getter, setter, kind, is_readonly);
302
334
 
303
335
  return name; //TODO: ID2SYM(id)
336
+
337
+ }
338
+ static VALUE
339
+ gvars_f_define_virtual_method(int argc, VALUE *argv, VALUE self)
340
+ {
341
+ return gvars_f_define_virtual_method_foo(argc, argv, self, false);
342
+ }
343
+
344
+ static VALUE
345
+ gvars_f_define_hooked_method(int argc, VALUE *argv, VALUE self)
346
+ {
347
+ return gvars_f_define_virtual_method_foo(argc, argv, self, true);
304
348
  }
305
349
 
306
350
  void
@@ -309,6 +353,8 @@ Init_gvars(void)
309
353
  gvars_module = rb_define_module("GVars");
310
354
  rb_ivar_set(gvars_module, rb_intern("vars"), rb_hash_new());
311
355
 
356
+ virtual_method_keyword_ids[VIRTUAL_METHOD_KEYWORDS_GETTER] = rb_intern("getter");
357
+ virtual_method_keyword_ids[VIRTUAL_METHOD_KEYWORDS_SETTER] = rb_intern("setter");
312
358
  virtual_method_keyword_ids[VIRTUAL_METHOD_KEYWORDS_INITIAL] = rb_intern("initial");
313
359
  virtual_method_keyword_ids[VIRTUAL_METHOD_KEYWORDS_STATE] = rb_intern("state");
314
360
  virtual_method_keyword_ids[VIRTUAL_METHOD_KEYWORDS_READONLY] = rb_intern("readonly");
@@ -323,6 +369,7 @@ Init_gvars(void)
323
369
 
324
370
  // Don't make it an instance method, cause we don't want it included
325
371
  rb_define_singleton_method(gvars_module, "define_virtual_method", gvars_f_define_virtual_method, -1);
372
+ rb_define_singleton_method(gvars_module, "define_hooked_method", gvars_f_define_hooked_method, -1);
326
373
 
327
374
  // Enumerable methods
328
375
  rb_extend_object(gvars_module, rb_mEnumerable);
@@ -339,4 +386,5 @@ Init_gvars(void)
339
386
  rb_define_alias(gvars_singleton, "alias", "alias_global_variable");
340
387
  rb_define_alias(gvars_singleton, "list", "global_variables");
341
388
  rb_define_alias(gvars_singleton, "virtual", "define_virtual_method");
389
+ rb_define_alias(gvars_singleton, "hooked", "define_hooked_method");
342
390
  }
data/lib/gvars/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GVars
4
- VERSION = '0.9.8'
4
+ VERSION = '0.9.10'
5
5
  end
data/tmp.rb CHANGED
@@ -2,6 +2,40 @@ require 'gvars'
2
2
  p GVars::VERSION
3
3
 
4
4
  require 'optparse'
5
+ trace_var :$timeout do p it end
6
+
7
+ GVars.virtual(:$hello,
8
+ getter: '[Regexp.last_match[1], $~]',
9
+ setter: 'puts("assigned: #{$__value__}")')
10
+
11
+ def foo
12
+ /(a)/ =~ 'heallo'
13
+ p $hello
14
+ $hello = 9
15
+ end
16
+
17
+ foo
18
+ ["a", #<MatchData "a" 1:"a">]
19
+ assigned: 9
20
+
21
+
22
+ __END__
23
+ GVars.virtual(:$timeout,
24
+ getter: ->{ 'hello' },
25
+ setter: ->val { puts "setting: #{val}" },
26
+ readonly: false)
27
+
28
+ GVars.hooked(:$COUNTER,
29
+ getter: :succ.to_proc,
30
+ setter: nil)
31
+ setter: ->val { puts "setting: #{val}" },
32
+ initial: 10)
33
+
34
+ p $timeout1
35
+ p $timeout1
36
+
37
+ __END__
38
+
5
39
  OptParse.new do |op|
6
40
  op.on '--timeout=X', 'the timeout'
7
41
  op.parse %w[--timeout 34.5], into: GVars
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gvars
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.8
4
+ version: 0.9.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Westerman