temjin 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # temjin
2
+
3
+ cli tool for talking to trello
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+ require 'rake/notes/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.test_files = ['test/main.rb']
9
+ t.verbose = true
10
+ t.warning = false
11
+ end
12
+
13
+ RuboCop::RakeTask.new
14
+
15
+ desc "Runs Rubocop style checker with xml output for Jenkins"
16
+ task 'rubocop:jenkins' do
17
+ system("bundle exec rubocop \
18
+ --require rubocop/formatter/checkstyle_formatter \
19
+ --format Robocop::Formatter::CheckstyleFormatter \
20
+ --no-color \
21
+ --out rubocop.xml")
22
+ end
23
+
24
+ task :default => %i[test rubocop]
25
+
26
+ CLEAN << 'coverage'
data/authors.yml ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ - Adam Price
data/bin/temjin ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'temjin'
4
+ begin
5
+ Temjin::MainCommand.run
6
+ rescue StandardError => e
7
+ puts e.message
8
+ exit 1
9
+ end
@@ -0,0 +1,26 @@
1
+ module Temjin
2
+ class CardCommand
3
+ class AddCommand < Temjin::TrelloAPICommand
4
+ parameter 'BOARD', 'board name'
5
+ parameter 'LIST', 'list name'
6
+ parameter '[CARD_NAME]', 'name of card to add'
7
+
8
+ option '--desc', 'DESC', 'card description'
9
+
10
+ def execute
11
+ # TODO: should also be included in the command setup
12
+ user = find_user(config.username)
13
+
14
+ list_id = find_list(user, board, list).id
15
+
16
+ create_options = {}
17
+ create_options[:list_id] = list_id
18
+ create_options[:name] = card_name if card_name
19
+ create_options[:desc] = desc if desc
20
+
21
+ new_card = Trello::Card.create(create_options)
22
+ Formatador.display_line("[[yellow]#{new_card.id}[/]] added")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Temjin
2
+ class CardCommand
3
+ class ListCommand < Temjin::TrelloAPICommand
4
+ # TODO: eventually make BOARD and LIST optional, but for now we need them
5
+ parameter 'BOARD', 'trello board'
6
+ parameter 'LIST', 'trello list'
7
+
8
+ def execute
9
+ # TODO: should also be included in the command setup
10
+ user = find_user(config.username)
11
+
12
+ cards = find_list(user, board, list).cards
13
+
14
+ cards = cards.map do |c|
15
+ {:id => c.id,
16
+ :name => c.name,
17
+ :desc => c.desc}
18
+ end
19
+
20
+ cards.each do |card|
21
+ Formatador.display_line("[yellow]#{card[:id]}[/] - #{card[:name]}")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ module Temjin
2
+ class CardCommand
3
+ class ShowCommand < Temjin::TrelloAPICommand
4
+ parameter 'CARD_ID', 'card ID'
5
+
6
+ def execute
7
+ card = find_card(card_id)
8
+ list = find_list_by_id(card.list_id)
9
+ board = find_board(card.board_id)
10
+ members = card.member_ids.map { |member| find_user(member).username }
11
+ labels = card.labels.map { |label| label.name }
12
+
13
+ Formatador.display_line("# [bold]#{card.name}[/]")
14
+ Formatador.indent do
15
+ Formatador.display_line("-> #{list.name} --> #{board.name}")
16
+ end
17
+ Formatador.display_line("[[yellow]#{card.id}[/]] #{card.short_url} #{card.attachments.empty? ? '' : '+attachments '}- #{card.closed? ? '[_red_]closed[/]' : '[green]open[/]'}")
18
+ Formatador.display_line(members.map { |m| "[cyan]@#{m}[/]" }.join(', ')) unless members.empty?
19
+ Formatador.display_line("{ #{labels.map { |l| "[purple]#{l}[/]" }.join(', ')} }") unless labels.empty?
20
+ Formatador.display_line("due on [red]#{card.due.getlocal.ctime}[/]") unless card.due.nil?
21
+ unless card.checklists.empty?
22
+ Formatador.display_line("---")
23
+ card.checklists.each do |checklist|
24
+ Formatador.display_line(checklist.name)
25
+ Formatador.indent do
26
+ checklist.check_items.each do |checklist_item|
27
+ checkbox = if checklist_item['state'].match?(/^complete$/)
28
+ "\u2713".force_encoding('utf-8')
29
+ else
30
+ ' '
31
+ end
32
+ Formatador.display_line("[#{checkbox}] #{checklist_item['name']}")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ unless card.desc.empty?
38
+ Formatador.display_line("---")
39
+ Formatador.display_line(card.desc) # FIXME: what happens if the desc is multiple lines?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ module Temjin
2
+ class CardCommand
3
+ class UpdateCommand < Temjin::TrelloAPICommand
4
+ parameter 'CARD_ID', 'card ID'
5
+
6
+ option '--name', 'NAME', 'card name'
7
+ option '--desc', 'DESC', 'card description'
8
+
9
+ def execute
10
+ card = find_card(card_id)
11
+
12
+ card.name = name if name
13
+ card.desc = desc if desc
14
+
15
+ card.save
16
+ Formatador.display_line("[[yellow]#{card.id}[/]] updated")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Temjin
2
+ class CardCommand < Clamp::Command; end
3
+ end
@@ -0,0 +1,46 @@
1
+ module Temjin
2
+ class Command < Clamp::Command
3
+ def initialize(invocation_path, context = {})
4
+ @config = context[:config] if context.key?(:config)
5
+ super(invocation_path, context)
6
+ end
7
+
8
+ def config
9
+ @config ||= Config.new
10
+ end
11
+ end
12
+
13
+ class TrelloAPICommand < Temjin::Command
14
+ def initialize(invocation_path, context = {})
15
+ super(invocation_path, context)
16
+ trello_connection
17
+ end
18
+
19
+ def trello_connection
20
+ Trello.configure do |c|
21
+ c.developer_public_key = config.key
22
+ c.member_token = config.token
23
+ end
24
+ end
25
+
26
+ def find_board(board_id)
27
+ Trello::Board.find(board_id)
28
+ end
29
+
30
+ def find_list(user, board_name, list_name)
31
+ user.boards.detect { |b| b.name.match(board_name) }.lists.detect { |l| l.name.match(list_name) }
32
+ end
33
+
34
+ def find_list_by_id(list_id)
35
+ Trello::List.find(list_id)
36
+ end
37
+
38
+ def find_user(username)
39
+ Trello::Member.find(username)
40
+ end
41
+
42
+ def find_card(card_id)
43
+ Trello::Card.find(card_id)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,42 @@
1
+ module Temjin
2
+ class Config
3
+ attr_accessor :username, :key, :token
4
+
5
+ attr_reader :file_path
6
+
7
+ def initialize(options = {})
8
+ options = {:path => File.join(ENV['HOME'], '.config', 'temjin.yml'),
9
+ :create => false}.merge(options.select { |k, _| %i[path create].include?(k) })
10
+
11
+ @file_path = options[:path]
12
+
13
+ create_or_verify_file(options[:create])
14
+
15
+ config = YAML.load_file(@file_path) || {}
16
+ @username = config.dig('username')
17
+ @key = config.dig('key')
18
+ @token = config.dig('token')
19
+
20
+ verify_format unless options[:create]
21
+ end
22
+
23
+ def create_or_verify_file(create_bool = false)
24
+ if create_bool
25
+ FileUtils.touch(file_path)
26
+ else
27
+ fail Temjin::ConfigurationNotFoundError unless File.exist?(file_path)
28
+ end
29
+ end
30
+
31
+ def verify_format
32
+ malformed = username.nil? || key.nil? || token.nil?
33
+ fail Temjin::ConfigurationNotFoundError if malformed
34
+ end
35
+
36
+ def save!
37
+ File.open(file_path, 'w') do |f|
38
+ f.write({'username' => username, 'key' => key, 'token' => token}.to_yaml)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ module Temjin
2
+ class ConfigCommand
3
+ class InitCommand < Temjin::Command
4
+ def config # override
5
+ @config ||= Config.new(:create => true)
6
+ end
7
+
8
+ def prompt(message, default = nil)
9
+ print(message)
10
+ result = $stdin.gets.chomp
11
+ result.empty? ? default : result
12
+ end
13
+
14
+ def execute
15
+ # TODO: use Formatador for all output
16
+ puts 'Go to https://trello.com/app-key to get your key and token.'
17
+ puts
18
+
19
+ config.username = prompt('username: ')
20
+ config.key = prompt('key: ')
21
+ config.token = prompt('token: ')
22
+
23
+ config.save!
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Temjin
2
+ class ConfigCommand
3
+ class ShowCommand < Temjin::Command
4
+ def execute
5
+ # TODO: use formatador for all output
6
+ puts "key: #{config.key}"
7
+ puts "token: #{config.token}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Temjin
2
+ class ConfigCommand < Clamp::Command; end
3
+ end
@@ -0,0 +1,8 @@
1
+ module Temjin
2
+ class ConfigurationNotFoundError < StandardError
3
+ def initialize(message = nil)
4
+ message ||= "Configuration not found. Please run `temjin config init`."
5
+ super(message)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Temjin
2
+ class ListCommand < Clamp::Command; end
3
+ end
@@ -0,0 +1,8 @@
1
+ module Temjin
2
+ class MainCommand < Clamp::Command
3
+ option ['-v', '--version'], :flag, 'display version' do
4
+ puts Temjin.version
5
+ exit
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Temjin
2
+ def self.version
3
+ @version ||= Gem::Version.new('0.0.1')
4
+ end
5
+ end
data/lib/temjin.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'formatador'
2
+ require 'clamp'
3
+ require 'trello' # ruby-trello
4
+
5
+ # beta feature
6
+ Clamp.allow_options_after_parameters = true
7
+
8
+ require 'temjin/configuration_not_found_error'
9
+ require 'temjin/config'
10
+ require 'temjin/command'
11
+
12
+ require 'temjin/config_command'
13
+ require 'temjin/config_command/show_command'
14
+ require 'temjin/config_command/init_command'
15
+ require 'temjin/card_command'
16
+ require 'temjin/card_command/list_command'
17
+ require 'temjin/card_command/show_command'
18
+ require 'temjin/card_command/add_command'
19
+ require 'temjin/card_command/update_command'
20
+ require 'temjin/list_command'
21
+ require 'temjin/main_command'
22
+
23
+ # cli structure
24
+ module Temjin
25
+ ConfigCommand.subcommand("show", "display current configuration", ConfigCommand::ShowCommand)
26
+ ConfigCommand.subcommand("init", "setup configuration", ConfigCommand::InitCommand)
27
+ MainCommand.subcommand("config", "temjin configuration", ConfigCommand)
28
+
29
+ CardCommand.subcommand("list", "list cards using optional filters", CardCommand::ListCommand)
30
+ CardCommand.subcommand("show", "display a card's information", CardCommand::ShowCommand)
31
+ CardCommand.subcommand("add", "add a new card", CardCommand::AddCommand)
32
+ CardCommand.subcommand("update", "update a card", CardCommand::UpdateCommand)
33
+ MainCommand.subcommand("card", "trello cards", CardCommand)
34
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ username: bigboss
3
+ key: 0123456789abcdef0123456789abcdef
4
+ token: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
@@ -0,0 +1,26 @@
1
+ module Temjin
2
+ module Test
3
+ module CaptureOutput
4
+ def capture_output
5
+ original_stdout = $stdout
6
+ $stdout = fake_stdout = StringIO.new
7
+
8
+ original_stderr = $stderr
9
+ $stderr = fake_stderr = StringIO.new
10
+
11
+ original_stdin = $stdin
12
+ $stdin = fake_stdin = StringIO.new
13
+
14
+ begin
15
+ yield(fake_stdin)
16
+ ensure
17
+ $stdout = original_stdout
18
+ $stderr = original_stderr
19
+ $stdin = original_stdin
20
+ end
21
+
22
+ [fake_stdout.string, fake_stderr.string]
23
+ end
24
+ end
25
+ end
26
+ end
data/test/main.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'mocha/mini_test'
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ add_filter '/test/'
6
+ end
7
+
8
+ require 'minitest/autorun'
9
+ require 'mocha/setup'
10
+
11
+ require 'temjin'
12
+
13
+ require 'webmock'
14
+ include WebMock::API
15
+ WebMock.enable!
16
+ WebMock.disable_net_connect!
17
+
18
+ module Temjin
19
+ module Test
20
+ HOME_DIR = File.dirname(__FILE__)
21
+ end
22
+ end
23
+
24
+ require './test/helpers/capture_output.rb'
25
+
26
+ Dir['./test/**/*_test.rb'].each do |f|
27
+ require f
28
+ end
@@ -0,0 +1,98 @@
1
+ module Temjin
2
+ module Test
3
+ module CardCommand
4
+ class AddCommandTest < MiniTest::Test
5
+ include CaptureOutput
6
+
7
+ def setup
8
+ @username = 'solid_snake'
9
+ @key = '0123456789abcdef0123456789abcdef'
10
+ @token = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
11
+
12
+ @board_name = 'fake_board'
13
+ @list_name = 'fake_list'
14
+ @id = '0123456789abcdef0123456789abcdef'
15
+ @card_name = "remind everyone that i'm no hero"
16
+ @card_desc = "uttered it a million times in metal gear solid 4"
17
+ end
18
+
19
+ def teardown; end
20
+
21
+ def test_execute_no_options
22
+ config = mock()
23
+ config.expects(:username).returns(@username)
24
+ config.expects(:key).returns(@key)
25
+ config.expects(:token).returns(@token)
26
+
27
+ list = mock()
28
+ list.expects(:id).returns(@id)
29
+
30
+ add_command = Temjin::CardCommand::AddCommand.new('temjin card add', :config => config)
31
+ add_command.expects(:find_user).with(@username).returns(@username)
32
+ add_command.expects(:find_list).with(@username, @board_name, @list_name).returns(list)
33
+
34
+ added_card = mock()
35
+ added_card.expects(:id).returns(@id)
36
+
37
+ create_options = {:list_id => @id}
38
+
39
+ Trello::Card.expects(:create).with(create_options).returns(added_card)
40
+
41
+ capture_output do
42
+ add_command.run([@board_name, @list_name])
43
+ end
44
+ end
45
+
46
+ def test_execute_with_name
47
+ config = mock()
48
+ config.expects(:username).returns(@username)
49
+ config.expects(:key).returns(@key)
50
+ config.expects(:token).returns(@token)
51
+
52
+ list = mock()
53
+ list.expects(:id).returns(@id)
54
+
55
+ add_command = Temjin::CardCommand::AddCommand.new('temjin card add', :config => config)
56
+ add_command.expects(:find_user).with(@username).returns(@username)
57
+ add_command.expects(:find_list).with(@username, @board_name, @list_name).returns(list)
58
+
59
+ added_card = mock()
60
+ added_card.expects(:id).returns(@id)
61
+
62
+ create_options = {:list_id => @id, :name => @card_name}
63
+
64
+ Trello::Card.expects(:create).with(create_options).returns(added_card)
65
+
66
+ capture_output do
67
+ add_command.run([@board_name, @list_name, @card_name])
68
+ end
69
+ end
70
+
71
+ def test_execute_with_name_and_desc
72
+ config = mock()
73
+ config.expects(:username).returns(@username)
74
+ config.expects(:key).returns(@key)
75
+ config.expects(:token).returns(@token)
76
+
77
+ list = mock()
78
+ list.expects(:id).returns(@id)
79
+
80
+ add_command = Temjin::CardCommand::AddCommand.new('temjin card add', :config => config)
81
+ add_command.expects(:find_user).with(@username).returns(@username)
82
+ add_command.expects(:find_list).with(@username, @board_name, @list_name).returns(list)
83
+
84
+ added_card = mock()
85
+ added_card.expects(:id).returns(@id)
86
+
87
+ create_options = {:list_id => @id, :name => @card_name, :desc => @card_desc}
88
+
89
+ Trello::Card.expects(:create).with(create_options).returns(added_card)
90
+
91
+ capture_output do
92
+ add_command.run([@board_name, @list_name, @card_name, '--desc', @card_desc])
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,47 @@
1
+ module Temjin
2
+ module Test
3
+ module CardCommand
4
+ class ListCommandTest < MiniTest::Test
5
+ include CaptureOutput
6
+
7
+ def setup
8
+ @username = 'solid_snake'
9
+ @key = '0123456789abcdef0123456789abcdef'
10
+ @token = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
11
+
12
+ @board_name = 'fake_board'
13
+ @list_name = 'fake_list'
14
+ @id = '0123456789abcdef0123456789abcdef'
15
+ @card_name = "remind everyone that i'm no hero"
16
+ @card_desc = "uttered it a million times in metal gear solid 4"
17
+ end
18
+
19
+ def teardown; end
20
+
21
+ def test_run
22
+ config = mock('config')
23
+ config.expects(:username).returns(@username)
24
+ config.expects(:key).returns(@key)
25
+ config.expects(:token).returns(@token)
26
+
27
+ card = mock('card')
28
+ card.expects(:id).times(3).returns(@id)
29
+ card.expects(:name).times(3).returns(@card_name)
30
+ card.expects(:desc).times(3).returns(@card_desc)
31
+ card_list = [card, card, card]
32
+
33
+ list = mock('list')
34
+ list.expects(:cards).returns(card_list)
35
+
36
+ list_command = Temjin::CardCommand::ListCommand.new('temjin card list', :config => config)
37
+ list_command.expects(:find_user).with(@username).returns(@username)
38
+ list_command.expects(:find_list).with(@username, @board_name, @list_name).returns(list)
39
+
40
+ capture_output do
41
+ list_command.run([@board_name, @list_name])
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,66 @@
1
+ module Temjin
2
+ module Test
3
+ module CardCommand
4
+ class ShowCommandTest < MiniTest::Test
5
+ include CaptureOutput
6
+
7
+ def setup
8
+ @username = 'solid_snake'
9
+ @key = '0123456789abcdef0123456789abcdef'
10
+ @token = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
11
+
12
+ @board_name = 'fake_board'
13
+ @list_name = 'fake_list'
14
+ @label = 'fake_label'
15
+ @id = '0123456789abcdef0123456789abcdef'
16
+ @card_name = "remind everyone that i'm no hero"
17
+ @card_desc = "uttered it a million times in metal gear solid 4"
18
+ end
19
+
20
+ def teardown; end
21
+
22
+ def test_run
23
+ config = mock('config')
24
+ config.expects(:key).returns(@key)
25
+ config.expects(:token).returns(@token)
26
+
27
+ label = mock('label')
28
+ label.expects(:name).times(2).returns(@label)
29
+
30
+ card = mock('card')
31
+ card.expects(:list_id).returns(@id)
32
+ card.expects(:board_id).returns(@id)
33
+ card.expects(:member_ids).returns([@id, @id])
34
+ card.expects(:labels).returns([label, label])
35
+ card.expects(:name).returns(@card_name)
36
+ card.expects(:id).returns(@id)
37
+ card.expects(:short_url).returns('http://trello.com/fancyshorturl')
38
+ card.expects(:attachments).returns([])
39
+ card.expects(:closed?).returns(false)
40
+ card.expects(:due).times(2).returns(Time.now + (60 * 60 * 24))
41
+ card.expects(:checklists).returns([])
42
+ card.expects(:desc).times(2).returns(@card_desc)
43
+
44
+ user = mock('user')
45
+ user.expects(:username).times(2).returns(@username)
46
+
47
+ list = mock('list')
48
+ list.expects(:name).returns(@list_name)
49
+
50
+ board = mock('board')
51
+ board.expects(:name).returns(@board_name)
52
+
53
+ show_command = Temjin::CardCommand::ShowCommand.new('temjin card show', :config => config)
54
+ show_command.expects(:find_card).with(@id).returns(card)
55
+ show_command.expects(:find_list_by_id).with(@id).returns(list)
56
+ show_command.expects(:find_user).with(@id).times(2).returns(user)
57
+ show_command.expects(:find_board).with(@id).returns(board)
58
+
59
+ capture_output do
60
+ show_command.run([@id])
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end