epitools 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.5.1
data/epitools.gemspec CHANGED
@@ -4,14 +4,14 @@
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{epitools}
8
- s.version = "0.5.0"
7
+ s.name = "epitools"
8
+ s.version = "0.5.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = [%q{epitron}]
12
- s.date = %q{2011-10-13}
13
- s.description = %q{Miscellaneous utility libraries to make my life easier.}
14
- s.email = %q{chris@ill-logic.com}
11
+ s.authors = ["epitron"]
12
+ s.date = "2011-11-23"
13
+ s.description = "Miscellaneous utility libraries to make my life easier."
14
+ s.email = "chris@ill-logic.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
17
17
  "README.rdoc",
@@ -35,6 +35,7 @@ Gem::Specification.new do |s|
35
35
  "lib/epitools/colored.rb",
36
36
  "lib/epitools/ezdb.rb",
37
37
  "lib/epitools/hexdump.rb",
38
+ "lib/epitools/iter.rb",
38
39
  "lib/epitools/its.rb",
39
40
  "lib/epitools/lcs.rb",
40
41
  "lib/epitools/mimemagic.rb",
@@ -59,6 +60,7 @@ Gem::Specification.new do |s|
59
60
  "spec/clitools_spec.rb",
60
61
  "spec/colored_spec.rb",
61
62
  "spec/ezdb_spec.rb",
63
+ "spec/iter_spec.rb",
62
64
  "spec/lcs_spec.rb",
63
65
  "spec/numwords_spec.rb",
64
66
  "spec/path_spec.rb",
@@ -71,11 +73,11 @@ Gem::Specification.new do |s|
71
73
  "spec/term_spec.rb",
72
74
  "spec/zopen_spec.rb"
73
75
  ]
