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.
@@ -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
@@ -0,0 +1,4 @@
1
+ --markup markdown
2
+ -
3
+ LICENSE
4
+ README.md
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
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.
@@ -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
+ ![Work smarter](//raw.github.com/dmfrancisco/kood/media/promo.gif "Kood Promo")
@@ -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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require "kood"
3
+
4
+ Kood::CLI.start
@@ -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
@@ -0,0 +1,10 @@
1
+ module Kood
2
+ module Plugin
3
+ class Example < Thor
4
+ desc "foo", "An example command"
5
+ def foo
6
+ puts "Hello from example"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -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)
@@ -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