* enumerator.c: Finalize and document Lazy.new. [Bug #7248]
Add Lazy#to_enum and simplify Lazy#size. * test/ruby/test_lazy_enumerator.rb: tests for above git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@39057 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
5af821c6e9
commit
44cd5f21e9
@ -1,3 +1,10 @@
|
|||||||
|
Tue Feb 5 12:48:10 2013 Marc-Andre Lafortune <ruby-core@marc-andre.ca>
|
||||||
|
|
||||||
|
* enumerator.c: Finalize and document Lazy.new. [Bug #7248]
|
||||||
|
Add Lazy#to_enum and simplify Lazy#size.
|
||||||
|
|
||||||
|
* test/ruby/test_lazy_enumerator.rb: tests for above
|
||||||
|
|
||||||
Tue Feb 5 11:35:35 2013 Eric Hodel <drbrain@segment7.net>
|
Tue Feb 5 11:35:35 2013 Eric Hodel <drbrain@segment7.net>
|
||||||
|
|
||||||
* lib/rubygems/commands/push_command.rb: Fixed credential download for
|
* lib/rubygems/commands/push_command.rb: Fixed credential download for
|
||||||
|
196
enumerator.c
196
enumerator.c
@ -457,6 +457,9 @@ rb_enumeratorize(VALUE obj, VALUE meth, int argc, VALUE *argv)
|
|||||||
return rb_enumeratorize_with_size(obj, meth, argc, argv, 0);
|
return rb_enumeratorize_with_size(obj, meth, argc, argv, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
lazy_to_enum_i(VALUE self, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS));
|
||||||
|
|
||||||
VALUE
|
VALUE
|
||||||
rb_enumeratorize_with_size(VALUE obj, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS))
|
rb_enumeratorize_with_size(VALUE obj, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS))
|
||||||
{
|
{
|
||||||
@ -1022,7 +1025,7 @@ enumerator_size(VALUE obj)
|
|||||||
struct enumerator *e = enumerator_ptr(obj);
|
struct enumerator *e = enumerator_ptr(obj);
|
||||||
|
|
||||||
if (e->size_fn) {
|
if (e->size_fn) {
|
||||||
return (*e->size_fn)(e->obj, e->args);
|
return (*e->size_fn)(e->obj, e->args, obj);
|
||||||
}
|
}
|
||||||
if (rb_obj_is_proc(e->size)) {
|
if (rb_obj_is_proc(e->size)) {
|
||||||
if (e->args)
|
if (e->args)
|
||||||
@ -1271,20 +1274,22 @@ generator_each(int argc, VALUE *argv, VALUE obj)
|
|||||||
|
|
||||||
/* Lazy Enumerator methods */
|
/* Lazy Enumerator methods */
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_receiver_size(VALUE self)
|
enum_size(VALUE self)
|
||||||
{
|
{
|
||||||
VALUE r = rb_check_funcall(rb_ivar_get(self, id_receiver), id_size, 0, 0);
|
VALUE r = rb_check_funcall(self, id_size, 0, 0);
|
||||||
return (r == Qundef) ? Qnil : r;
|
return (r == Qundef) ? Qnil : r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_size(VALUE self)
|
lazy_size(VALUE self)
|
||||||
{
|
{
|
||||||
struct enumerator *e = enumerator_ptr(self);
|
return enum_size(rb_ivar_get(self, id_receiver));
|
||||||
if (e->size_fn) {
|
}
|
||||||
return (*e->size_fn)(self);
|
|
||||||
}
|
static VALUE
|
||||||
return Qnil;
|
lazy_receiver_size(VALUE generator, VALUE args, VALUE lazy)
|
||||||
|
{
|
||||||
|
return lazy_size(lazy);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -1313,15 +1318,6 @@ lazy_init_iterator(VALUE val, VALUE m, int argc, VALUE *argv)
|
|||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
|
||||||
lazy_init_yielder(VALUE val, VALUE m, int argc, VALUE *argv)
|
|
||||||
{
|
|
||||||
VALUE result;
|
|
||||||
result = rb_funcall2(m, id_yield, argc, argv);
|
|
||||||
if (result == Qundef) rb_iter_break();
|
|
||||||
return Qnil;
|
|
||||||
}
|
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_init_block_i(VALUE val, VALUE m, int argc, VALUE *argv)
|
lazy_init_block_i(VALUE val, VALUE m, int argc, VALUE *argv)
|
||||||
{
|
{
|
||||||
@ -1329,39 +1325,51 @@ lazy_init_block_i(VALUE val, VALUE m, int argc, VALUE *argv)
|
|||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
/*
|
||||||
lazy_init_block(VALUE val, VALUE m, int argc, VALUE *argv)
|
* call-seq:
|
||||||
{
|
* Lazy.new(obj, size=nil) { |yielder, *values| ... }
|
||||||
rb_block_call(m, id_each, argc-1, argv+1, lazy_init_yielder, val);
|
*
|
||||||
return Qnil;
|
* Creates a new Lazy enumerator. When the enumerator is actually enumerated
|
||||||
}
|
* (e.g. by calling #force), +obj+ will be enumerated and each value passed
|
||||||
|
* to the given block. The block can yield values back using +yielder+.
|
||||||
|
* For example, to create a method +filter_map+ in both lazy and
|
||||||
|
* non-lazy fashions:
|
||||||
|
*
|
||||||
|
* module Enumerable
|
||||||
|
* def filter_map(&block)
|
||||||
|
* map(&block).compact
|
||||||
|
* end
|
||||||
|
* end
|
||||||
|
*
|
||||||
|
* class Enumerator::Lazy
|
||||||
|
* def filter_map
|
||||||
|
* Lazy.new(self) do |yielder, *values|
|
||||||
|
* result = yield *values
|
||||||
|
* yielder << result if result
|
||||||
|
* end
|
||||||
|
* end
|
||||||
|
* end
|
||||||
|
*
|
||||||
|
* (1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.first(5)
|
||||||
|
* # => [4, 16, 36, 64, 100]
|
||||||
|
*/
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_initialize(int argc, VALUE *argv, VALUE self)
|
lazy_initialize(int argc, VALUE *argv, VALUE self)
|
||||||
{
|
{
|
||||||
VALUE obj, meth;
|
VALUE obj, size = Qnil;
|
||||||
VALUE generator;
|
VALUE generator;
|
||||||
int offset;
|
|
||||||
|
|
||||||
if (argc < 1) {
|
rb_check_arity(argc, 1, 2);
|
||||||
rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..)", argc);
|
if (!rb_block_given_p()) {
|
||||||
|
rb_raise(rb_eArgError, "tried to call lazy new without a block");
|
||||||
}
|
}
|
||||||
else {
|
obj = argv[0];
|
||||||
obj = argv[0];
|
if (argc > 1) {
|
||||||
if (argc == 1) {
|
size = argv[1];
|
||||||
meth = sym_each;
|
|
||||||
offset = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
meth = argv[1];
|
|
||||||
offset = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
generator = generator_allocate(rb_cGenerator);
|
generator = generator_allocate(rb_cGenerator);
|
||||||
rb_block_call(generator, id_initialize, 0, 0,
|
rb_block_call(generator, id_initialize, 0, 0, lazy_init_block_i, obj);
|
||||||
(rb_block_given_p() ? lazy_init_block_i : lazy_init_block),
|
enumerator_init(self, generator, sym_each, 0, 0, 0, size);
|
||||||
obj);
|
|
||||||
enumerator_init(self, generator, meth, argc - offset, argv + offset, lazy_receiver_size, Qnil);
|
|
||||||
rb_ivar_set(self, id_receiver, obj);
|
rb_ivar_set(self, id_receiver, obj);
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
@ -1418,14 +1426,58 @@ lazy_set_method(VALUE lazy, VALUE args, VALUE (*size_fn)(ANYARGS))
|
|||||||
static VALUE
|
static VALUE
|
||||||
enumerable_lazy(VALUE obj)
|
enumerable_lazy(VALUE obj)
|
||||||
{
|
{
|
||||||
VALUE result;
|
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, enum_size);
|
||||||
|
|
||||||
result = rb_class_new_instance(1, &obj, rb_cLazy);
|
|
||||||
/* Qfalse indicates that the Enumerator::Lazy has no method name */
|
/* Qfalse indicates that the Enumerator::Lazy has no method name */
|
||||||
rb_ivar_set(result, id_method, Qfalse);
|
rb_ivar_set(result, id_method, Qfalse);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
lazy_to_enum_i(VALUE obj, VALUE meth, int argc, VALUE *argv, VALUE (*size_fn)(ANYARGS))
|
||||||
|
{
|
||||||
|
return enumerator_init(enumerator_allocate(rb_cLazy),
|
||||||
|
obj, meth, argc, argv, size_fn, Qnil);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* lzy.to_enum(method = :each, *args) -> lazy_enum
|
||||||
|
* lzy.enum_for(method = :each, *args) -> lazy_enum
|
||||||
|
* lzy.to_enum(method = :each, *args) {|*args| block} -> lazy_enum
|
||||||
|
* lzy.enum_for(method = :each, *args){|*args| block} -> lazy_enum
|
||||||
|
*
|
||||||
|
* Similar to Kernel#to_enum, except it returns a lazy enumerator.
|
||||||
|
* This makes it easy to define Enumerable methods that will
|
||||||
|
* naturally remain lazy if called from a lazy enumerator.
|
||||||
|
*
|
||||||
|
* For example, continuing from the example in Kernel#to_enum:
|
||||||
|
*
|
||||||
|
* # See Kernel#to_enum for the definition of repeat
|
||||||
|
* r = 1..Float::INFINITY
|
||||||
|
* r.repeat(2).first(5) # => [1, 1, 2, 2, 3]
|
||||||
|
* r.repeat(2).class # => Enumerator
|
||||||
|
* r.repeat(2).map{|n| n ** 2}.first(5) # => endless loop!
|
||||||
|
* # works naturally on lazy enumerator:
|
||||||
|
* r.lazy.repeat(2).class # => Enumerator::Lazy
|
||||||
|
* r.lazy.repeat(2).map{|n| n ** 2}.first(5) # => [1, 1, 4, 4, 9]
|
||||||
|
*/
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
lazy_to_enum(int argc, VALUE *argv, VALUE self)
|
||||||
|
{
|
||||||
|
VALUE lazy, meth = sym_each;
|
||||||
|
|
||||||
|
if (argc > 0) {
|
||||||
|
--argc;
|
||||||
|
meth = *argv++;
|
||||||
|
}
|
||||||
|
lazy = lazy_to_enum_i(self, meth, argc, argv, 0);
|
||||||
|
if (rb_block_given_p()) {
|
||||||
|
enumerator_ptr(lazy)->size = rb_block_proc();
|
||||||
|
}
|
||||||
|
return lazy;
|
||||||
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_map_func(VALUE val, VALUE m, int argc, VALUE *argv)
|
lazy_map_func(VALUE val, VALUE m, int argc, VALUE *argv)
|
||||||
{
|
{
|
||||||
@ -1723,10 +1775,10 @@ lazy_take_func(VALUE val, VALUE args, int argc, VALUE *argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_take_size(VALUE lazy)
|
lazy_take_size(VALUE generator, VALUE args, VALUE lazy)
|
||||||
{
|
{
|
||||||
|
VALUE receiver = lazy_size(lazy);
|
||||||
long len = NUM2LONG(RARRAY_PTR(rb_ivar_get(lazy, id_arguments))[0]);
|
long len = NUM2LONG(RARRAY_PTR(rb_ivar_get(lazy, id_arguments))[0]);
|
||||||
VALUE receiver = lazy_receiver_size(lazy);
|
|
||||||
if (NIL_P(receiver) || (FIXNUM_P(receiver) && FIX2LONG(receiver) < len))
|
if (NIL_P(receiver) || (FIXNUM_P(receiver) && FIX2LONG(receiver) < len))
|
||||||
return receiver;
|
return receiver;
|
||||||
return LONG2NUM(len);
|
return LONG2NUM(len);
|
||||||
@ -1736,21 +1788,20 @@ static VALUE
|
|||||||
lazy_take(VALUE obj, VALUE n)
|
lazy_take(VALUE obj, VALUE n)
|
||||||
{
|
{
|
||||||
long len = NUM2LONG(n);
|
long len = NUM2LONG(n);
|
||||||
int argc = 1;
|
VALUE lazy;
|
||||||
VALUE argv[3];
|
|
||||||
|
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
rb_raise(rb_eArgError, "attempt to take negative size");
|
rb_raise(rb_eArgError, "attempt to take negative size");
|
||||||
}
|
}
|
||||||
argv[0] = obj;
|
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
argv[1] = sym_cycle;
|
VALUE len = INT2NUM(0);
|
||||||
argv[2] = INT2NUM(0);
|
lazy = lazy_to_enum_i(obj, sym_cycle, 1, &len, 0);
|
||||||
argc = 3;
|
|
||||||
}
|
}
|
||||||
return lazy_set_method(rb_block_call(rb_cLazy, id_new, argc, argv,
|
else {
|
||||||
lazy_take_func, n),
|
lazy = rb_block_call(rb_cLazy, id_new, 1, &obj,
|
||||||
rb_ary_new3(1, n), lazy_take_size);
|
lazy_take_func, n);
|
||||||
|
}
|
||||||
|
return lazy_set_method(lazy, rb_ary_new3(1, n), lazy_take_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -1774,10 +1825,10 @@ lazy_take_while(VALUE obj)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_drop_size(VALUE lazy)
|
lazy_drop_size(VALUE generator, VALUE args, VALUE lazy)
|
||||||
{
|
{
|
||||||
long len = NUM2LONG(RARRAY_PTR(rb_ivar_get(lazy, id_arguments))[0]);
|
long len = NUM2LONG(RARRAY_PTR(rb_ivar_get(lazy, id_arguments))[0]);
|
||||||
VALUE receiver = lazy_receiver_size(lazy);
|
VALUE receiver = lazy_size(lazy);
|
||||||
if (NIL_P(receiver))
|
if (NIL_P(receiver))
|
||||||
return receiver;
|
return receiver;
|
||||||
if (FIXNUM_P(receiver)) {
|
if (FIXNUM_P(receiver)) {
|
||||||
@ -1841,37 +1892,13 @@ lazy_drop_while(VALUE obj)
|
|||||||
Qnil, 0);
|
Qnil, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
|
||||||
lazy_cycle_size(VALUE lazy)
|
|
||||||
{
|
|
||||||
return rb_enum_cycle_size(rb_ivar_get(lazy, id_receiver), rb_ivar_get(lazy, id_arguments));
|
|
||||||
}
|
|
||||||
|
|
||||||
static VALUE
|
|
||||||
lazy_cycle_func(VALUE val, VALUE m, int argc, VALUE *argv)
|
|
||||||
{
|
|
||||||
return rb_funcall2(argv[0], id_yield, argc - 1, argv + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
lazy_cycle(int argc, VALUE *argv, VALUE obj)
|
lazy_cycle(int argc, VALUE *argv, VALUE obj)
|
||||||
{
|
{
|
||||||
VALUE args;
|
|
||||||
int len = rb_long2int((long)argc + 2);
|
|
||||||
|
|
||||||
if (rb_block_given_p()) {
|
if (rb_block_given_p()) {
|
||||||
return rb_call_super(argc, argv);
|
return rb_call_super(argc, argv);
|
||||||
}
|
}
|
||||||
args = rb_ary_tmp_new(len);
|
return lazy_to_enum_i(obj, sym_cycle, argc, argv, rb_enum_cycle_size);
|
||||||
rb_ary_push(args, obj);
|
|
||||||
rb_ary_push(args, sym_cycle);
|
|
||||||
if (argc > 0) {
|
|
||||||
rb_ary_cat(args, argv, argc);
|
|
||||||
}
|
|
||||||
return lazy_set_method(rb_block_call(rb_cLazy, id_new, len,
|
|
||||||
RARRAY_PTR(args), lazy_cycle_func,
|
|
||||||
args /* prevent from GC */),
|
|
||||||
rb_ary_new4(argc, argv), lazy_cycle_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
@ -1963,12 +1990,13 @@ InitVM_Enumerator(void)
|
|||||||
rb_cLazy = rb_define_class_under(rb_cEnumerator, "Lazy", rb_cEnumerator);
|
rb_cLazy = rb_define_class_under(rb_cEnumerator, "Lazy", rb_cEnumerator);
|
||||||
rb_define_method(rb_mEnumerable, "lazy", enumerable_lazy, 0);
|
rb_define_method(rb_mEnumerable, "lazy", enumerable_lazy, 0);
|
||||||
rb_define_method(rb_cLazy, "initialize", lazy_initialize, -1);
|
rb_define_method(rb_cLazy, "initialize", lazy_initialize, -1);
|
||||||
|
rb_define_method(rb_cLazy, "to_enum", lazy_to_enum, -1);
|
||||||
|
rb_define_method(rb_cLazy, "enum_for", lazy_to_enum, -1);
|
||||||
rb_define_method(rb_cLazy, "map", lazy_map, 0);
|
rb_define_method(rb_cLazy, "map", lazy_map, 0);
|
||||||
rb_define_method(rb_cLazy, "collect", lazy_map, 0);
|
rb_define_method(rb_cLazy, "collect", lazy_map, 0);
|
||||||
rb_define_method(rb_cLazy, "flat_map", lazy_flat_map, 0);
|
rb_define_method(rb_cLazy, "flat_map", lazy_flat_map, 0);
|
||||||
rb_define_method(rb_cLazy, "collect_concat", lazy_flat_map, 0);
|
rb_define_method(rb_cLazy, "collect_concat", lazy_flat_map, 0);
|
||||||
rb_define_method(rb_cLazy, "select", lazy_select, 0);
|
rb_define_method(rb_cLazy, "select", lazy_select, 0);
|
||||||
rb_define_method(rb_cLazy, "size", lazy_size, 0);
|
|
||||||
rb_define_method(rb_cLazy, "find_all", lazy_select, 0);
|
rb_define_method(rb_cLazy, "find_all", lazy_select, 0);
|
||||||
rb_define_method(rb_cLazy, "reject", lazy_reject, 0);
|
rb_define_method(rb_cLazy, "reject", lazy_reject, 0);
|
||||||
rb_define_method(rb_cLazy, "grep", lazy_grep, 1);
|
rb_define_method(rb_cLazy, "grep", lazy_grep, 1);
|
||||||
|
@ -20,7 +20,8 @@ class TestLazyEnumerator < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_initialize
|
def test_initialize
|
||||||
assert_equal([1, 2, 3], [1, 2, 3].lazy.to_a)
|
assert_equal([1, 2, 3], [1, 2, 3].lazy.to_a)
|
||||||
assert_equal([1, 2, 3], Enumerator::Lazy.new([1, 2, 3]).to_a)
|
assert_equal([1, 2, 3], Enumerator::Lazy.new([1, 2, 3]){|y, v| y << v}.to_a)
|
||||||
|
assert_raise(ArgumentError) { Enumerator::Lazy.new([1, 2, 3]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_each_args
|
def test_each_args
|
||||||
@ -361,10 +362,6 @@ class TestLazyEnumerator < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_inspect
|
def test_inspect
|
||||||
assert_equal("#<Enumerator::Lazy: 1..10:each>",
|
|
||||||
Enumerator::Lazy.new(1..10).inspect)
|
|
||||||
assert_equal("#<Enumerator::Lazy: 1..10:cycle(2)>",
|
|
||||||
Enumerator::Lazy.new(1..10, :cycle, 2).inspect)
|
|
||||||
assert_equal("#<Enumerator::Lazy: 1..10>", (1..10).lazy.inspect)
|
assert_equal("#<Enumerator::Lazy: 1..10>", (1..10).lazy.inspect)
|
||||||
assert_equal('#<Enumerator::Lazy: #<Enumerator: "foo":each_char>>',
|
assert_equal('#<Enumerator::Lazy: #<Enumerator: "foo":each_char>>',
|
||||||
"foo".each_char.lazy.inspect)
|
"foo".each_char.lazy.inspect)
|
||||||
@ -388,10 +385,28 @@ class TestLazyEnumerator < Test::Unit::TestCase
|
|||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_lazy_to_enum
|
||||||
|
lazy = [1, 2, 3].lazy
|
||||||
|
def lazy.foo(*args)
|
||||||
|
yield args
|
||||||
|
yield args
|
||||||
|
end
|
||||||
|
enum = lazy.to_enum(:foo, :hello, :world)
|
||||||
|
assert_equal Enumerator::Lazy, enum.class
|
||||||
|
assert_equal nil, enum.size
|
||||||
|
assert_equal [[:hello, :world], [:hello, :world]], enum.to_a
|
||||||
|
|
||||||
|
assert_equal [1, 2, 3], lazy.to_enum.to_a
|
||||||
|
end
|
||||||
|
|
||||||
def test_size
|
def test_size
|
||||||
lazy = [1, 2, 3].lazy
|
lazy = [1, 2, 3].lazy
|
||||||
assert_equal 3, lazy.size
|
assert_equal 3, lazy.size
|
||||||
assert_equal 42, Enumerator.new(42){}.lazy.size
|
assert_equal 42, Enumerator::Lazy.new([],->{42}){}.size
|
||||||
|
assert_equal 42, Enumerator::Lazy.new([],42){}.size
|
||||||
|
assert_equal 42, Enumerator::Lazy.new([],42){}.lazy.size
|
||||||
|
assert_equal 42, lazy.to_enum{ 42 }.size
|
||||||
|
|
||||||
%i[map collect].each do |m|
|
%i[map collect].each do |m|
|
||||||
assert_equal 3, lazy.send(m){}.size
|
assert_equal 3, lazy.send(m){}.size
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user