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.
Files changed (76) hide show
  1. data/.gitignore +4 -0
  2. data/.travis.yml +7 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +18 -0
  5. data/README.rdoc +20 -0
  6. data/Rakefile +28 -0
  7. data/TODO +1 -0
  8. data/VERSION +1 -0
  9. data/lib/spruz.rb +1 -0
  10. data/lib/tins.rb +34 -0
  11. data/lib/tins/alias.rb +1 -0
  12. data/lib/tins/attempt.rb +51 -0
  13. data/lib/tins/bijection.rb +46 -0
  14. data/lib/tins/count_by.rb +8 -0
  15. data/lib/tins/deep_dup.rb +11 -0
  16. data/lib/tins/file_binary.rb +85 -0
  17. data/lib/tins/generator.rb +68 -0
  18. data/lib/tins/go.rb +43 -0
  19. data/lib/tins/hash_symbolize_keys_recursive.rb +28 -0
  20. data/lib/tins/hash_union.rb +15 -0
  21. data/lib/tins/limited.rb +38 -0
  22. data/lib/tins/lines_file.rb +123 -0
  23. data/lib/tins/memoize.rb +78 -0
  24. data/lib/tins/minimize.rb +55 -0
  25. data/lib/tins/module_group.rb +13 -0
  26. data/lib/tins/null.rb +26 -0
  27. data/lib/tins/once.rb +25 -0
  28. data/lib/tins/p.rb +23 -0
  29. data/lib/tins/partial_application.rb +31 -0
  30. data/lib/tins/range_plus.rb +9 -0
  31. data/lib/tins/round.rb +51 -0
  32. data/lib/tins/secure_write.rb +25 -0
  33. data/lib/tins/shuffle.rb +17 -0
  34. data/lib/tins/string_camelize.rb +16 -0
  35. data/lib/tins/string_underscore.rb +15 -0
  36. data/lib/tins/string_version.rb +105 -0
  37. data/lib/tins/subhash.rb +42 -0
  38. data/lib/tins/time_dummy.rb +31 -0
  39. data/lib/tins/to_proc.rb +11 -0
  40. data/lib/tins/uniq_by.rb +10 -0
  41. data/lib/tins/version.rb +10 -0
  42. data/lib/tins/write.rb +19 -0
  43. data/lib/tins/xt.rb +25 -0
  44. data/lib/tins/xt/attempt.rb +7 -0
  45. data/lib/tins/xt/blank.rb +67 -0
  46. data/lib/tins/xt/count_by.rb +11 -0
  47. data/lib/tins/xt/deep_dup.rb +7 -0
  48. data/lib/tins/xt/file_binary.rb +7 -0
  49. data/lib/tins/xt/full.rb +33 -0
  50. data/lib/tins/xt/hash_symbolize_keys_recursive.rb +7 -0
  51. data/lib/tins/xt/hash_union.rb +11 -0
  52. data/lib/tins/xt/irb.rb +17 -0
  53. data/lib/tins/xt/named.rb +35 -0
  54. data/lib/tins/xt/null.rb +5 -0
  55. data/lib/tins/xt/p.rb +7 -0
  56. data/lib/tins/xt/partial_application.rb +11 -0
  57. data/lib/tins/xt/range_plus.rb +12 -0
  58. data/lib/tins/xt/round.rb +13 -0
  59. data/lib/tins/xt/secure_write.rb +11 -0
  60. data/lib/tins/xt/shuffle.rb +11 -0
  61. data/lib/tins/xt/string.rb +5 -0
  62. data/lib/tins/xt/string_camelize.rb +6 -0
  63. data/lib/tins/xt/string_underscore.rb +6 -0
  64. data/lib/tins/xt/string_version.rb +7 -0
  65. data/lib/tins/xt/subhash.rb +11 -0
  66. data/lib/tins/xt/symbol_to_proc.rb +7 -0
  67. data/lib/tins/xt/time_dummy.rb +7 -0
  68. data/lib/tins/xt/uniq_by.rb +15 -0
  69. data/lib/tins/xt/write.rb +11 -0
  70. data/tests/tins_file_binary_test.rb +67 -0
  71. data/tests/tins_lines_file_test.rb +84 -0
  72. data/tests/tins_memoize_test.rb +52 -0
  73. data/tests/tins_secure_write_test.rb +44 -0
  74. data/tests/tins_test.rb +629 -0
  75. data/tins.gemspec +35 -0
  76. 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'
@@ -0,0 +1,15 @@
1
+ module Tins
2
+ module HashUnion
3
+ def |(other)
4
+ case
5
+ when other.respond_to?(:to_hash)
6
+ other = other.to_hash
7
+ when other.respond_to?(:to_h)
8
+ other = other.to_h
9
+ end
10
+ other.merge(self)
11
+ end
12
+ end
13
+ end
14
+
15
+ require 'tins/alias'
@@ -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'
@@ -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'
@@ -0,0 +1,13 @@
1
+ module Tins
2
+ module ModuleGroup
3
+ def self.[](*modules)
4
+ modul = Module.new
5
+ modules.each do |m|
6
+ m.module_eval { include modul }
7
+ end
8
+ modul
9
+ end
10
+ end
11
+ end
12
+
13
+ require 'tins/alias'
@@ -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'
@@ -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'
@@ -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'