74
- s.homepage = %q{http://github.com/epitron/epitools}
75
- s.licenses = [%q{WTFPL}]
76
- s.require_paths = [%q{lib}]
77
- s.rubygems_version = %q{1.8.6}
78
- s.summary = %q{NOT UTILS... METILS!}
76
+ s.homepage = "http://github.com/epitron/epitools"
77
+ s.licenses = ["WTFPL"]
78
+ s.require_paths = ["lib"]
79
+ s.rubygems_version = "1.8.10"
80
+ s.summary = "NOT UTILS... METILS!"
79
81
 
80
82
  if s.respond_to? :specification_version then
81
83
  s.specification_version = 3
@@ -16,6 +16,12 @@ autoload :Date, 'date'
16
16
  autoload :Open3, 'open3'
17
17
  #autoload :DelegateClass, 'delegate'
18
18
 
19
+ if RUBY_VERSION["1.8.7"]
20
+ autoload :Prime, 'mathn'
21
+ else
22
+ autoload :Prime, 'prime'
23
+ end
24
+
19
25
  module Digest
20
26
  autoload :SHA1, 'digest/sha1'
21
27
  autoload :SHA2, 'digest/sha2'
@@ -33,6 +39,7 @@ autoload :ProgressBar, 'epitools/progressbar'
33
39
  autoload :Trie, 'epitools/trie'
34
40
  autoload :MimeMagic, 'epitools/mimemagic'
35
41
  autoload :Term, 'epitools/term'
42
+ autoload :Iter, 'epitools/iter'
36
43
 
37
44
  ## Gems
38
45
  autoreq :ANSI, 'ansi'
@@ -176,6 +176,15 @@ class Integer
176
176
 
177
177
  result.map{|digit| BASE62_DIGITS[digit]}.join ''
178
178
  end
179
+
180
+ #
181
+ # Returns the all the prime factors of a number.
182
+ #
183
+ def factors
184
+ Prime # autoload the prime module
185
+ prime_division.map { |n,count| [n]*count }.flatten
186
+ end
187
+
179
188
  end
180
189
 
181
190
 
@@ -312,11 +321,10 @@ class String
312
321
  end
313
322
 
314
323
 
315
-
316
324
  #
317
325
  # Cached constants for base62 decoding.
318
326
  #
319
- BASE62_DIGITS = Hash[ Integer::BASE62_DIGITS.map.with_index{|letter,index| [letter,index]} ]
327
+ BASE62_DIGITS = Hash[ Integer::BASE62_DIGITS.zip((0...Integer::BASE62_DIGITS.size).to_a) ]
320
328
  BASE62_BASE = Integer::BASE62_BASE
321
329
 
322
330
  #
@@ -511,6 +519,9 @@ class Array
511
519
  piece_size = (size.to_f / pieces).ceil
512
520
  each_slice(piece_size).to_a
513
521
  end
522
+
523
+
524
+ alias_method :unzip, :transpose
514
525
 
515
526
  end
516
527
 
@@ -721,6 +732,42 @@ module Enumerable
721
732
  a.select.with_index{ |e, i| bitmask[i] == 1 }
722
733
  end
723
734
  end
735
+
736
+ #
737
+ # Does the opposite of #zip -- converts [ [:a, 1], [:b, 2] ] to [ [:a, :b], [1, 2] ]
738
+ #
739
+ def unzip
740
+ # TODO: make it work for arrays containing uneven-length contents
741
+ to_a.transpose
742
+ end
743
+
744
+ #
745
+ # Associative grouping; groups all elements who share something in common with each other.
746
+ # You supply a block which takes two elements, and have it return true if they are "neighbours"
747
+ # (eg: belong in the same group).
748
+ #
749
+ # Example:
750
+ # [1,2,5,6].group_neighbours_by { |a,b| b-a <= 1 } #=> [ [1,2], [5,6] ]
751
+ #
752
+ # (Note: This is a very fast one-pass algorithm -- therefore, the groups must be pre-sorted.)
753
+ #
754
+ def group_neighbours_by(&block)
755
+ result = []
756
+ cluster = [first]
757
+ each_cons(2) do |a,b|
758
+ if yield(a,b)
759
+ cluster << b
760
+ else
761
+ result << cluster
762
+ cluster = [b]
763
+ end
764
+ end
765
+
766
+ result << cluster if cluster.any?
767
+
768
+ result
769
+ end
770
+ alias_method :group_neighbors_by, :group_neighbours_by
724
771
 
725
772
  end
726
773
 
@@ -0,0 +1,149 @@
1
+ #
2
+ # A stable iterator class.
3
+ # (You can reorder/remove elements in the container without affecting iteration.)
4
+ #
5
+ # For example, to reverse all the elements in a list:
6
+ # >> i = Iter.new(1..10)
7
+ # >> i.each_cons(2) { |a,b| b.move_before(a) }
8
+ # >> i.to_a #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
9
+ #
10
+ class Iter
11
+
12
+ attr_accessor :container
13
+
14
+ def initialize(vals)
15
+ @container = vals.map{|val| Elem.new(self, val)}
16
+ end
17
+
18
+ def self.from_elems(elems)
19
+ new([]).tap{|i| i.container = elems}
20
+ end
21
+
22
+ def ==(other)
23
+ case other
24
+ when Iter
25
+ @container == other.container
26
+ when Array
27
+ @container == other
28
+ end
29
+ end
30
+
31
+ def each
32
+ @container.each do |elem|
33
+ yield elem
34
+ end
35
+ end
36
+
37
+ def each_cons(num=1)
38
+ @container.each_cons(num) do |(*elems)|
39
+ yield *elems
40
+ end
41
+ end
42
+
43
+ alias_method :iterate, :each_cons
44
+ alias_method :every, :each_cons
45
+
46
+ def to_a
47
+ @container.map(&:val)
48
+ end
49
+
50
+ def method_missing(name, *args)
51
+ result = @container.send(name, *args)
52
+ case result
53
+ when Array
54
+ Iter.from_elems result
55
+ else
56
+ result
57
+ end
58
+ end
59
+
60
+ class Elem < BasicObject
61
+
62
+ attr_accessor :val
63
+
64
+ def initialize(iter, val)
65
+ @iter = iter
66
+ @val = val
67
+ end
68
+
69
+ def ==(other)
70
+ self.eql?(other)
71
+ end
72
+
73
+ def container
74
+ @iter.container
75
+ end
76
+
77
+ def current
78
+ self
79
+ end
80
+
81
+ def next
82
+ p = pos+1
83
+ if p >= container.size
84
+ nil
85
+ else
86
+ container[p]
87
+ end
88
+ end
89
+
90
+ def prev
91
+ p = pos-1
92
+ if p < 0
93
+ nil
94
+ else
95
+ container[p]
96
+ end
97
+ end
98
+
99
+ def remove
100
+ container.delete_at(pos)
101
+ end
102
+ alias_method :delete, :remove
103
+
104
+ def replace_with(replacement)
105
+ container[pos] = Elem.new(@iter, replacement)
106
+ end
107
+
108
+ def pos
109
+ container.index(self)
110
+ end
111
+
112
+ def move_before(other)
113
+ remove
114
+ container.insert(other.pos, self) # insert at pos and shift everything over
115
+ end
116
+
117
+ def move_after(other)
118
+ remove
119
+ container.insert(other.pos+1, self) # insert after pos
120
+ end
121
+
122
+ def move_first
123
+ remove
124
+ container.insert(0, self) # insert at beginning
125
+ end
126
+ alias_method :move_start, :move_first
127
+
128
+ def move_last
129
+ remove
130
+ container.insert(-1, self) # insert at end
131
+ end
132
+ alias_method :move_end, :move_last
133
+
134
+ def value
135
+ @val
136
+ end
137
+
138
+ def method_missing(name, *args)
139
+ @val.send(name, *args)
140
+ end
141
+
142
+ def inspect
143
+ "<Elem: #{@val.inspect}>"
144
+ end
145
+ end
146
+
147
+
148
+ end
149
+
data/lib/epitools/path.rb CHANGED
@@ -57,8 +57,8 @@ class Path
57
57
 
58
58
  ## initializers
59
59
 
60
- def initialize(newpath)
61
- self.path = newpath
60
+ def initialize(newpath, hints={})
61
+ self.send("path=", newpath, hints)
62
62
  end
63
63
 
64
64
  def self.glob(str)
@@ -96,9 +96,14 @@ class Path
96
96
  attr_writer :base
97
97
  attr_writer :dirs
98
98
 
99
- def path=(newpath)
100
- if File.exists? newpath
101
- if File.directory? newpath
99
+ #
100
+ # This is the core that initializes the whole class.
101
+ #
102
+ # Note: The `hints` parameter contains options so `path=` doesn't have to touch the filesytem as much.
103
+ #
104
+ def path=(newpath, hints={})
105
+ if hints[:type] or File.exists? newpath
106
+ if hints[:type] == :dir or File.directory? newpath
102
107
  self.dir = newpath
103
108
  else
104
109
  self.dir, self.filename = File.split(newpath)
@@ -261,6 +266,14 @@ class Path
261
266
  uri?
262
267
  end
263
268
 
269
+ def child_of?(parent)
270
+ parent.parent_of? self
271
+ end
272
+
273
+ def parent_of?(child)
274
+ # If `self` is a parent of `child`, it's a prefix.
275
+ child.path[/^#{Regexp.escape self.path}\/.+/] != nil
276
+ end
264
277
 
265
278
  ## comparisons
266
279
 
@@ -324,7 +337,16 @@ class Path
324
337
  def ls; Path[File.join(path, "*")]; end
325
338
 
326
339
  def ls_r; Path[File.join(path, "**/*")]; end
327
-
340
+
341
+ def ls_dirs
342
+ ls.select(&:dir?)
343
+ #Dir.glob("#{path}*/", File::FNM_DOTMATCH).map { |s| Path.new(s, :type=>:dir) }
344
+ end
345
+
346
+ def ls_files
347
+ ls.select(&:file?)
348
+ #Dir.glob("#{path}*", File::FNM_DOTMATCH).map { |s| Path.new(s, :type=>:file) }
349
+ end
328
350
 
329
351
  def siblings
330
352
  ls - [self]
@@ -455,7 +477,6 @@ raise "Broken!"
455
477
  end
456
478
  end
457
479
 
458
-
459
480
  def ln_s(dest)
460
481
  dest = Path[dest]
461
482
  FileUtils.ln_s self, dest
@@ -582,7 +603,7 @@ raise "Broken!"
582
603
  end
583
604
 
584
605
  def lstat
585
- #@lstat ||= File.lstat self
606
+ #@lstat ||= File.lstat self # to cache or not to cache -- that is the question.
586
607
  File.lstat self
587
608
  end
588
609
 
@@ -590,6 +611,9 @@ raise "Broken!"
590
611
  lstat.mode
591
612
  end
592
613
 
614
+ #
615
+ # Find the parent directory. If the `Path` is a filename, it returns the containing directory.
616
+ #
593
617
  def parent
594
618
  if file?
595
619
  with(:filename=>nil)
@@ -598,6 +622,21 @@ raise "Broken!"
598
622
  end
599
623
  end
600
624
 
625
+ #
626
+ # Follows all symlinks to give the true location of a path.
627
+ #
628
+ if File.respond_to?(:realpath)
629
+ def realpath
630
+ Path.new File.realpath(path)
631
+ end
632
+ else
633
+ def realpath
634
+ require 'pathname'
635
+ Path.new Pathname.new(path).realpath
636
+ end
637
+ end
638
+
639
+
601
640
  # Mimetype finding and magic (requires 'mimemagic' gem)
602
641
 
603
642
  #
@@ -701,6 +740,7 @@ raise "Broken!"
701
740
  md5
702
741
  rm
703
742
  truncate
743
+ realpath
704
744
  mv/1
705
745
  move/1
706
746
  chmod/1
@@ -717,9 +757,8 @@ raise "Broken!"
717
757
  end
718
758
  }
719
759
  end
720
-
721
-
722
-
760
+
761
+
723
762
  #
724
763
  # Same as File.expand_path, except preserves the trailing '/'.
725
764
  #
@@ -811,7 +850,7 @@ class Path::URL < Path
811
850
  # TODO: only include certain methods from Path (delegate style)
812
851
  # (eg: remove commands that write)
813
852
 
814
- def initialize(uri)
853
+ def initialize(uri, hints={})
815
854
  @uri = URI.parse(uri)
816
855
  self.path = @uri.path
817
856
  end
@@ -849,7 +888,7 @@ class Path::URL < Path
849
888
  # ...and this is: 80
850
889
  #
851
890
  def port
852
- uri.host
891
+ uri.port
853
892
  end
854
893
 
855
894
  #
data/lib/epitools/sys.rb CHANGED
@@ -224,8 +224,10 @@ module Sys
224
224
  def self.ps(*pids)
225
225
  options = PS_FIELDS.join(',')
226
226
 
227
+ pids = pids.map(&:to_i)
228
+
227
229
  if pids.any?
228
- command = "ps -p #{pids.map(&:to_i).join(',')} -o #{options}"
230
+ command = "ps -p #{pids.join(',')} -o #{options}"
229
231
  else
230
232
  command = "ps ax -o #{options}"
231
233
  end
data/lib/epitools/term.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  #require 'epitools'
2
2
 
3
+ #
4
+ # Example usage:
5
+ # puts Term::Table[ (1..100).to_a ].horizontally #=> prints all the numbers, ordered across rows
6
+ # puts Term::Table[ (1..100).to_a ].vertically #=> prints all the numbers, ordered across columns
7
+ # puts Term::Table[ [[1,2], [3,4]] ] #=> prints the table that was supplied
8
+ #
3
9
  module Term
4
10
 
5
11
  extend self
@@ -259,6 +259,11 @@ describe Integer do
259
259
  sum.to_base62.from_base62.to_s(16).should == sum
260
260
  end
261
261
 
262
+ it "factors numbers" do
263
+ 10.factors.should == [2,5]
264
+ 256.factors.should == [2,2,2,2,2,2,2,2]
265
+ end
266
+
262
267
  end
263
268
 
264
269
 
@@ -358,8 +363,17 @@ describe Enumerable do
358
363
  it "powersets" do
359
364
  [1,2,3].powerset.should == [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
360
365
  Enum.new([1,2], :each).powerset.should == [[], [1], [2], [1, 2]]
361
- end
366
+ end
362
367
 
368
+ it "unzips" do
369
+ [ [:a, 1], [:b, 2] ].unzip.should == [ [:a, :b], [1, 2] ]
370
+ end
371
+
372
+ it "group_neighbours_bys" do
373
+ a = [1,2,5,6,7,10,11,13]
374
+ result = a.group_neighbours_by { |a,b| b-a <= 1 }
375
+ result.should == [[1,2],[5,6,7],[10,11],[13]]
376
+ end
363
377
  end
364
378
 
365
379
  describe Hash do
data/spec/iter_spec.rb ADDED
@@ -0,0 +1,89 @@
1
+ require 'epitools/iter'
2
+
3
+ describe Iter do
4
+
5
+ before :each do
6
+ @i = Iter.new([1,2,3,4,5])
7
+ end
8
+
9
+ it "iterates" do
10
+ @i.iterate(2) do |i,j|
11
+ i.should_not == j
12
+ end
13
+ end
14
+
15
+ it "to_a's" do
16
+ @i.to_a.should == [1,2,3,4,5]
17
+ end
18
+
19
+ it "reverses" do
20
+ @i.iterate(2) do |a, b|
21
+ b.move_before(a)
22
+ end
23
+
24
+ @i.to_a.should == [5,4,3,2,1]
25
+ end
26
+
27
+ it "next/prevs" do
28
+ @i.iterate(2) do |a,b|
29
+ a.next.should == b
30
+ b.prev.should == a
31
+ end
32
+ end
33
+
34
+ it "removes" do
35
+ @i.iterate {|x| x.remove if x % 2 == 1 }
36
+ @i.to_a.should == [2,4]
37
+ end
38
+
39
+ it "replaces" do
40
+ @i.first.replace_with(-1)
41
+ @i.to_a.should == [-1,2,3,4,5]
42
+ end
43
+
44
+ it "slices, values, indexes, etc." do
45
+ # todo: slice should return an iter
46
+ @i.first.should == 1
47
+ @i[0..1].should == @i.values_at(0,1)
48
+ @i[0..-1].should == @i
49
+ @i[-1].should == @i.last
50
+ @i[-2..-1].should == @i.values_at(-2,-1)
51
+ end
52
+
53
+ it "move_first/last" do
54
+ @i.first.move_last
55
+ @i.to_a.should == [2,3,4,5,1]
56
+
57
+ @i.last.move_first
58
+ @i.should == [1,2,3,4,5]
59
+ end
60
+
61
+ it "cluters nearby elements" do
62
+ class Cluster < Array
63
+ def min_distance(other)
64
+ a, b = other.max, other.min
65
+ x, y = max, min
66
+ [a-x, a-y, b-x, b-y].map(&:abs).min
67
+ end
68
+
69
+ def absorb(other)
70
+ concat other
71
+ sort!
72
+ other.clear
73
+ end
74
+ end
75
+
76
+ a = [1,2,5,6,7,10,11,13].map { |e| Cluster.new [e] }
77
+ i = Iter.new(a)
78
+
79
+ i.each_cons(2) do |a,b|
80
+ if b.any? and a.any? and a.min_distance(b) <= 1
81
+ b.absorb(a)
82
+ a.remove
83
+ end
84
+ end
85
+
86
+ i.to_a.should == [[1,2],[5,6,7],[10,11],[13]]
87
+ end
88
+
89
+ end
data/spec/path_spec.rb CHANGED
@@ -126,6 +126,7 @@ describe Path do
126
126
  it "handles URLs" do
127
127
  path = Path["http://google.com/?search=blah"]
128
128
  path.host.should == "google.com"
129
+ path.port.should == 80
129
130
  path.query.should == {"search" => "blah"}
130
131
  path.uri?.should == true
131
132
  end
@@ -321,4 +322,34 @@ describe Path do
321
322
  # swap two symlinks
322
323
  end
323
324
 
325
+ it 'realpaths' do
326
+ Path["/etc"].realpath.should == Path["/etc"]
327
+
328
+ end
329
+
330
+ it 'parents and childs properly' do
331
+
332
+ root = Path["/"]
333
+ parent = Path["/blah/stuff"]
334
+ child = Path["/blah/stuff/what"]
335
+ neither = Path["/whee/yay"]
336
+
337
+ # Table of parent=>child relationships
338
+ {
339
+ parent => child,
340
+ root => parent,
341
+ root => child,
342
+ root => neither,
343
+ }.each do |p, c|
344
+ p.parent_of?(c).should == true
345
+ c.parent_of?(p).should == false
346
+
347
+ c.child_of?(p).should == true
348
+ p.child_of?(c).should == false
349
+ end
350
+
351
+ neither.parent_of?(child).should == false
352
+ neither.parent_of?(parent).should == false
353
+ end
354
+
324
355
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epitools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-13 00:00:00.000000000 Z
12
+ date: 2011-11-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &71901040 !ruby/object:Gem::Requirement
16
+ requirement: &83289590 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.2.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *71901040
24
+ version_requirements: *83289590
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: mechanize
27
- requirement: &71900430 !ruby/object:Gem::Requirement
27
+ requirement: &83289150 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *71900430
35
+ version_requirements: *83289150
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sqlite3-ruby
38
- requirement: &71899880 !ruby/object:Gem::Requirement
38
+ requirement: &83288710 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *71899880
46
+ version_requirements: *83288710
47
47
  description: Miscellaneous utility libraries to make my life easier.
48
48
  email: chris@ill-logic.com
49
49
  executables: []
@@ -70,6 +70,7 @@ files:
70
70
  - lib/epitools/colored.rb
71
71
  - lib/epitools/ezdb.rb
72
72
  - lib/epitools/hexdump.rb
73
+ - lib/epitools/iter.rb
73
74
  - lib/epitools/its.rb
74
75
  - lib/epitools/lcs.rb
75
76
  - lib/epitools/mimemagic.rb
@@ -94,6 +95,7 @@ files:
94
95
  - spec/clitools_spec.rb
95
96
  - spec/colored_spec.rb
96
97
  - spec/ezdb_spec.rb
98
+ - spec/iter_spec.rb
97
99
  - spec/lcs_spec.rb
98
100
  - spec/numwords_spec.rb
99
101
  - spec/path_spec.rb
@@ -126,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
128
  version: '0'
127
129
  requirements: []
128
130
  rubyforge_project:
129
- rubygems_version: 1.8.6
131
+ rubygems_version: 1.8.10
130
132
  signing_key:
131
133
  specification_version: 3
132
134
  summary: NOT UTILS... METILS!