From f90bbee818169a11d16df6bc5de4dac753d3555d Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Sat, 28 Dec 2013 11:55:48 +0100 Subject: [PATCH] Rewrite in ruby. --- Gemfile | 0 Kookfile | 5 + bin/kook | 23 ++++ bin/kook.rb | 164 ---------------------------- lib/kook.rb | 7 ++ lib/kook/cli.rb | 242 +++++++++++++++++++++++++++++++++++++++++ lib/kook/config.rb | 105 ++++++++++++++++++ lib/kook/exceptions.rb | 4 + lib/kook/project.rb | 71 ++++++++++++ lib/kook/view.rb | 39 +++++++ test.sh | 18 +++ 11 files changed, 514 insertions(+), 164 deletions(-) create mode 100644 Gemfile create mode 100644 Kookfile create mode 100755 bin/kook delete mode 100755 bin/kook.rb create mode 100644 lib/kook.rb create mode 100644 lib/kook/cli.rb create mode 100644 lib/kook/config.rb create mode 100644 lib/kook/exceptions.rb create mode 100644 lib/kook/project.rb create mode 100644 lib/kook/view.rb create mode 100755 test.sh diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..e69de29 diff --git a/Kookfile b/Kookfile new file mode 100644 index 0000000..549921a --- /dev/null +++ b/Kookfile @@ -0,0 +1,5 @@ +--- +:project: kook2 +:description: +:path: /home/warbrain/src/_Glenux/kotam +:views: [] diff --git a/bin/kook b/bin/kook new file mode 100755 index 0000000..469902e --- /dev/null +++ b/bin/kook @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +# vim: set syntax=ruby: + +#require 'bundler/setup' +require 'yaml' +require 'singleton' +require 'pry' +require 'colorize' +require 'pp' + +DATA_DIR = File.expand_path( + File.join(File.dirname(__FILE__),'..') +) + +VERSION = "0.3" + +$:.insert 0, File.join(DATA_DIR, 'lib') + +require 'thor' +require 'kook' + +Kook::CLI::Main.start(ARGV) + diff --git a/bin/kook.rb b/bin/kook.rb deleted file mode 100755 index 5c5df4b..0000000 --- a/bin/kook.rb +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env ruby - -require 'singleton' -require 'yaml' -require 'pp' - -module Kook - class Project - attr_reader :name, :path - attr_accessor :description - PROJECT_NAME_MIN_SIZE = 4 - PROJECT_NAME_MAX_SIZE = 12 - - def initialize project_name - self.class.validate_name project_name - @name = project_name - @description = nil - @path = nil - @views = {} - end - - def path= path - # FIXME: validate current path exists - # - if not (File.exist? path and File.directory? path) then - raise "PathDoesNotExist #{path}" - end - @path = path - end - - def add_view view - raise "ExistingView #{view.name}" if @views.has_key? view.name - @views[view.name] = view - end - - def remove_view view_name - View.validate_name view_name - return @view.delete(view_name) - end - - def to_hash - return { - project: @name, - description: @description, - path: @path, - views: @views.values.map{ |v| v.to_hash } - } - end - - def from_hash project_hash - @name = project_hash[:project] - @description = project_hash[:description] - @path = project_hash[:path] - project_hash[:views].each do |hash_view| - view = View.new do |v| - v.from_hash hash_view - end - add_view view - end - end - - def self.validate_name name - raise "TooShortProjectIdentifier" if name.size < Project::PROJECT_NAME_MIN_SIZE - raise "TooLongProjectIdentifier" if name.size > Project::PROJECT_NAME_MAX_SIZE - if not name =~ /^\w+$/ then - raise "BadProjectIdentifier #{name}" - end - return true - end - end - - class View - attr_reader :name, :path - attr_accessor :description - VIEW_NAME_MIN_SIZE = 4 - VIEW_NAME_MAX_SIZE = 12 - - def initialize name - self.class.validate_name name - @name = name - @path = nil - @commands = {} - end - - def self.validate_name name - raise "TooShortViewIdentifier" if name.size < View::VIEW_NAME_MIN_SIZE - raise "TooLongViewIdentifier" if name.size > View::VIEW_NAME_MAX_SIZE - if not name =~ /^[\w-]+$/ then - raise "BadViewIdentifier #{name}" - end - return true - end - - def to_hash - return { - view: @name, - path: @path, - commands: @commands.values - } - end - - def from_hash view_hash - @name = view_hash[:view] - @path = view_hash[:path] - @commands = view_hash[:commands] - end - end - - class Command - end - - class Config - include Singleton - - def create_project project_name - raise "ExistingProject" if @projects.has_key? project_name - - @projects[project_name] = Project.new project_name - end - - def create_view project_name, view_name - Project.validate_name project_name - View.validate_name view_name - raise "MissingProject" if not @projects.has_key? project_name - - view = View.new view_name - @projects[project_name].add_view view - end - - def to_yaml - return { - global: {}, - projects: @projects.values.map{ |p| p.to_hash } - }.to_yaml - end - - private - - def initialize - @projects = {} - super - end - end - -end - -class Test - def self.test_project_create - config = Kook::Config.instance - config.create_project 'proj' - end - - def self.test_view_create - config = Kook::Config.instance - config.create_view 'proj', 'proj-root' - config.create_view 'proj', 'proj-base' - config.create_view 'proj', 'proj-3' - - puts config.to_yaml - end -end - -Test.test_project_create -Test.test_view_create diff --git a/lib/kook.rb b/lib/kook.rb new file mode 100644 index 0000000..cee1c0f --- /dev/null +++ b/lib/kook.rb @@ -0,0 +1,7 @@ + +require 'kook/exceptions' +require 'kook/view' +require 'kook/project' +require 'kook/config' +require 'kook/cli' + diff --git a/lib/kook/cli.rb b/lib/kook/cli.rb new file mode 100644 index 0000000..1d9b35d --- /dev/null +++ b/lib/kook/cli.rb @@ -0,0 +1,242 @@ +require 'thor' + +module Kook + module CLI + module KookHelper + + def fail_for exception, params=nil + STDERR.puts "ERROR(#{exception.class}) : #{exception}" + STDERR.puts exception.backtrace + exit 1 + end + end + + + class OldConfig + include Singleton + include KookHelper + + DEFAULT_CONFIG = { + 'global' => {}, # (ConfigKey_str => ConfigVal_str ) list + 'projects' => {}, # Project_str list + 'views' => {}, # (Project_str => View_str) list + 'commands' => {} # (Project_str => View_str) => Command list + } + + def initialize + # load file + @config = DEFAULT_CONFIG + load_main + @config + end + + + + def [] key + @config[key] + end + end + + class Project < Thor + include KookHelper + + desc "detect", "Detect current project" + def detect + project_name = current_project.nil? ? "-none-" : current_project + say "Current project is #{project_name}." + end + + desc "list", "List projects" + def list + config.each_project do |project_name,project| + pp project_name + exist = File.exist? project.path + display_path = ( + path.clone + .gsub!(/#{ENV['HOME']}/,'~') + .send(exist ? :green : :red) + ) + puts "%- 24s %s" % [project_name, display_path] + end + end + + # FIXME: option for alternative path + desc "add PROJECT [PATH]", "Register new project" + def add project_name, project_path=nil + + if project_path.nil? then + project_path = Dir.pwd + end + project_path = File.expand_path project_path + config.add_project project_name, project_path + + say "Project #{project_name} registered on #{project_path}." + rescue Exception => e + fail_for e, project: project_name, path: project_path + end + + desc "rm PROJECT", "Unregister existing project" + def rm project + config['projects'].delete project + config.save_main + say "Project #{project} unregistered." + end + + desc "edit PROJECT", "Open editor on project file" + def edit project + if config['projects'].has_key? project then + project_config_path = File.join CONFIG_DIR, "Kookfile" + system "%s %s" % [ENV['EDITOR'], project_config_path] + else + raise "Project #{project} not found" + end + end + # TODO: editcopy project to another name + base path + # TODO: copy project to another name + base path + # + private + + def config + Config.instance + end + end + + class View < Thor + include KookHelper + + desc "list [PROJECT]", "List view for a project" + def list project=nil + project ||= current_project + validate_project_exists project + + if config['views'].has_key? project then + return if config['views'][project].nil? + config['views'][project].each do |view,path| + puts "%- 24s %s" % [view, path] + + next if config['commands'][project].nil? or \ + config['commands'][project][view].nil? + + config['commands'][project][view].each_index do |idx| + puts " % 4d. %s" % [ + idx, + config['commands'][project][view][idx] + ] + end + end + end + end + + desc "add PROJECT VIEW", "Register new view" + def add project, view, path=nil + if path.nil? then + path = Dir.pwd + end + project_rootdir = config['projects'][project] + # simplify if current dir is a subdir of project base + if path == project_rootdir then + path = '.' + else + path.gsub!(/^#{project_rootdir}\//,'') + end + + if not config['views'].has_key? project then + config['views'][project] = {} + elsif config['views'][project].nil? then + config['views'][project] = {} + end + #binding.pry + config['views'][project][view] = path + config.save_main + end + + desc "rm PROJECT VIEW", "Unregister existing view on project" + def rm project, view + # FIXME: validate project existance + # FIXME: validate view existance + config['views'][project].delete view + config.save_main + end + + private + + def config + Config.instance + end + end + + # FIXME: add helper validating project name + # FIXME: add helper validating vie name for project + class Command < Thor + desc "add PROJECT VIEW COMMAND", "Add command for view " + def add project, view, command + unless config['commands'].has_key? project then + config['commands'][project] = {} + end + if config['commands'][project].nil? then + config['commands'][project] = {} + end + unless config['commands'][project].has_key? view then + config['commands'][project][view] = [] + end + config['commands'][project][view] << command + config.save_main + end + + desc "rm PROJECT VIEW", "Remove command for view" + def rm project, view, index + raise NotImplementedError + end + + private + + def config + Config.instance + end + end + + class Main < Thor + include KookHelper + + desc "project SUBCOMMAND [options]", "Commands for managing projects" + subcommand "project", CLI::Project + + desc "view SUBCOMMAND [options]", "Commands for managing view" + subcommand "view", CLI::View + + desc "command SUBCOMMAND [options]", "Commands for managing commands" + subcommand "command", CLI::Command + + desc "fire PROJECT", "Run project environment" + def fire project + validate_project_name project + validate_project_exists project + pp config + + raise "No view defined for #{project}" if not config['views'].has_key? project + config['views'][project].each do |view,view_path| + project_path = config['projects'][project] + target = ENV['KONSOLE_DBUS_SERVICE'] || 'org.kde.konsole' + session=`qdbus #{target} /Konsole newSession`.strip + + system "qdbus org.kde.konsole /Sessions/#{session} sendText \"cd #{project_path}\n\"" + system "qdbus org.kde.konsole /Sessions/#{session} sendText \"cd #{view_path}\n\"" + system "qdbus org.kde.konsole /Sessions/#{session} sendText \"clear\n\"" + system "qdbus org.kde.konsole /Sessions/#{session} setTitle 1 \"#{view}\"" + next unless config['commands'][project].has_key? view + config['commands'][project][view].each do |command| + system "qdbus org.kde.konsole /Sessions/#{session} sendText \"#{command}\"" + system "qdbus org.kde.konsole /Sessions/#{session} sendText \"\n\"" + end + end + end + + + private + + def config + Config.instance + end + end + end +end diff --git a/lib/kook/config.rb b/lib/kook/config.rb new file mode 100644 index 0000000..dc977fe --- /dev/null +++ b/lib/kook/config.rb @@ -0,0 +1,105 @@ +module Kook + class Config + include Singleton + + CONFIG_DIR = File.join ENV['HOME'], '.config', 'kook' + CONFIG_FILE = File.join CONFIG_DIR, 'config.yml' + + class ExistingProject < RuntimeError ; end + class MissingProject < RuntimeError ; end + + def add_project project_name, project_path=nil + raise ExistingProject if @projects.has_key? project_name + + project_data = Project.new project_name + project_data.path = project_path + @projects[project_name] = project_data + save + end + + def each_project + @projects.each do |p,data| + yield p,data + end + end + + def add_view project_name, view_name + Project.validate_name project_name + View.validate_name view_name + raise MissingProject if not @projects.has_key? project_name + + view = View.new view_name + @projects[project_name].add_view view + save + end + + def load + yaml = YAML::load_file CONFIG_FILE + + yaml['projects'].each do |project_name,project_path| + pp project_path + #project_path = @config['projects'][project] + project_file = kook_file_for project_path + + if File.exist? project_file then + subconfig = YAML::load_file project_file + next if not subconfig + @config['views'][project] = subconfig['views'] + @config['commands'][project] = subconfig['commands'] + end + end + end + + def save + if not File.exist? CONFIG_DIR then + FileUtils.mkdir_p CONFIG_DIR + end + + @projects.each do |project_name,project_data| + # FIXME: test if project configuration is dirty + #pp project_data + project_file = File.join project_data.path, "Kookfile" + + File.open(project_file, "w") do |file| + file.write project_data.to_hash.to_yaml + end + end + + File.open(CONFIG_FILE, "w") do |file| + file.write to_yaml + end + self + end + + def current_project + current_dir = Dir.pwd + @projects.each do |project_name,project| + if current_dir =~ /^#{project.path}/ then + return project_name + end + end + return nil + end + + private + + def kook_file_for project_path + kook_files = Dir.glob(File.join(project_path, 'Kookfile')) + raise MissingProjectFile if kook_files.empty? + kook_files.first + end + + def to_yaml + return { + 'global' => {}, + 'projects' => @projects.values.map{ |p| { p.name => p.path } } + }.to_yaml + end + + def initialize + super + @projects = {} + self.load + end + end +end diff --git a/lib/kook/exceptions.rb b/lib/kook/exceptions.rb new file mode 100644 index 0000000..e4004b0 --- /dev/null +++ b/lib/kook/exceptions.rb @@ -0,0 +1,4 @@ + +module Kook + class MissingProjectConfiguration < RuntimeError ; end +end diff --git a/lib/kook/project.rb b/lib/kook/project.rb new file mode 100644 index 0000000..ee885f9 --- /dev/null +++ b/lib/kook/project.rb @@ -0,0 +1,71 @@ + +require 'kook/exceptions' + +module Kook + class Project + attr_reader :name, :path + attr_accessor :description + + class MissingProjectFile < RuntimeError ; end + class InvalidProjectName < RuntimeError ; end + PROJECT_NAME_MIN_SIZE = 4 + PROJECT_NAME_MAX_SIZE = 12 + + def initialize project_name + self.class.validate_name project_name + @name = project_name + @description = nil + @path = nil + @views = {} + end + + def path= path + # FIXME: validate current path exists + # + if not (File.exist? path and File.directory? path) then + raise "PathDoesNotExist #{path}" + end + @path = path + end + + def add_view view + raise "ExistingView #{view.name}" if @views.has_key? view.name + @views[view.name] = view + end + + def remove_view view_name + View.validate_name view_name + return @view.delete(view_name) + end + + def to_hash + return { + project: @name, + description: @description, + path: @path, + views: @views.values.map{ |v| v.to_hash } + } + end + + def from_hash project_hash + @name = project_hash[:project] + @description = project_hash[:description] + @path = project_hash[:path] + project_hash[:views].each do |hash_view| + view = View.new do |v| + v.from_hash hash_view + end + add_view view + end + end + + def self.validate_name name + raise "TooShortProjectIdentifier" if name.size < Project::PROJECT_NAME_MIN_SIZE + raise "TooLongProjectIdentifier" if name.size > Project::PROJECT_NAME_MAX_SIZE + if not name =~ /^\w+$/ then + raise "BadProjectIdentifier #{name}" + end + return true + end + end +end diff --git a/lib/kook/view.rb b/lib/kook/view.rb new file mode 100644 index 0000000..aae7c54 --- /dev/null +++ b/lib/kook/view.rb @@ -0,0 +1,39 @@ + +module Kook + class View + attr_reader :name, :path + attr_accessor :description + VIEW_NAME_MIN_SIZE = 4 + VIEW_NAME_MAX_SIZE = 12 + + def initialize name + self.class.validate_name name + @name = name + @path = nil + @commands = {} + end + + def self.validate_name name + raise "TooShortViewIdentifier" if name.size < View::VIEW_NAME_MIN_SIZE + raise "TooLongViewIdentifier" if name.size > View::VIEW_NAME_MAX_SIZE + if not name =~ /^[\w-]+$/ then + raise "BadViewIdentifier #{name}" + end + return true + end + + def to_hash + return { + view: @name, + path: @path, + commands: @commands.values + } + end + + def from_hash view_hash + @name = view_hash[:view] + @path = view_hash[:path] + @commands = view_hash[:commands] + end + end +end diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..188c018 --- /dev/null +++ b/test.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +PATH=./bin:$PATH + +set -e + +fail() { + set +x + echo "ERROR: $*" + exit 1 +} + +set -x + +kook project add kook1 . || fail "test1" +kook project add kook2 || fail "test2" +kook project list || fail "test3" +