1#!/usr/bin/ruby
2# encoding: utf-8
3
4class String
5  def /( subpath )
6    File.join( self, subpath.to_s )
7  end
8
9  def here_indent( chr = '| ' )
10    dup.here_indent!( chr )
11  end
12
13  def here_indent!( chr = '| ' )
14    chr = Regexp.escape( chr )
15    exp = Regexp.new( "^ *#{ chr }" )
16    self.gsub!( exp,'' )
17    return self
18  end
19
20  def here_flow( chr = '| ' )
21    dup.here_flow!( chr )
22  end
23
24  def here_flow!( chr = '| ' )
25    here_indent!( chr ).gsub!( /\n\s+/,' ' )
26    return( self )
27  end
28
29  # Indent left or right by n spaces.
30  # (This used to be called #tab and aliased as #indent.)
31  #
32  #  CREDIT: Gavin Sinclair
33  #  CREDIT: Trans
34
35  def indent( n )
36    if n >= 0
37      gsub( /^/, ' ' * n )
38    else
39      gsub( /^ {0,#{ -n }}/, "" )
40    end
41  end
42
43  # Outdent just indents a negative number of spaces.
44  #
45  #  CREDIT: Noah Gibbs
46
47  def outdent( n )
48    indent( -n )
49  end
50  
51  # Returns the shortest length of leading whitespace for all non-blank lines
52  #
53  #   n = %Q(
54  #     a = 3
55  #       b = 4
56  #   ).level_of_indent  #=> 2
57  #
58  #   CREDIT: Kyle Yetter
59  def level_of_indent
60    self.scan( /^ *(?=\S)/ ).map { |space| space.length }.min || 0
61  end
62  
63  def fixed_indent( n )
64    self.outdent( self.level_of_indent ).indent( n )
65  end
66  
67  # Provides a margin controlled string.
68  #
69  #   x = %Q{
70  #         | This
71  #         |   is
72  #         |     margin controlled!
73  #         }.margin
74  #
75  #
76  #   NOTE: This may still need a bit of tweaking.
77  #
78  #  CREDIT: Trans
79
80  def margin( n=0 )
81    #d = /\A.*\n\s*(.)/.match( self )[1]
82    #d = /\A\s*(.)/.match( self)[1] unless d
83    d = ( ( /\A.*\n\s*(.)/.match( self ) ) ||
84        ( /\A\s*(.)/.match( self ) ) )[ 1 ]
85    return '' unless d
86    if n == 0
87      gsub( /\n\s*\Z/,'' ).gsub( /^\s*[#{ d }]/, '' )
88    else
89      gsub( /\n\s*\Z/,'' ).gsub( /^\s*[#{ d }]/, ' ' * n )
90    end
91  end
92
93  # Expands tabs to +n+ spaces.  Non-destructive.  If +n+ is 0, then tabs are
94  # simply removed.  Raises an exception if +n+ is negative.
95  #
96  # Thanks to GGaramuno for a more efficient algorithm.  Very nice.
97  #
98  #  CREDIT: Gavin Sinclair
99  #  CREDIT: Noah Gibbs
100  #  CREDIT: GGaramuno
101
102  def expand_tabs( n=8 )
103    n = n.to_int
104    raise ArgumentError, "n must be >= 0" if n < 0
105    return gsub( /\t/, "" ) if n == 0
106    return gsub( /\t/, " " ) if n == 1
107    str = self.dup
108    while
109      str.gsub!( /^([^\t\n]*)(\t+)/ ) { |f|
110        val = ( n * $2.size - ( $1.size % n ) )
111        $1 << ( ' ' * val )
112      }
113    end
114    str
115  end
116
117
118  # The reverse of +camelcase+. Makes an underscored of a camelcase string.
119  #
120  # Changes '::' to '/' to convert namespaces to paths.
121  #
122  # Examples
123  #   "SnakeCase".snakecase           #=> "snake_case"
124  #   "Snake-Case".snakecase          #=> "snake_case"
125  #   "SnakeCase::Errors".underscore  #=> "snake_case/errors"
126
127  def snakecase
128    gsub( /::/, '/' ).  # NOT SO SURE ABOUT THIS -T
129    gsub( /([A-Z]+)([A-Z][a-z])/,'\1_\2' ).
130    gsub( /([a-z\d])([A-Z])/,'\1_\2' ).
131    tr( "-", "_" ).
132    downcase
133  end
134  
135end
136
137
138class Module
139  # Returns the module's container module.
140  #
141  #   module Example
142  #     class Demo
143  #     end
144  #   end
145  #
146  #   Example::Demo.modspace   #=> Example
147  #
148  # See also Module#basename.
149  #
150  #   CREDIT: Trans
151
152  def modspace
153    space = name[ 0...( name.rindex( '::' ) || 0 ) ]
154    space.empty? ? Object : eval( space )
155  end
156end
157
158module Kernel
159  autoload :Tempfile, 'tempfile'
160  
161  def screen_width( out=STDERR )
162    default_width = ENV[ 'COLUMNS' ] || 80
163    tiocgwinsz = 0x5413
164    data = [ 0, 0, 0, 0 ].pack( "SSSS" )
165    if out.ioctl( tiocgwinsz, data ) >= 0 then
166      rows, cols, xpixels, ypixels = data.unpack( "SSSS" )
167      if cols >= 0 then cols else default_width end
168    else
169      default_width
170    end
171  rescue Exception => e
172    default_width rescue ( raise e )
173  end
174end
175
176
177class File
178  
179  # given some target path string, and an optional reference path
180  # (Dir.pwd by default), this method returns a string containing
181  # the relative path of the target path from the reference path
182  # 
183  # Examples:
184  #    File.relative_path('rel/path')   # => './rel/path'
185  #    File.relative_path('/some/abs/path', '/some')  # => './abs/path'
186  #    File.relative_path('/some/file.txt', '/some/abs/path')  # => '../../file.txt'
187  def self.relative_path( target, reference = Dir.pwd )
188    pair = [ target, reference ].map! do |path|
189      File.expand_path( path.to_s ).split( File::Separator ).tap do |list|
190        if list.empty? then list << String.new( File::Separator )
191        elsif list.first.empty? then list.first.replace( File::Separator )
192        end
193      end
194    end
195
196    target_list, reference_list = pair
197    while target_list.first == reference_list.first
198      target_list.shift
199      reference_list.shift or break
200    end
201    
202    relative_list = Array.new( reference_list.length, '..' )
203    relative_list.empty? and relative_list << '.'
204    relative_list.concat( target_list ).compact!
205    return relative_list.join( File::Separator )
206  end
207  
208end
209
210class Dir
211  defined?( DOTS ) or DOTS = %w(. ..).freeze
212  def self.children( directory )
213    entries = Dir.entries( directory ) - DOTS
214    entries.map! do |entry|
215      File.join( directory, entry )
216    end
217  end
218  
219  def self.mkpath( path )
220    $VERBOSE and $stderr.puts( "INFO: Dir.mkpath(%p)" % path )
221    test( ?d, path ) and return( path )
222    parent = File.dirname( path )
223    test( ?d, parent ) or mkpath( parent )
224    Dir.mkdir( path )
225    return( path )
226  end
227  
228end
229
230class Array
231
232  # Pad an array with a given <tt>value</tt> upto a given <tt>length</tt>.
233  #
234  #   [0,1,2].pad(6,"a")  #=> [0,1,2,"a","a","a"]
235  #
236  # If <tt>length</tt> is a negative number padding will be added
237  # to the beginning of the array.
238  #
239  #   [0,1,2].pad(-6,"a")  #=> ["a","a","a",0,1,2]
240  #
241  #  CREDIT: Richard Laugesen
242
243  def pad( len, val=nil )
244    return dup if self.size >= len.abs
245    if len < 0
246      Array.new( ( len+size ).abs,val ) + self
247    else
248      self + Array.new( len-size,val )
249    end
250  end
251
252  # Like #pad but changes the array in place.
253  #
254  #    a = [0,1,2]
255  #    a.pad!(6,"x")
256  #    a  #=> [0,1,2,"x","x","x"]
257  #
258  #  CREDIT: Richard Laugesen
259
260  def pad!( len, val=nil )
261    return self if self.size >= len.abs
262    if len < 0
263      replace Array.new( ( len+size ).abs,val ) + self
264    else
265      concat Array.new( len-size,val )
266    end
267  end
268
269end
270