tins 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +5 -0
- data/LICENSE +18 -0
- data/README.rdoc +20 -0
- data/Rakefile +28 -0
- data/TODO +1 -0
- data/VERSION +1 -0
- data/lib/spruz.rb +1 -0
- data/lib/tins.rb +34 -0
- data/lib/tins/alias.rb +1 -0
- data/lib/tins/attempt.rb +51 -0
- data/lib/tins/bijection.rb +46 -0
- data/lib/tins/count_by.rb +8 -0
- data/lib/tins/deep_dup.rb +11 -0
- data/lib/tins/file_binary.rb +85 -0
- data/lib/tins/generator.rb +68 -0
- data/lib/tins/go.rb +43 -0
- data/lib/tins/hash_symbolize_keys_recursive.rb +28 -0
- data/lib/tins/hash_union.rb +15 -0
- data/lib/tins/limited.rb +38 -0
- data/lib/tins/lines_file.rb +123 -0
- data/lib/tins/memoize.rb +78 -0
- data/lib/tins/minimize.rb +55 -0
- data/lib/tins/module_group.rb +13 -0
- data/lib/tins/null.rb +26 -0
- data/lib/tins/once.rb +25 -0
- data/lib/tins/p.rb +23 -0
- data/lib/tins/partial_application.rb +31 -0
- data/lib/tins/range_plus.rb +9 -0
- data/lib/tins/round.rb +51 -0
- data/lib/tins/secure_write.rb +25 -0
- data/lib/tins/shuffle.rb +17 -0
- data/lib/tins/string_camelize.rb +16 -0
- data/lib/tins/string_underscore.rb +15 -0
- data/lib/tins/string_version.rb +105 -0
- data/lib/tins/subhash.rb +42 -0
- data/lib/tins/time_dummy.rb +31 -0
- data/lib/tins/to_proc.rb +11 -0
- data/lib/tins/uniq_by.rb +10 -0
- data/lib/tins/version.rb +10 -0
- data/lib/tins/write.rb +19 -0
- data/lib/tins/xt.rb +25 -0
- data/lib/tins/xt/attempt.rb +7 -0
- data/lib/tins/xt/blank.rb +67 -0
- data/lib/tins/xt/count_by.rb +11 -0
- data/lib/tins/xt/deep_dup.rb +7 -0
- data/lib/tins/xt/file_binary.rb +7 -0
- data/lib/tins/xt/full.rb +33 -0
- data/lib/tins/xt/hash_symbolize_keys_recursive.rb +7 -0
- data/lib/tins/xt/hash_union.rb +11 -0
- data/lib/tins/xt/irb.rb +17 -0
- data/lib/tins/xt/named.rb +35 -0
- data/lib/tins/xt/null.rb +5 -0
- data/lib/tins/xt/p.rb +7 -0
- data/lib/tins/xt/partial_application.rb +11 -0
- data/lib/tins/xt/range_plus.rb +12 -0
- data/lib/tins/xt/round.rb +13 -0
- data/lib/tins/xt/secure_write.rb +11 -0
- data/lib/tins/xt/shuffle.rb +11 -0
- data/lib/tins/xt/string.rb +5 -0
- data/lib/tins/xt/string_camelize.rb +6 -0
- data/lib/tins/xt/string_underscore.rb +6 -0
- data/lib/tins/xt/string_version.rb +7 -0
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/symbol_to_proc.rb +7 -0
- data/lib/tins/xt/time_dummy.rb +7 -0
- data/lib/tins/xt/uniq_by.rb +15 -0
- data/lib/tins/xt/write.rb +11 -0
- data/tests/tins_file_binary_test.rb +67 -0
- data/tests/tins_lines_file_test.rb +84 -0
- data/tests/tins_memoize_test.rb +52 -0
- data/tests/tins_secure_write_test.rb +44 -0
- data/tests/tins_test.rb +629 -0
- data/tins.gemspec +35 -0
- metadata +212 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Tins
|
2
|
+
module HashSymbolizeKeysRecursive
|
3
|
+
def symbolize_keys_recursive
|
4
|
+
inject(self.class.new) do |h,(k, v)|
|
5
|
+
k = k.to_s
|
6
|
+
k.empty? and next
|
7
|
+
case v
|
8
|
+
when Hash
|
9
|
+
h[k.to_sym] = v.symbolize_keys_recursive
|
10
|
+
when Array
|
11
|
+
h[k.to_sym] = a = v.dup
|
12
|
+
v.each_with_index do |x, i|
|
13
|
+
Hash === x and a[i] = x.symbolize_keys_recursive
|
14
|
+
end
|
15
|
+
else
|
16
|
+
h[k.to_sym] = v
|
17
|
+
end
|
18
|
+
h
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def symbolize_keys_recursive!
|
23
|
+
replace symbolize_keys_recursive
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'tins/alias'
|
data/lib/tins/limited.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Tins
|
4
|
+
class Limited
|
5
|
+
# Create a Limited instance, that runs _maximum_ threads at most.
|
6
|
+
def initialize(maximum)
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@continue = ConditionVariable.new
|
9
|
+
@maximum = Integer(maximum)
|
10
|
+
raise ArgumentError, "maximum < 1" if @maximum < 1
|
11
|
+
@count = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
# The maximum number of worker threads.
|
15
|
+
attr_reader :maximum
|
16
|
+
|
17
|
+
# Execute _maximum_ number of threads in parallel.
|
18
|
+
def execute
|
19
|
+
@mutex.synchronize do
|
20
|
+
loop do
|
21
|
+
if @count < @maximum
|
22
|
+
@count += 1
|
23
|
+
Thread.new do
|
24
|
+
yield
|
25
|
+
@mutex.synchronize { @count -= 1 }
|
26
|
+
@continue.signal
|
27
|
+
end
|
28
|
+
return
|
29
|
+
else
|
30
|
+
@continue.wait(@mutex)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'tins/alias'
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Tins
|
2
|
+
class LinesFile
|
3
|
+
module LineExtension
|
4
|
+
attr_reader :line_number
|
5
|
+
|
6
|
+
def filename
|
7
|
+
lines_file.filename.dup
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.for_filename(filename, line_number = nil)
|
12
|
+
obj = new(File.readlines(filename), line_number)
|
13
|
+
obj.filename = filename
|
14
|
+
obj
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for_file(file, line_number = nil)
|
18
|
+
obj = new(file.readlines, line_number)
|
19
|
+
obj.filename = file.path
|
20
|
+
obj
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.for_lines(lines, line_number = nil)
|
24
|
+
new(lines, line_number)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(lines, line_number = nil)
|
28
|
+
@lines = lines
|
29
|
+
@lines.each_with_index do |line, i|
|
30
|
+
line.extend LineExtension
|
31
|
+
line.instance_variable_set :@line_number, i + 1
|
32
|
+
line.instance_variable_set :@lines_file, self
|
33
|
+
end
|
34
|
+
instance_variable_set :@line_number, line_number || (@lines.empty? ? 0 : 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :filename
|
38
|
+
|
39
|
+
attr_reader :line_number
|
40
|
+
|
41
|
+
def rewind
|
42
|
+
self.line_number = 1
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def next!
|
47
|
+
old = line_number
|
48
|
+
self.line_number += 1
|
49
|
+
line_number > old ? self : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def previous!
|
53
|
+
old = line_number
|
54
|
+
self.line_number -= 1
|
55
|
+
line_number < old ? self : nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def line_number=(number)
|
59
|
+
number = number.to_i
|
60
|
+
if number > 0 && number <= last_line_number
|
61
|
+
@line_number = number
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def last_line_number
|
66
|
+
@lines.size
|
67
|
+
end
|
68
|
+
|
69
|
+
def empty?
|
70
|
+
@lines.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def each(&block)
|
74
|
+
empty? and return self
|
75
|
+
old_line_number = line_number
|
76
|
+
1.upto(last_line_number) do |number|
|
77
|
+
self.line_number = number
|
78
|
+
block.call(line)
|
79
|
+
end
|
80
|
+
self
|
81
|
+
ensure
|
82
|
+
self.line_number = old_line_number
|
83
|
+
end
|
84
|
+
include Enumerable
|
85
|
+
|
86
|
+
def line
|
87
|
+
index = line_number - 1
|
88
|
+
@lines[index] if index >= 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def file_linenumber
|
92
|
+
"#{filename}:#{line_number}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def match_backward(regexp, previous_after_match = false)
|
96
|
+
begin
|
97
|
+
if line =~ regexp
|
98
|
+
previous_after_match and previous!
|
99
|
+
return $~.captures
|
100
|
+
end
|
101
|
+
end while previous!
|
102
|
+
end
|
103
|
+
|
104
|
+
def match_forward(regexp, next_after_match = false)
|
105
|
+
begin
|
106
|
+
if line =~ regexp
|
107
|
+
next_after_match and next!
|
108
|
+
return $~.captures
|
109
|
+
end
|
110
|
+
end while next!
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
"#{line_number} #{line.chomp}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def inspect
|
118
|
+
"#<#{self.class}: #{to_s.inspect}>"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
require 'tins/alias'
|
data/lib/tins/memoize.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Tins
|
2
|
+
module Memoize
|
3
|
+
class ::Module
|
4
|
+
class << self
|
5
|
+
# Returns the current memoize cache for all the stored objects and
|
6
|
+
# method call results.
|
7
|
+
def __memoize_cache__
|
8
|
+
@__memoize_cache__ ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Finalizer to delete the stored result for a garbage collected object.
|
12
|
+
def __memoize_cache_delete__
|
13
|
+
lambda do |id|
|
14
|
+
$DEBUG and warn "Deleted method results for object id='#{id}'"
|
15
|
+
__memoize_cache__.delete(id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Automatically memoize calls of the the methods +method_ids+. The
|
21
|
+
# memoized results do NOT ONLY depend on the arguments, but ALSO on the
|
22
|
+
# object the method is called on.
|
23
|
+
def memoize_method(*method_ids)
|
24
|
+
method_ids.each do |method_id|
|
25
|
+
method_id = method_id.to_s.to_sym
|
26
|
+
orig_method = instance_method(method_id)
|
27
|
+
__send__(:define_method, method_id) do |*args|
|
28
|
+
unless mc = ::Module.__memoize_cache__[__id__]
|
29
|
+
mc = ::Module.__memoize_cache__[__id__] ||= {}
|
30
|
+
ObjectSpace.define_finalizer(self, ::Module.__memoize_cache_delete__)
|
31
|
+
end
|
32
|
+
if mc.key?(method_id) and result = mc[method_id][args]
|
33
|
+
result
|
34
|
+
else
|
35
|
+
(mc[method_id] ||= {})[args] = result = orig_method.bind(self).call(*args)
|
36
|
+
$DEBUG and warn "#{self.class} cached method #{method_id}(#{args.inspect unless args.empty?}) = #{result.inspect} [#{__id__}]"
|
37
|
+
end
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the current memoize cache for this Module.
|
44
|
+
def __memoize_cache__
|
45
|
+
@__memoize_cache__
|
46
|
+
end
|
47
|
+
|
48
|
+
# Automatically memoize calls of the functions +function_ids+. The
|
49
|
+
# memoized result does ONLY depend on the arguments given to the
|
50
|
+
# function.
|
51
|
+
def memoize_function(*function_ids)
|
52
|
+
mc = @__memoize_cache__ ||= {}
|
53
|
+
function_ids.each do |method_id|
|
54
|
+
method_id = method_id.to_s.to_sym
|
55
|
+
orig_method = instance_method(method_id)
|
56
|
+
__send__(:define_method, method_id) do |*args|
|
57
|
+
if mc.key?(method_id) and result = mc[method_id][args]
|
58
|
+
result
|
59
|
+
else
|
60
|
+
(mc[method_id] ||= {})[args] = result = orig_method.bind(self).call(*args)
|
61
|
+
$DEBUG and warn "#{self.class} cached function #{method_id}(#{args.inspect unless args.empty?}) = #{result.inspect}"
|
62
|
+
end
|
63
|
+
result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Clear cached values for all methods and functions.
|
69
|
+
def memoize_cache_clear
|
70
|
+
mc = @__memoize_cache__ and mc.clear
|
71
|
+
mc = ::Module.__memoize_cache__ and mc.clear
|
72
|
+
self
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
require 'tins/alias'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Tins
|
2
|
+
# This module can be mixed into all classes, whose instances respond to the
|
3
|
+
# [] and size-methods, like for example Array. The returned elements from []
|
4
|
+
# should respond to the succ method.
|
5
|
+
module Minimize
|
6
|
+
# Returns a minimized version of this object, that is successive elements
|
7
|
+
# are substituted with ranges a..b. In the situation ..., x, y,... and y !=
|
8
|
+
# x.succ a range x..x is created, to make it easier to iterate over all the
|
9
|
+
# ranges in one run. A small example:
|
10
|
+
# [ 'A', 'B', 'C', 'G', 'K', 'L', 'M' ].minimize # => [ 'A'..'C', 'G'..'G', 'K'..'M' ]
|
11
|
+
#
|
12
|
+
# If the order of the original elements doesn't matter, it's a good idea to
|
13
|
+
# first sort them and then minimize:
|
14
|
+
# [ 5, 1, 4, 2 ].sort.minimize # => [ 1..2, 4..5 ]
|
15
|
+
def minimize
|
16
|
+
result = []
|
17
|
+
last_index = size - 1
|
18
|
+
size.times do |i|
|
19
|
+
result << [ self[0] ] if i == 0
|
20
|
+
if self[i].succ != self[i + 1] or i == last_index
|
21
|
+
result[-1] << self[i]
|
22
|
+
result << [ self[i + 1] ] unless i == last_index
|
23
|
+
end
|
24
|
+
end
|
25
|
+
result.map! { |a, b| a..b }
|
26
|
+
end
|
27
|
+
|
28
|
+
# First minimizes this object, then calls the replace method with the
|
29
|
+
# result.
|
30
|
+
def minimize!
|
31
|
+
replace minimize
|
32
|
+
end
|
33
|
+
|
34
|
+
# Invert a minimized version of an object. Some small examples:
|
35
|
+
# [ 'A'..'C', 'G'..'G', 'K'..'M' ].unminimize # => [ 'A', 'B', 'C', 'G', 'K', 'L', 'M' ]
|
36
|
+
# and
|
37
|
+
# [ 1..2, 4..5 ].unminimize # => [ 1, 2, 4, 5 ]
|
38
|
+
def unminimize
|
39
|
+
result = []
|
40
|
+
for range in self
|
41
|
+
for e in range
|
42
|
+
result << e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
# Invert a minimized version of this object in place.
|
49
|
+
def unminimize!
|
50
|
+
replace unminimize
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
require 'tins/alias'
|
data/lib/tins/null.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Tins
|
2
|
+
# Implementation of the null object pattern in Ruby.
|
3
|
+
module Null
|
4
|
+
def method_missing(*)
|
5
|
+
self
|
6
|
+
end
|
7
|
+
|
8
|
+
def const_missing(*)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
''
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
'NULL'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
NULL = Class.new do
|
22
|
+
include Tins::Null
|
23
|
+
end.new
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'tins/alias'
|
data/lib/tins/once.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Tins
|
2
|
+
module Once
|
3
|
+
include File::Constants
|
4
|
+
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def only_once(lock_filename = nil, locking_constant = nil)
|
8
|
+
lock_filename ||= $0
|
9
|
+
locking_constant ||= LOCK_EX
|
10
|
+
f = File.new(lock_filename, RDONLY)
|
11
|
+
f.flock(locking_constant) and yield
|
12
|
+
ensure
|
13
|
+
if f
|
14
|
+
f.flock LOCK_UN
|
15
|
+
f.close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def try_only_once(lock_filename = nil, locking_constant = nil, &block)
|
20
|
+
only_once(lock_filename, locking_constant || LOCK_EX | LOCK_NB, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'tins/alias'
|
data/lib/tins/p.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module Tins
|
4
|
+
module P
|
5
|
+
private
|
6
|
+
|
7
|
+
# Raise a runtime error with the inspected objects +objs+ (obtained by
|
8
|
+
# calling the #inspect method) as their message text. This is useful for
|
9
|
+
# quick debugging.
|
10
|
+
def p!(*objs)
|
11
|
+
raise((objs.size < 2 ? objs.first : objs).inspect)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Raise a runtime error with the inspected objects +objs+ (obtained by
|
15
|
+
# calling the #pretty_inspect method) as their message text. This is useful
|
16
|
+
# for quick debugging.
|
17
|
+
def pp!(*objs)
|
18
|
+
raise("\n" + (objs.size < 2 ? objs.first : objs).pretty_inspect.chomp)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'tins/alias'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Tins
|
2
|
+
module PartialApplication
|
3
|
+
# If this module is included into a Proc (or similar object), it tampers
|
4
|
+
# with its Proc#arity method.
|
5
|
+
def self.included(modul)
|
6
|
+
modul.module_eval do
|
7
|
+
old_arity = instance_method(:arity)
|
8
|
+
define_method(:arity) do
|
9
|
+
@__arity__ or old_arity.bind(self).call
|
10
|
+
end
|
11
|
+
end
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
# Create a partial application of this Proc (or similar object) using
|
16
|
+
# _args_ as the already applied arguments.
|
17
|
+
def partial(*args)
|
18
|
+
if args.empty?
|
19
|
+
dup
|
20
|
+
elsif args.size > arity
|
21
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for #{arity})"
|
22
|
+
else
|
23
|
+
f = lambda { |*b| call(*(args + b)) }
|
24
|
+
f.instance_variable_set :@__arity__, arity - args.size
|
25
|
+
f
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'tins/alias'
|