kood 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +47 -0
- data/.yardopts +4 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +9 -0
- data/Rakefile +40 -0
- data/bin/kood +4 -0
- data/kood.gemspec +26 -0
- data/lib/kood-plugin-example.rb +10 -0
- data/lib/kood.rb +14 -0
- data/lib/kood/adapter/git.rb +56 -0
- data/lib/kood/adapter/user_config.rb +36 -0
- data/lib/kood/board.rb +160 -0
- data/lib/kood/card.rb +128 -0
- data/lib/kood/cli.rb +121 -0
- data/lib/kood/cli/board.rb +139 -0
- data/lib/kood/cli/card.rb +188 -0
- data/lib/kood/cli/edit.rb +40 -0
- data/lib/kood/cli/helpers/shell.rb +124 -0
- data/lib/kood/cli/helpers/table.rb +195 -0
- data/lib/kood/cli/list.rb +70 -0
- data/lib/kood/cli/plugin.rb +37 -0
- data/lib/kood/cli/switch.rb +10 -0
- data/lib/kood/core.rb +95 -0
- data/lib/kood/errors.rb +8 -0
- data/lib/kood/extensions/grit.rb +65 -0
- data/lib/kood/list.rb +36 -0
- data/lib/kood/version.rb +3 -0
- data/man/kood-board.1 +74 -0
- data/man/kood-board.1.html +150 -0
- data/man/kood-board.1.ronn +65 -0
- data/man/kood-card.1 +40 -0
- data/man/kood-card.1.html +140 -0
- data/man/kood-card.1.ronn +52 -0
- data/spec/kood/cli_spec.rb +280 -0
- data/spec/spec_helper.rb +67 -0
- data/test/kood/cli_test.rb +67 -0
- metadata +198 -0
data/.gitignore
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Numerous always-ignore extensions
|
2
|
+
*.diff
|
3
|
+
*.err
|
4
|
+
*.orig
|
5
|
+
*.log
|
6
|
+
*.rej
|
7
|
+
*.swo
|
8
|
+
*.swp
|
9
|
+
*.vi
|
10
|
+
*~
|
11
|
+
*.sass-cache
|
12
|
+
|
13
|
+
# OS or Editor folders
|
14
|
+
.DS_Store
|
15
|
+
Thumbs.db
|
16
|
+
.cache
|
17
|
+
.project
|
18
|
+
.settings
|
19
|
+
.tmproj
|
20
|
+
*.esproj
|
21
|
+
nbproject
|
22
|
+
|
23
|
+
# Dreamweaver added files
|
24
|
+
_notes
|
25
|
+
dwsync.xml
|
26
|
+
|
27
|
+
# Komodo
|
28
|
+
*.komodoproject
|
29
|
+
.komodotools
|
30
|
+
|
31
|
+
# Folders to ignore
|
32
|
+
.hg
|
33
|
+
.svn
|
34
|
+
.CVS
|
35
|
+
intermediate
|
36
|
+
publish
|
37
|
+
.idea
|
38
|
+
|
39
|
+
# Build script local files
|
40
|
+
build/buildinfo.properties
|
41
|
+
build/config/buildinfo.properties
|
42
|
+
|
43
|
+
# Ruby specifics
|
44
|
+
.rvmrc
|
45
|
+
Gemfile.lock
|
46
|
+
pkg/*
|
47
|
+
.yardoc
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 David Francisco
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
|
2
|
+
# k▥
|
3
|
+
|
4
|
+
Kood is a command-line tool for task boards. It's collaborative and works offline. All data and settings are stored in text files. The application uses Git to handle conflicts and keep track of changes.
|
5
|
+
The idea was to use Git as a database and take advantage of some of its features (such as conflict-resolution and branching) and distributed nature.
|
6
|
+
|
7
|
+
At least for now, this is a **pet project not intended for real use**, so expect some things to be broken. Nevertheless, more information will be added here in the future.
|
8
|
+
|
9
|
+

|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
# To run all tests at once, simply do:
|
4
|
+
# bundle exec rake test
|
5
|
+
#
|
6
|
+
# To run unit tests:
|
7
|
+
# bundle exec rake test:unit
|
8
|
+
#
|
9
|
+
# To turn specs with verbose output:
|
10
|
+
# bundle exec rake test:spec TESTOPTS="--verbose"
|
11
|
+
#
|
12
|
+
# For additional help:
|
13
|
+
# rake --tasks
|
14
|
+
#
|
15
|
+
require 'rake/testtask'
|
16
|
+
require 'bundler/gem_tasks'
|
17
|
+
|
18
|
+
namespace :test do
|
19
|
+
Rake::TestTask.new(:spec) do |t|
|
20
|
+
ENV["RACK_ENV"] = "test"
|
21
|
+
t.libs << "spec"
|
22
|
+
t.pattern = "spec/**/*_spec.rb"
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::TestTask.new(:unit) do |t|
|
26
|
+
ENV["RACK_ENV"] = "test"
|
27
|
+
t.libs << "test"
|
28
|
+
t.pattern = "test/**/*_test.rb"
|
29
|
+
end
|
30
|
+
|
31
|
+
task :all => [:unit, :spec]
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Run all test suites"
|
35
|
+
task :test => 'test:all'
|
36
|
+
|
37
|
+
desc "Uninstall the current version of kood"
|
38
|
+
task :uninstall do
|
39
|
+
puts `gem uninstall -x kood` # -x flag uninstalls executables too without confirmation
|
40
|
+
end
|
data/bin/kood
ADDED
data/kood.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/kood/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'kood'
|
6
|
+
gem.version = Kood::VERSION
|
7
|
+
gem.authors = ['David Francisco']
|
8
|
+
gem.email = 'kood@dmfranc.com'
|
9
|
+
gem.homepage = 'http://kood.dmfranc.com'
|
10
|
+
gem.summary = 'Work smarter -- An extensible CLI for git-backed taskboards.'
|
11
|
+
gem.platform = Gem::Platform::RUBY
|
12
|
+
gem.required_ruby_version = '>= 1.9.0'
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_development_dependency "ronn", "~> 0.7.0"
|
20
|
+
gem.add_development_dependency "rake", "~> 0.9.2"
|
21
|
+
gem.add_runtime_dependency "activesupport", "~> 3.2.9"
|
22
|
+
gem.add_runtime_dependency "thor", "~> 0.16.0"
|
23
|
+
gem.add_runtime_dependency "adapter-git", "~> 0.5.0"
|
24
|
+
gem.add_runtime_dependency "toystore", "~> 0.10.4"
|
25
|
+
gem.add_runtime_dependency "user_config"
|
26
|
+
end
|
data/lib/kood.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'kood/adapter/git'
|
2
|
+
require 'kood/adapter/user_config'
|
3
|
+
require 'kood/extensions/grit'
|
4
|
+
require 'kood/errors'
|
5
|
+
require 'kood/version'
|
6
|
+
|
7
|
+
module Kood
|
8
|
+
autoload :Card, 'kood/card'
|
9
|
+
autoload :List, 'kood/list'
|
10
|
+
autoload :Board, 'kood/board'
|
11
|
+
|
12
|
+
require 'kood/cli'
|
13
|
+
require 'kood/core'
|
14
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'adapter-git'
|
2
|
+
|
3
|
+
module Adapter
|
4
|
+
# This reopens the git adapter module and changes part of its behavior.
|
5
|
+
#
|
6
|
+
# It adds support for custom file extensions. Like the original git adapter, data is
|
7
|
+
# persisted in YAML but, if there is a key named `content`, then the content will be
|
8
|
+
# saved in plain text and a YAML front matter block is added to the top of the file.
|
9
|
+
#
|
10
|
+
module Git
|
11
|
+
attr_accessor :file_extension
|
12
|
+
|
13
|
+
# Transform a key into a filename
|
14
|
+
# @return [String] the name of the file
|
15
|
+
def key_for(key)
|
16
|
+
key = super + "." + (@file_extension || 'yml')
|
17
|
+
File.join(*[options[:path], key].compact)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Encode data to be written
|
21
|
+
# @return [String] data in `yaml` format or `yaml frontmatter + text`
|
22
|
+
def encode(value)
|
23
|
+
# If it contains a `content` attribute, other data is in a YAML front matter block
|
24
|
+
if value.key? "content"
|
25
|
+
content = value["content"].to_s
|
26
|
+
value.delete("content")
|
27
|
+
|
28
|
+
data = value.to_yaml
|
29
|
+
data += "---\n\n"
|
30
|
+
data += content
|
31
|
+
data
|
32
|
+
else
|
33
|
+
# Standard YAML file
|
34
|
+
value.to_yaml
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Decode data to be read
|
39
|
+
# @return [Hash] data
|
40
|
+
def decode(value)
|
41
|
+
# Check if a YAML front matter block is present
|
42
|
+
yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
43
|
+
if value =~ yaml_regex
|
44
|
+
content = value.sub(yaml_regex, "")
|
45
|
+
data = YAML.load($1)
|
46
|
+
data["content"] = content
|
47
|
+
data
|
48
|
+
else
|
49
|
+
# Standard YAML file
|
50
|
+
data = YAML.load(value)
|
51
|
+
end
|
52
|
+
rescue => e
|
53
|
+
raise "YAML Exception: #{ e.message }"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'user_config'
|
2
|
+
|
3
|
+
module Adapter
|
4
|
+
# Simple adapter that uses the `user_config` gem to
|
5
|
+
# persist data into a YAML configuration file.
|
6
|
+
#
|
7
|
+
module UserConfigFile
|
8
|
+
def read(key, options = nil)
|
9
|
+
config[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(key, attributes, options = nil)
|
13
|
+
config[key] = attributes
|
14
|
+
config.save
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key, options = nil)
|
18
|
+
config.delete(key)
|
19
|
+
config.save
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear(options = nil)
|
23
|
+
config.clear
|
24
|
+
config.save
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def config
|
30
|
+
@@conf ||= UserConfig.new Kood::KOOD_PATH
|
31
|
+
@@conf[Kood.config_path]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Adapter.define(:user_config, Adapter::UserConfigFile)
|
data/lib/kood/board.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'toystore'
|
2
|
+
|
3
|
+
module Kood
|
4
|
+
class Board
|
5
|
+
include Toy::Store
|
6
|
+
|
7
|
+
# Associations
|
8
|
+
list :lists, List
|
9
|
+
|
10
|
+
# Attributes
|
11
|
+
attribute :custom_repo, String, virtual: true
|
12
|
+
|
13
|
+
# Validations
|
14
|
+
validates_format_of :id, :with => /^[A-Za-z\d_\-]+$/
|
15
|
+
|
16
|
+
# Observers
|
17
|
+
before_create :id_is_unique?
|
18
|
+
before_create do |board|
|
19
|
+
if custom_repo # Support external branches
|
20
|
+
Kood.config.custom_repos[board.id] = custom_repo
|
21
|
+
Kood.config.save!
|
22
|
+
end
|
23
|
+
board.update_adapter # To create a board we need to change the current branch
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get(id)
|
27
|
+
board = nil
|
28
|
+
Board.with_adapter(id, root(id)) do
|
29
|
+
board = super
|
30
|
+
board.update_adapter unless board.nil?
|
31
|
+
end
|
32
|
+
return board
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.get!(id)
|
36
|
+
super rescue raise NotFound, "The specified board does not exist."
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.current
|
40
|
+
get Kood.config.current_board_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.current!
|
44
|
+
current or raise Error, "No board has been selected yet."
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_current?
|
48
|
+
Board.current.eql? self
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete
|
52
|
+
client.with_stash do
|
53
|
+
client.git.checkout('master') if client.on_branch? id
|
54
|
+
Kood.config.unselect_board if is_current?
|
55
|
+
Kood.config.custom_repos.delete(id)
|
56
|
+
client.git.branch({ :D => true }, id)
|
57
|
+
end # Since we deleted the branch, the default behavior is not necessary
|
58
|
+
end
|
59
|
+
|
60
|
+
def cards
|
61
|
+
lists.inject([]) { |cards, list| cards += list.cards }
|
62
|
+
end
|
63
|
+
|
64
|
+
def select
|
65
|
+
Kood.config.select_board(id)
|
66
|
+
end
|
67
|
+
|
68
|
+
def pull(remote = 'origin')
|
69
|
+
client.with_stash_and_branch(id) do
|
70
|
+
client.git.pull({ process_info: true }, remote, id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def push(remote = 'origin')
|
75
|
+
client.with_stash_and_branch(id) do
|
76
|
+
client.git.push({ process_info: true }, remote, id)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def sync(remote = 'origin')
|
81
|
+
exit_status, out, err = pull(remote)
|
82
|
+
exit_status.zero? ? push(remote) : [exit_status, out, err]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a list of git users. It will search in other boards or in the rest of the
|
86
|
+
# git repository if this is an external board. The result also includes the users that
|
87
|
+
# already made commits to this board
|
88
|
+
def potential_members(options = { all_branches: true })
|
89
|
+
members = client.git.log(all: options[:all_branches], format: '%aN <%cE>').split("\n").uniq
|
90
|
+
members.map! { |m| m.force_encoding("UTF-8") }
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_potential_member_by_partial_name_or_email(search_param)
|
94
|
+
# Find partial (and exact) matches
|
95
|
+
matches = potential_members.select do |u|
|
96
|
+
# `search_param` may be a normal string or a string representing a regular expression
|
97
|
+
u.match /#{ search_param }/i or u.downcase.include?(search_param.downcase)
|
98
|
+
end
|
99
|
+
return matches.first if matches.length <= 1
|
100
|
+
|
101
|
+
# Refine the search and retrieve only exact matches
|
102
|
+
exact_matches = matches.select { |u| u.casecmp(search_param).zero? }
|
103
|
+
return exact_matches.length == 1 ? exact_matches.first : nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def published?
|
107
|
+
client.remotes.any? { |b| b.name =~ /\/#{ id }$/ }
|
108
|
+
end
|
109
|
+
|
110
|
+
def external?
|
111
|
+
Kood.config.custom_repos[id].present?
|
112
|
+
end
|
113
|
+
|
114
|
+
def root
|
115
|
+
Board.root(id)
|
116
|
+
end
|
117
|
+
|
118
|
+
def adapter
|
119
|
+
@adapter || self.class.adapter
|
120
|
+
end
|
121
|
+
|
122
|
+
# Set adapter for this instance
|
123
|
+
def update_adapter
|
124
|
+
@adapter = Adapter[:git].new(Kood.repo(root), branch: id)
|
125
|
+
end
|
126
|
+
|
127
|
+
def with_context
|
128
|
+
Board.with_adapter(id, root) do
|
129
|
+
yield self
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# ToyStore supports adapters per model but this program needs an adapter per instance
|
136
|
+
def self.with_adapter(branch, root)
|
137
|
+
current_client = adapter.client
|
138
|
+
current_options = adapter.options
|
139
|
+
|
140
|
+
adapter :git, Kood.repo(root), branch: branch
|
141
|
+
List.with_adapter(branch, root) do
|
142
|
+
yield
|
143
|
+
end
|
144
|
+
ensure
|
145
|
+
adapter :git, current_client, current_options
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.root(board_id)
|
149
|
+
Kood.config.custom_repos[board_id] or Kood.root
|
150
|
+
end
|
151
|
+
|
152
|
+
def client
|
153
|
+
adapter.client
|
154
|
+
end
|
155
|
+
|
156
|
+
def id_is_unique?
|
157
|
+
raise NotUnique, "A board with this ID already exists." unless Board.get(id).nil?
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|