Skip to content

Instantly share code, notes, and snippets.

@aeris
Created December 31, 2012 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aeris/4420087 to your computer and use it in GitHub Desktop.
Save aeris/4420087 to your computer and use it in GitHub Desktop.
Fast cd with learning
#!/usr/bin/env ruby
#encoding: utf-8
require 'sqlite3'
SEPARATOR = ''
class Stats
DB_FILE=File.join Dir.home, ".xd.sqlite3"
def initialize
exist = File.exist? DB_FILE
@db = SQLite3::Database.new DB_FILE
self.create unless exist
self.purge
end
def create
@db.execute "CREATE TABLE stats (path TEXT NOT NULL, occurrence INTEGER NOT NULL, last_used TEXT NOT NULL)"
end
def purge
date = DateTime.now.prev_month.to_s
@db.execute "DELETE FROM stats WHERE last_used < ?", date
end
def close
@db.close
end
def get(paths)
paths.uniq!
result = {}
@db.execute "SELECT path, occurrence FROM stats WHERE path in ( ? )", paths.join(", ") do |row|
path = row[0]
occurrence = row[1]
result[path] = occurrence
paths.delete path
end
paths.each { |path| result[path] = 0 }
result.sort { |a, b| a[1] <=> b[1]}.reverse
end
def add(path)
@db.execute "UPDATE stats SET occurrence = occurrence + 1, last_used = datetime('now') WHERE path = ?", path
if @db.changes == 0
@db.execute "INSERT INTO stats (path, occurrence, last_used) VALUES (?, 0, datetime('now'))", path
end
end
def get_target(path)
result = []
l = path.split(SEPARATOR).length
@db.execute "SELECT path FROM stats" do |row|
file = row[0]
expected = file.split File::SEPARATOR
next if expected.length < l
expected = expected.collect { |p| p[0] }[-l, l] .join SEPARATOR
result << file if expected == path
end
result
end
end
class DirectoryChanger
def initialize stats
@stats = stats
end
def get_target(path)
path = path.downcase
if path.start_with?('/')
start = '/'
path = path.sub /^#{Regexp.escape('/')}/, ""
elsif path.start_with?('-')
start = Dir.home
path = path.sub /^#{Regexp.escape('-')}/, ""
else
start = Dir.pwd
end
path = path.split SEPARATOR
target = self.find_target start, path
end
def find_target(directory, path)
return [] unless File.readable? directory
if path.length <= 0
return [directory]
end
result = []
Dir.entries(directory).each do |entry|
subdirectory = File.join(directory, entry)
if File.directory?(subdirectory) && entry.downcase.start_with?(path[0])
result.concat self.find_target subdirectory, path[1..-1]
end
end
result
end
def perform(path)
if path.start_with? '+'
force = true
path = path.sub /^#{Regexp.escape('+')}/, ""
else
force = false
end
target = self.get_target path
target.concat @stats.get_target path
target = @stats.get target
if force && target.length > 1
target = target[0, 1]
end
if target.length == 0
return 1
elsif target.length == 1
target = target[0]
else
begin
i = 1
target.each do |t|
STDERR.puts "#{i} → #{t[0]}"
i+=1
end
STDERR.print "? > "
value = STDIN.gets.chomp
if value == ""
value = 0
else
value = value.to_i - 1
end
end while 0 > value || value >= target.length
target = target[value]
end
target = target[0]
@stats.add target
print target
return 0
end
end
stats = Stats.new
changer = DirectoryChanger.new stats
code = changer.perform ARGV[0]
stats.close
exit code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment