# -*- coding: utf-8 -*- # # Copyright (C) 2011 Miho SUZUKI # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . require 'thor' require 'rainbow' require "logaling/repository" require "logaling/glossary" class Logaling::Command < Thor VERSION = "0.1.2" LOGALING_CONFIG = '.logaling' map '-a' => :add, '-d' => :delete, '-u' => :update, '-l' => :lookup, '-i' => :import, '-n' => :new, '-r' => :register, '-U' => :unregister, '-L' => :list, '-s' => :show, '-v' => :version class_option "glossary", type: :string, aliases: "-g" class_option "source-language", type: :string, aliases: "-S" class_option "target-language", type: :string, aliases: "-T" class_option "logaling-home", type: :string, aliases: "-h" desc 'new [PROJECT NAME] [SOURCE LANGUAGE] [TARGET LANGUAGE(optional)]', 'Create .logaling' method_option "no-register", type: :boolean, default: false def new(project_name, source_language, target_language=nil) unless File.exist?(LOGALING_CONFIG) FileUtils.mkdir_p(File.join(LOGALING_CONFIG, "glossary")) config = {"glossary" => project_name, "source-language" => source_language} config["target-language"] = target_language if target_language write_config(File.join(LOGALING_CONFIG, "config"), config) register unless options["no-register"] say "Successfully created #{LOGALING_CONFIG}" else say "#{LOGALING_CONFIG} already exists." end end desc 'import', 'Import external glossary' method_option "list", type: :boolean, default: false def import(external_glossary=nil) require "logaling/external_glossary" Logaling::ExternalGlossary.load if options["list"] Logaling::ExternalGlossary.list.each {|glossary| say "#{glossary.name.bright} : #{glossary.description}" } else repository.import(Logaling::ExternalGlossary.get(external_glossary)) end rescue Logaling::ExternalGlossaryNotFound say "'#{external_glossary}' can't find in import list." say "Try 'loga import --list' and confirm import list." end desc 'register', 'Register .logaling' def register logaling_path = find_dotfile required_options = {"glossary" => "input glossary name '-g '"} config = load_config_and_merge_options(required_options) repository.register(logaling_path, config["glossary"]) say "#{config['glossary']} is now registered to logaling." rescue Logaling::CommandFailed => e say e.message say "Try 'loga new' first." rescue Logaling::GlossaryAlreadyRegistered => e say "#{config['glossary']} is already registered." end desc 'unregister', 'Unregister .logaling' def unregister required_options = {"glossary" => "input glossary name '-g '"} config = load_config_and_merge_options(required_options) repository.unregister(config["glossary"]) say "#{config['glossary']} is now unregistered." rescue Logaling::CommandFailed => e say e.message rescue Logaling::GlossaryNotFound => e say "#{config['glossary']} is not yet registered." end desc 'config [KEY] [VALUE] [--global(optional)]', 'Set config.' method_option "global", type: :boolean, default: false def config(key, value) support_keys = %w(glossary source-language target-language) raise Logaling::CommandFailed, "#{key} is unsupported option" unless support_keys.include?(key) config_path = options["global"] ? File.join(LOGALING_HOME, "config") : File.join(find_dotfile, "config") FileUtils.touch(config_path) unless File.exist?(config_path) config = load_config(config_path) config = merge_options({key => value}, config) write_config(config_path, config) say "Successfully set config." rescue Logaling::CommandFailed => e say e.message end desc 'add [SOURCE TERM] [TARGET TERM] [NOTE(optional)]', 'Add term to glossary.' def add(source_term, target_term, note='') config = load_config_and_merge_options repository.index if repository.bilingual_pair_exists?(source_term, target_term, config["glossary"]) raise Logaling::TermError, "term '#{source_term}: #{target_term}' already exists in '#{config["glossary"]}'" end glossary.add(source_term, target_term, note) rescue Logaling::CommandFailed, Logaling::TermError => e say e.message end desc 'delete [SOURCE TERM] [TARGET TERM(optional)] [--force(optional)]', 'Delete term.' method_option "force", type: :boolean, default: false def delete(source_term, target_term=nil) if target_term glossary.delete(source_term, target_term) else glossary.delete_all(source_term, options["force"]) end rescue Logaling::CommandFailed, Logaling::TermError => e say e.message rescue Logaling::GlossaryNotFound => e say "Try 'loga new or register' first." end desc 'update [SOURCE TERM] [TARGET TERM] [NEW TARGET TERM], [NOTE(optional)]', 'Update term.' def update(source_term, target_term, new_target_term, note='') config = load_config_and_merge_options repository.index if repository.bilingual_pair_exists_and_has_same_note?(source_term, new_target_term, note, config["glossary"]) raise Logaling::TermError, "term '#{source_term}: #{new_target_term}' already exists in '#{config["glossary"]}'" end glossary.update(source_term, target_term, new_target_term, note) rescue Logaling::CommandFailed, Logaling::TermError => e say e.message rescue Logaling::GlossaryNotFound => e say "Try 'loga new or register' first." end desc 'lookup [TERM]', 'Lookup terms.' def lookup(source_term) config = load_config_and_merge_options repository.index terms = repository.lookup(source_term, config["source_language"], config["target_language"], config["glossary"]) unless terms.empty? max_str_size = terms.map{|term| term[:source_term].size}.sort.last run_pager terms.each do |term| target_string = "#{term[:target_term].bright}" target_string << "\t# #{term[:note]}" unless term[:note].empty? if repository.glossary_counts > 1 target_string << "\t" glossary_name = "(#{term[:glossary_name]})" if term[:glossary_name] == config["glossary"] target_string << glossary_name.foreground(:white).background(:green) else target_string << glossary_name end end source_string = term[:snipped_source_term].map{|word| word.is_a?(Hash) ? word[:keyword].bright : word }.join printf(" %-#{max_str_size+10}s %s\n", source_string, target_string) end else "source-term <#{source_term}> not found" end rescue Logaling::CommandFailed, Logaling::TermError => e say e.message end desc 'version', 'Show version.' def version say "logaling-command version #{Logaling::Command::VERSION}" end desc 'show', 'Show terms in glossary.' def show required_options = { "glossary" => "input glossary name '-g '", "source-language" => "input source-language code '-S '", "target-language" => "input target-language code '-T '" } config = load_config_and_merge_options(required_options) repository.index terms = repository.show_glossary(config["glossary"], config["source-language"], config["target-language"]) unless terms.empty? run_pager max_str_size = terms.map{|term| term[:source_term].size}.sort.last terms.each do |term| target_string = "#{term[:target_term]}" target_string << "\t# #{term[:note]}" unless term[:note].empty? printf(" %-#{max_str_size+10}s %s\n", term[:source_term], target_string) end else "glossary <#{config['glossary']}> not found" end rescue Logaling::CommandFailed, Logaling::GlossaryDBNotFound => e say e.message end desc 'list', 'Show glossary list.' def list repository.index glossaries = repository.list unless glossaries.empty? run_pager glossaries.each do |glossary| printf(" %s\n", glossary) end else "There is no registered glossary." end rescue Logaling::CommandFailed, Logaling::GlossaryDBNotFound => e say e.message end private def repository @repository ||= Logaling::Repository.new(LOGALING_HOME) end def glossary if @glossary @glossary else required_options = { "glossary" => "input glossary name '-g '", "source-language" => "input source-language code '-S '", "target-language" => "input target-language code '-T '" } config = load_config_and_merge_options(required_options) @glossary = Logaling::Glossary.new(config["glossary"], config["source-language"], config["target-language"]) end end def error(msg) STDERR.puts(msg) exit 1 end def load_config_and_merge_options(required={}) config_list ||= {} find_config.each{|type, path| config_list[type] = load_config(path)} global_config = config_list["global_config"] ? config_list["global_config"] : {} project_config = config_list["project_config"] ? config_list["project_config"] : {} config = merge_options(project_config, global_config) config = merge_options(options, config) required.each do |required_option, message| raise(Logaling::CommandFailed, message) unless config[required_option] end config end def merge_options(options, secondary_options) config ||={} config["glossary"] = options["glossary"] ? options["glossary"] : secondary_options["glossary"] config["source-language"] = options["source-language"] ? options["source-language"] : secondary_options["source-language"] config["target-language"] = options["target-language"] ? options["target-language"] : secondary_options["target-language"] config end def find_config config ||= {} config["project_config"] = File.join(find_dotfile, 'config') config["global_config"] = global_config_path if global_config_path config rescue Logaling::CommandFailed config ||= {} config["project_config"] = repository.config_path if repository.config_path config["global_config"] = global_config_path if global_config_path config end def load_config(config_path=nil) config ||= {} if config_path File.readlines(config_path).map{|l| l.chomp.split " "}.each do |option| key = option[0].sub(/^[\-]{2}/, "") value = option[1] config[key] = value end end config end def find_dotfile dir = Dir.pwd searched_path = [] while(dir) do path = File.join(dir, '.logaling') if File.exist?(path) return path else if dir != "/" searched_path << dir dir = File.dirname(dir) else raise(Logaling::CommandFailed, "Can't found .logaling in #{searched_path}") end end end end def global_config_path path = File.join(LOGALING_HOME, "config") File.exist?(path) ? path : nil end def write_config(config_path, config) File.open(config_path, 'w') do |fp| fp.puts "--glossary #{config['glossary']}" if config['glossary'] fp.puts "--source-language #{config['source-language']}" if config['source-language'] fp.puts "--target-language #{config['target-language']}" if config['target-language'] end end # http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby def run_pager return if ::RUBY_PLATFORM =~ /win32/ return unless STDOUT.tty? read, write = IO.pipe unless Kernel.fork # Child process STDOUT.reopen(write) STDERR.reopen(write) if STDERR.tty? read.close write.close return end # Parent process, become pager STDIN.reopen(read) read.close write.close ENV['LESS'] = 'FSRX' # Don't page if the input is short enough # wait until we have input before we start the pager Kernel.select [STDIN] pager = ENV['PAGER'] || 'less' exec pager rescue exec "/bin/sh", "-c", pager end end