gyunyu 0.1.0

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,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'json'
3
+
4
+ module Gyunyu
5
+ module Command
6
+ module Export
7
+ module Format
8
+ module Json
9
+ def self.export( tasks, fields )
10
+ tasks.to_json
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'yaml'
3
+
4
+ module Gyunyu
5
+ module Command
6
+ module Export
7
+ module Format
8
+ module Yaml
9
+ def self.export( tasks, fields )
10
+ tasks.to_yaml
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'optparse'
3
+
4
+ module Gyunyu
5
+ module Command
6
+ module Export
7
+ class Option
8
+ class FormatNotFound < StandardError; end
9
+
10
+ FIELD_SEP = ','
11
+
12
+ def initialize( argv = [] )
13
+ @lists = []
14
+ @filter = nil
15
+ @fields = []
16
+ @format = :csv
17
+
18
+ parser.parse( argv )
19
+ end
20
+ attr_reader :lists, :filter, :fields, :format
21
+
22
+ def fields
23
+ if @fields.size > 0
24
+ @fields
25
+ else
26
+ %w( task_id name due )
27
+ end
28
+ end
29
+
30
+ def parser
31
+ OptionParser.new do |opt|
32
+ opt.on('-l', '--list LIST') { |list|
33
+ if !@lists.include?( list )
34
+ @lists << list
35
+ end
36
+ }
37
+ opt.on('-f', '--filter FILTER') { |filter|
38
+ @filter = filter if filter.size > 0
39
+ }
40
+ opt.on('-d', '--field FIELD') { |field|
41
+ if field.include?( FIELD_SEP )
42
+ @fields = field.split( FIELD_SEP )
43
+ elsif !@fields.include?( field )
44
+ @fields << field
45
+ end
46
+ }
47
+ opt.on('-o', '--format FORMAT') { |format|
48
+ format.downcase!
49
+ formats = Format.constants.map { |e|
50
+ e.to_s.downcase
51
+ }
52
+ if formats.include?( format )
53
+ @format = format
54
+ else
55
+ raise FormatNotFound
56
+ end
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ module Gyunyu
2
+ module Expander
3
+ class << self
4
+ #
5
+ # [param] Array or Hash taskseries
6
+ # [return] Array
7
+ #
8
+ def taskseries( list )
9
+ expanded = []
10
+
11
+ if list.is_a?( Hash ) and list.has_key?( 'taskseries' )
12
+ list = list['taskseries']
13
+ end
14
+
15
+ list.each { |series|
16
+ expanded += tasks( series )
17
+ }
18
+
19
+ expanded
20
+ end
21
+
22
+ #
23
+ # [param] Hash taskseries
24
+ # [return] Array
25
+ #
26
+ def tasks( taskseries )
27
+ task = Marshal.load( Marshal.dump( taskseries ) )
28
+ task.delete('id')
29
+ task.delete('task')
30
+ task['taskseries_id'] = taskseries['id']
31
+
32
+ taskseries['task'].map { |t|
33
+ t['task_id'] = t['id']
34
+ t.delete( 'id' )
35
+ task.merge( t )
36
+ }
37
+ end
38
+ end # of << self
39
+ end
40
+ end
@@ -0,0 +1,89 @@
1
+ require 'fileutils'
2
+
3
+ module Gyunyu
4
+ class Token
5
+ API_KEY = '0af285d87a0798cdd4751acfb02fc058'
6
+ SHARED_SECRET = 'f0611feb04a04e1d'
7
+
8
+ include ::FileUtils
9
+
10
+ def initialize
11
+ RTM::API.init( API_KEY, SHARED_SECRET )
12
+ end
13
+
14
+ def self.get
15
+ @self = self.new
16
+ token = @self.load
17
+
18
+ if ( !token )
19
+ token = @self.request
20
+ @self.save( token )
21
+ end
22
+
23
+ RTM::API.token = token
24
+ token
25
+ end
26
+
27
+ def self.purge
28
+ RTM::API.token = nil
29
+ self.new.instance_eval {
30
+ while ( File.exist?( magazine ) )
31
+ rm( magazine )
32
+ end
33
+ }
34
+ end
35
+
36
+ def save( token )
37
+ if ( !File.exist?( magazine ) )
38
+ open( magazine, 'w', 0600 ) { }
39
+ end
40
+
41
+ open( magazine, 'r+b', 0600 ) { |f|
42
+ f.flock( File::LOCK_EX )
43
+ f.truncate(0)
44
+ f.print token
45
+ f.flock( File::LOCK_UN )
46
+ }
47
+ token
48
+ end
49
+
50
+ def load
51
+ token = nil
52
+
53
+ if ( File.exist?( magazine ) )
54
+ open( magazine, 'r+b' ) { |f|
55
+ # TODO WARN if permission is too loose
56
+ f.flock( File::LOCK_SH )
57
+ token = f.read
58
+ f.flock( File::LOCK_UN )
59
+ }
60
+ end
61
+
62
+ token
63
+ end
64
+
65
+ def magazine
66
+ env || dotfile
67
+ end
68
+
69
+ def env
70
+ ENV['RTMSTAT_TOKEN']
71
+ end
72
+
73
+ def dotfile
74
+ File.join( ENV['HOME'], '.gyunyu' )
75
+ end
76
+
77
+ def request
78
+ frob = RTM::Auth::GetFrob.new.invoke
79
+ url = RTM::API.get_auth_url('read', frob)
80
+ puts "open RTM with browser and accept API KEY."
81
+ puts "and hit enter."
82
+ system("open '#{url}'")
83
+ STDIN.gets
84
+
85
+ res = RTM::Auth::GetToken.new(frob).invoke
86
+ res[:token]
87
+ end
88
+ end
89
+ end
data/spec/app/.gitkeep ADDED
File without changes
@@ -0,0 +1,137 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '/../../../spec_helper'
3
+
4
+ describe Gyunyu::Command::Export::Option do
5
+ describe 'parser' do
6
+ subject {
7
+ Gyunyu::Command::Export::Option.new.parser
8
+ }
9
+ it {
10
+ subject.class.should == OptionParser
11
+ }
12
+
13
+ describe 'lists' do
14
+ context 'no lists' do
15
+ subject {
16
+ Gyunyu::Command::Export::Option.new.lists
17
+ }
18
+ it {
19
+ should == []
20
+ }
21
+ end
22
+ context 'one list' do
23
+ subject {
24
+ Gyunyu::Command::Export::Option.new( %w( -l 仕事 ) ).lists
25
+ }
26
+ it {
27
+ should == %w( 仕事 )
28
+ }
29
+ end
30
+ context 'two lists' do
31
+ subject {
32
+ Gyunyu::Command::Export::Option.new( %w( -l 仕事 -l 勉強 ) ).lists
33
+ }
34
+ it {
35
+ should == %w( 仕事 勉強 )
36
+ }
37
+ end
38
+ context 'twice same list' do
39
+ subject {
40
+ Gyunyu::Command::Export::Option.new( %w( -l 仕事 -l 仕事 ) ).lists
41
+ }
42
+ it {
43
+ should == %w( 仕事 )
44
+ }
45
+ end
46
+ end
47
+
48
+ describe 'filter' do
49
+ context 'no filter' do
50
+ subject {
51
+ Gyunyu::Command::Export::Option.new( %w() ).filter
52
+ }
53
+ it {
54
+ should be_nil
55
+ }
56
+ end
57
+ context 'one filter' do
58
+ subject {
59
+ Gyunyu::Command::Export::Option.new( %w( -f filter ) ).filter
60
+ }
61
+ it {
62
+ should == 'filter'
63
+ }
64
+ end
65
+ context 'multiple filters' do
66
+ subject {
67
+ Gyunyu::Command::Export::Option.new( %W( -f first\ one -f last\ one ) ).filter
68
+ }
69
+ it {
70
+ should == 'last one'
71
+ }
72
+ end
73
+ end
74
+
75
+ describe 'fields' do
76
+ context 'no fields' do
77
+ subject {
78
+ Gyunyu::Command::Export::Option.new().fields
79
+ }
80
+ it {
81
+ should == %w( task_id name due )
82
+ }
83
+ end
84
+ context 'specified fields' do
85
+ subject {
86
+ Gyunyu::Command::Export::Option.new( %w( -d id ) ).fields
87
+ }
88
+ it {
89
+ should == %w( id )
90
+ }
91
+ end
92
+ context 'duplicated fields' do
93
+ subject {
94
+ Gyunyu::Command::Export::Option.new( %w( -d id -d name -d id ) ).fields
95
+ }
96
+ it {
97
+ should == %w( id name )
98
+ }
99
+ end
100
+ context 'include #{FIELD_SEP}' do
101
+ subject {
102
+ Gyunyu::Command::Export::Option.new( %w( -d id,name,taskseries_id,due ) ).fields
103
+ }
104
+ it 'bulk definition' do
105
+ should == %w( id name taskseries_id due )
106
+ end
107
+ end
108
+ end
109
+
110
+ describe 'format' do
111
+ context 'no format' do
112
+ subject {
113
+ Gyunyu::Command::Export::Option.new().format
114
+ }
115
+ it {
116
+ should == :csv
117
+ }
118
+ end
119
+ context 'correct format' do
120
+ subject {
121
+ Gyunyu::Command::Export::Option.new( %w( -o json ) ).format
122
+ }
123
+ it {
124
+ should == 'json'
125
+ }
126
+ end
127
+ context 'wrong format' do
128
+ subject {
129
+ Gyunyu::Command::Export::Option.new( %w( -o atom ) ).format
130
+ }
131
+ it {
132
+ lambda { subject }.should raise_error( Gyunyu::Command::Export::Option::FormatNotFound )
133
+ }
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Gyunyu::Command do
4
+ describe 'commands' do
5
+ let(:commands) {
6
+ Gyunyu::Command.commands
7
+ }
8
+ describe 'array' do
9
+ it {
10
+ commands.size.should > 0
11
+ }
12
+ end
13
+ describe 'array of strings' do
14
+ it 'array of strings' do
15
+ commands.map { |e| e.should.class == String }
16
+ end
17
+ end
18
+ end
19
+
20
+ describe 'dispatch' do
21
+ context 'correct command' do
22
+ before {
23
+ stub.instance_of(Gyunyu::Command::Export::App).run {
24
+ "`export' dispatched"
25
+ }
26
+ }
27
+ subject {
28
+ Gyunyu::Command.dispatch( 'export' )
29
+ }
30
+ it {
31
+ should == "`export' dispatched"
32
+ }
33
+ end
34
+ context 'wrong command' do
35
+ subject {
36
+ Gyunyu::Command.dispatch( 'foo' )
37
+ }
38
+ it {
39
+ lambda {subject}.should raise_error(Gyunyu::Command::NotFound)
40
+ }
41
+ end
42
+ end
43
+
44
+ describe 'app_const' do
45
+ context 'correct app' do
46
+ subject {
47
+ Gyunyu::Command.app_const( 'export' )
48
+ }
49
+ it {
50
+ should == Gyunyu::Command::Export::App
51
+ }
52
+ end
53
+ context 'wrong app' do
54
+ subject {
55
+ Gyunyu::Command.app_const( 'foo' )
56
+ }
57
+ it {
58
+ lambda {subject}.should raise_error( NameError )
59
+ }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,140 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe Gyunyu::Expander do
5
+ describe 'taskseries' do
6
+ shared_examples_for :taskseries do
7
+ it 'array of hash' do
8
+ subject.class.should == Array
9
+ subject.each { |e|
10
+ e.class.should == Hash
11
+ }
12
+ end
13
+ end
14
+
15
+ context 'array given' do
16
+ it_should_behave_like :taskseries do
17
+ subject {
18
+ Gyunyu::Expander.taskseries( task_raw_data.first['taskseries'] )
19
+ }
20
+ end
21
+ end
22
+ context 'hash given' do
23
+ it_should_behave_like :taskseries do
24
+ subject {
25
+ Gyunyu::Expander.taskseries( task_raw_data.first )
26
+ }
27
+ end
28
+ end
29
+ end
30
+
31
+ describe 'tasks' do
32
+ subject {
33
+ Gyunyu::Expander.tasks(task_raw_data.first['taskseries'].first)
34
+ }
35
+ it {
36
+ should == [
37
+ { "taskseries_id"=>"118872028",
38
+ "created"=>"2011-05-28T01:04:49Z",
39
+ "modified"=>"2011-10-10T22:00:53Z",
40
+ "name"=>"jsのlintについて",
41
+ "source"=>"js",
42
+ "url"=>"",
43
+ "location_id"=>"",
44
+ "tags"=>[{"tag"=>["research", "writing"]}],
45
+ "participants"=>[{}],
46
+ "notes"=>
47
+ [{"note"=>
48
+ [{"id"=>"26111105",
49
+ "created"=>"2011-10-10T22:00:53Z",
50
+ "modified"=>"2011-10-10T22:00:53Z",
51
+ "title"=>"",
52
+ "content"=>"意図が分かんないな、このメモ。結局何をしたかった?"}]}],
53
+ "task_id"=>"181820465",
54
+ "due"=>"",
55
+ "has_due_time"=>"0",
56
+ "added"=>"2011-05-28T01:04:49Z",
57
+ "completed"=>"",
58
+ "deleted"=>"",
59
+ "priority"=>"1",
60
+ "postponed"=>"0",
61
+ "estimate"=>""}]
62
+ }
63
+ end
64
+ end
65
+
66
+ def task_raw_data
67
+ # [{'id' => list id,
68
+ # 'taskseries' =>
69
+ # []
70
+ # }]
71
+ [{"id"=>"135243",
72
+ "taskseries"=>
73
+ [{"id"=>"118872028",
74
+ "created"=>"2011-05-28T01:04:49Z",
75
+ "modified"=>"2011-10-10T22:00:53Z",
76
+ "name"=>"jsのlintについて",
77
+ "source"=>"js",
78
+ "url"=>"",
79
+ "location_id"=>"",
80
+ "tags"=>[{"tag"=>["research", "writing"]}],
81
+ "participants"=>[{}],
82
+ "notes"=>
83
+ [{"note"=>
84
+ [{"id"=>"26111105",
85
+ "created"=>"2011-10-10T22:00:53Z",
86
+ "modified"=>"2011-10-10T22:00:53Z",
87
+ "title"=>"",
88
+ "content"=>"意図が分かんないな、このメモ。結局何をしたかった?"}]}],
89
+ "task"=>
90
+ [{"id"=>"181820465",
91
+ "due"=>"",
92
+ "has_due_time"=>"0",
93
+ "added"=>"2011-05-28T01:04:49Z",
94
+ "completed"=>"",
95
+ "deleted"=>"",
96
+ "priority"=>"1",
97
+ "postponed"=>"0",
98
+ "estimate"=>""}]},
99
+ {"id"=>"46116936",
100
+ "created"=>"2009-08-01T08:35:30Z",
101
+ "modified"=>"2010-05-10T03:18:43Z",
102
+ "name"=>"あの本読む",
103
+ "source"=>"js",
104
+ "url"=>"",
105
+ "location_id"=>"",
106
+ "tags"=>[{"tag"=>["reading"]}],
107
+ "participants"=>[{}],
108
+ "notes"=>[{}],
109
+ "task"=>
110
+ [{"id"=>"65881131",
111
+ "due"=>"",
112
+ "has_due_time"=>"0",
113
+ "added"=>"2009-08-01T08:35:30Z",
114
+ "completed"=>"",
115
+ "deleted"=>"",
116
+ "priority"=>"N",
117
+ "postponed"=>"0",
118
+ "estimate"=>"1時間"}]},
119
+ {"id"=>"115609158",
120
+ "created"=>"2011-05-02T10:13:47Z",
121
+ "modified"=>"2011-05-07T12:26:43Z",
122
+ "name"=>"この本読む",
123
+ "source"=>"js",
124
+ "url"=>"",
125
+ "location_id"=>"",
126
+ "tags"=>[{"tag"=>["reading"]}],
127
+ "participants"=>[{}],
128
+ "notes"=>[{}],
129
+ "task"=>
130
+ [{"id"=>"176273444",
131
+ "due"=>"",
132
+ "has_due_time"=>"0",
133
+ "added"=>"2011-05-02T10:13:47Z",
134
+ "completed"=>"",
135
+ "deleted"=>"",
136
+ "priority"=>"1",
137
+ "postponed"=>"0",
138
+ "estimate"=>"8h"}]}
139
+ ]}]
140
+ end