epitools 0.5.0 → 0.5.1

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/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!