module Lingo::Ctl

Constants

ALIASES
OPTIONS
OPTWIDTH
USAGE

Public Class Methods

cmd(name, short, desc, args = nil, default = nil) click to toggle source
# File lib/lingo/ctl.rb, line 52
def self.cmd(name, short, desc, args = nil, default = nil)
  if name.is_a?(Array)
    m, f, k = name
    name, short = "#{m}#{k}", "#{f}#{short}"
    class_eval %Q{private; def do_#{name}; #{m}(:#{k}); end}
  end

  if args
    desc = [desc, args = "Arguments: #{args}"]
    args << " (Default: #{default})" if default
  end

  COMMANDS[name], ALIASES[short] = desc, name
end

Public Instance Methods

ctl() click to toggle source
# File lib/lingo/ctl.rb, line 47
def ctl
  parse_options
  send("do_#{ALIASES[ARGV.shift]}")
end

Private Instance Methods

clear(what) click to toggle source
# File lib/lingo/ctl/files.rb, line 78
def clear(what)
  target = Dir["#{find(what, false)}.*"]
  FileUtils.rm(target, verbose: true) unless target.empty?
end
columns(hash, map = :keys) click to toggle source
# File lib/lingo/ctl/analysis.rb, line 105
def columns(hash, map = :keys)
  hash.values.map(&map).flatten.uniq.sort
end
copy(what) click to toggle source
# File lib/lingo/ctl/files.rb, line 62
def copy(what)
  usage('Source and target are the same.') if OPTIONS[:scope] == :local

  local_path = path_for_scope(:local)

  source = find(what, false, path_for_scope || Lingo::PATH - local_path)
  target = File.expand_path(Lingo.basepath(what, source), local_path[0])

  usage('Source and target are the same.') if source == target

  return unless overwrite?(target)

  FileUtils.mkdir_p(File.dirname(target))
  FileUtils.cp(source, target, verbose: true)
end
copy_list(what) { |i| ... } click to toggle source
# File lib/lingo/ctl/other.rb, line 131
def copy_list(what)
  files = list(what, false)
  files.select! { |i| yield i } if block_given?
  files.each { |file| ARGV.replace([file]); copy(what) }
end
csv_foreach(paths) { |path, *values_at(*%w)| ... } click to toggle source
# File lib/lingo/ctl/analysis.rb, line 100
def csv_foreach(paths)
  paths.each { |path| foreach_csv(path, headers: true) { |row|
    yield path, *row.values_at(*%w[string token word pattern]) } }
end
csv_writer(*paths) click to toggle source
# File lib/lingo/ctl/analysis.rb, line 92
def csv_writer(*paths)
  name = File.join(File.dirname(paths.first), paths.map { |path|
    File.basename(path.chomp(File.extname(path))) }.uniq.join('-'))

  lambda { |key, &block| overwrite?(file = "#{name}.#{key}.csv") &&
    puts("#{file}: #{Array(open_csv(file, 'wb', &block)).join(' / ')}") }
end
do_analysisstats() click to toggle source
# File lib/lingo/ctl/analysis.rb, line 36
def do_analysisstats
  require 'nuggets/array/histogram'

  paths, write = paths_write
  stats, patts = Hash.array(1), Hash.array(1)

  csv_foreach(paths) { |path, _, token, word, pattern|
    token ? stats[:tokens][path] << token : word ? begin
      stats[:words][path] << word
      patts[word][path] << pattern if pattern
    end : nil
  }

  stats.each { |k, h| write.(k) { |csv|
    csv << ['file', *c = columns(g = histograms(h))]
    histograms_to_csv(csv, c, g)
    h.values.map(&:size)
  } }

  write.(:patterns) { |csv|
    csv << ['file', 'word', *c = columns(patts, :values)]
    patts.sort.each { |k, h| histograms_to_csv(csv, c, histograms(h), k) }
    c.size - 1
  }
end
do_analysistrans() click to toggle source
# File lib/lingo/ctl/analysis.rb, line 62
def do_analysistrans
  paths, write = paths_write

  rows, comm, more, less = Hash.array(1), {}, Hash.array(1), Hash.array(1)

  csv_foreach(paths) { |path, string, token, word, _|
    rows[token || word][path] << string }

  c = rows.keys.sort.each { |k|
    a = (h = rows[k]).first.last

    paths.size == 1 ? comm[k] = a : begin
      comm[k] = a & (o = h.drop(1)).flat_map(&:last)
      o.each { |path, b| more[path][k] = b - a; less[path][k] = a - b }
    end
  }

  rows.clear

  write.(:transpose) { |csv| transpose_csv(csv, c, comm) }

  { transmore: more, transless: less }.each { |k, v| v.each { |path, h|
    csv_writer(path, *paths).(k) { |csv| transpose_csv(csv, c, h) } } }
end
do_archive() click to toggle source
# File lib/lingo/ctl/other.rb, line 43
def do_archive
  OPTIONS.update(path: ARGV.shift, scope: :local)
  no_args

  source = File.expand_path(path_for_scope.first)
  target = "#{source}.zip"

  abort "No such directory: #{source}" unless Dir.exist?(source)

  return unless overwrite?(target, true)

  base, name = File.split(source)

  Dir.chdir(base) {
    Zip::File.open(target, Zip::File::CREATE) { |zipfile|
      Dir[File.join(name, '**', '*')].each { |file|
        zipfile.add(file, file)
      }
    }
  }

  puts "Directory successfully archived at `#{target}'."
end
do_demo() click to toggle source
# File lib/lingo/ctl/other.rb, line 67
def do_demo
  OPTIONS.update(path: ARGV.shift, scope: :system)
  no_args

  path = path_for_scope(:local).first

  copy_list(:config) { |i| !File.basename(i).start_with?('test') }
  copy_list(:lang)
  copy_list(:dict)   { |i|  File.basename(i).start_with?('user') }
  copy_list(:sample)

  puts "Demo directory successfully initialized at `#{path}'."
end
do_help(opts = nil) click to toggle source
# File lib/lingo/ctl/other.rb, line 99
def do_help(opts = nil)
  msg = opts ? [opts, 'Commands:'] : []

  args = ARGV unless opts || ARGV.empty?

  aliases = Hash.array
  ALIASES.each { |k, v| aliases[v] << k }

  COMMANDS.each { |c, (d, *e)|
    a = aliases[c]
    next if args && ([c, *a] & args).empty?

    c = "#{c} (#{a.join(', ')})" unless a.empty?

    if opts
      msg << "    %-#{OPTWIDTH}s %s" % [c, d]
    else
      msg << "#{c}" << "  - #{d}"
      e.each { |i| msg <<  "  + #{i}" }
    end
  }

  msg.empty? ? abort : abort(msg.join("\n"))
end
do_path() click to toggle source
# File lib/lingo/ctl/other.rb, line 94
def do_path
  no_args
  puts path_for_scope || PATH
end
do_rackup(doit = true) click to toggle source
# File lib/lingo/ctl/other.rb, line 81
def do_rackup(doit = true)
  name = ARGV.shift or missing_arg(:name)
  no_args

  require 'lingo/app'

  if file = Lingo::App.rackup(name)
    doit ? puts(file) : file
  else
    usage("Invalid app name `#{name.inspect}'.")
  end
end
do_usage(msg = nil)
Alias for: usage
do_version(doit = true) click to toggle source
# File lib/lingo/ctl/other.rb, line 124
def do_version(doit = true)
  no_args

  msg = "Lingo v#{Lingo::VERSION}"
  doit ? puts(msg) : msg
end
find(what, doit = true, path = path_for_scope) click to toggle source
# File lib/lingo/ctl/files.rb, line 54
def find(what, doit = true, path = path_for_scope)
  name = ARGV.shift or missing_arg(:name)
  no_args

  file = Lingo.find(what, name, path: path) { |err| usage(err) }
  doit ? puts(file) : file
end
histograms(hash) click to toggle source
# File lib/lingo/ctl/analysis.rb, line 109
def histograms(hash)
  hash.each_with_object({}) { |(k, v), h|
    h[k] = v.histogram.tap { |x| x.default = nil } }
end
histograms_to_csv(csv, columns, histograms, *args) click to toggle source
# File lib/lingo/ctl/analysis.rb, line 114
def histograms_to_csv(csv, columns, histograms, *args)
  histograms.each { |key, histogram|
    others = histograms.values_at(*histograms.keys - [key])
    others = [{}] if others.empty?

    csv << args.dup.unshift(key).concat(columns.map { |header|
      value = histogram[header] or next
      value if others.any? { |other| other[header] != value }
    })
  }
end
list(what, doit = true) click to toggle source
# File lib/lingo/ctl/files.rb, line 46
def list(what, doit = true)
  names = Regexp.union(*ARGV.empty? ? '' : ARGV)

  Lingo.list(what, path: path_for_scope).select { |file|
    File.basename(file) =~ names ? doit ? puts(file) : true : false
  }
end
missing_arg(arg) click to toggle source
# File lib/lingo/ctl.rb, line 115
def missing_arg(arg)
  usage("Required argument `#{arg}' missing.")
end
no_args() click to toggle source
# File lib/lingo/ctl.rb, line 119
def no_args
  usage('Too many arguments.') unless ARGV.empty?
end
parse_options() click to toggle source
# File lib/lingo/ctl.rb, line 69
def parse_options
  OptionParser.new(USAGE, OPTWIDTH) { |opts|
    opts.separator ''
    opts.separator 'Scope options:'

    opts.on('--system', 'Restrict command to the system-wide Lingo directory') {
      OPTIONS[:scope] = :system
    }

    opts.on('--global', "Restrict command to the user's personal Lingo directory") {
      OPTIONS[:scope] = :global
    }

    opts.on('--local', 'Restrict command to the local Lingo directory') {
      OPTIONS[:scope] = :local
    }

    opts.separator ''
    opts.separator 'Generic options:'

    opts.on('-h', '--help', 'Print this help message and exit') {
      do_help(opts)
    }

    opts.on('--version', 'Print program version and exit') {
      abort "#{PROGNAME} v#{VERSION} (#{do_version(false)})"
    }
  }.parse!
end
path_for_scope(scope = OPTIONS[:scope]) click to toggle source
# File lib/lingo/ctl.rb, line 99
def path_for_scope(scope = OPTIONS[:scope])
  case scope
    when :system then [BASE]
    when :global then [HOME]
    when :local  then [OPTIONS[:path] || CURR]
    when nil
    else usage("Invalid scope `#{scope.inspect}'.")
  end
end
paths_write() click to toggle source
# File lib/lingo/ctl/analysis.rb, line 87
def paths_write
  ARGV.empty? ? missing_arg(:path) : [a = ARGV.each { |x|
    abort "No such file: #{x}" unless File.exist?(x) }, csv_writer(*a)]
end
transpose_csv(csv, columns, rows) click to toggle source
# File lib/lingo/ctl/analysis.rb, line 126
def transpose_csv(csv, columns, rows)
  csv << columns; values = rows.values_at(*columns); rows.clear
  values.map(&:size).max.times { |i| csv << values.map { |v| v[i] } }
end
usage(msg = nil) click to toggle source
# File lib/lingo/ctl.rb, line 109
def usage(msg = nil)
  abort "#{"#{PROGNAME}: #{msg}\n\n" if msg}#{USAGE}"
end
Also aliased as: do_usage