tins 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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'