twterm 1.0.8 → 1.0.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f8922ec344713188266282109b5d5a43a95948e
4
- data.tar.gz: b1ac62bbd18b92a058b8f0753a1f34470b129fd0
3
+ metadata.gz: 6b7f3f957fa6bb231072502fd2794d212db4547e
4
+ data.tar.gz: 83aecc6b84ed61620f52b69f6b65c91b6d1af67b
5
5
  SHA512:
6
- metadata.gz: c08ff7689bd89c875051779cd9eda592c699df11a855a56dbe83fb4ac1a67728de75477ff8152332b36b632456169f7490759c81555542fe46dc546d0125392d
7
- data.tar.gz: 6f920de275acdac03b647505fe37debe4defedd67f40dfb5c3eb9b0281a4382f688bd6266e4378c86411133b9ff4bd5197badf26c163fbb1f11775d2cfd86293
6
+ metadata.gz: e3b1748a40af7f4a32b93568422e0cd65dbe6a9e7a12818567e8aff5142639af6396df93febdffd0f93a19afd9020bf4ac5f86ad468f60b00c5e8cdb597ee5ee
7
+ data.tar.gz: 6df0822a5f0d59a9f1e2e260e2e43a2b5997a7a36426f5dc786f85283514c9d44ddd9ca74dc41fd7ff1fbc83085bf2eb84a6b4af8b12f33b3438a91ec7d3438c
@@ -34,6 +34,7 @@ require 'twterm/scheduler'
34
34
  require 'twterm/status'
35
35
  require 'twterm/tab_manager'
36
36
  require 'twterm/tab/base'
37
+ require 'twterm/tab/dumpable'
37
38
  require 'twterm/tab/exceptions'
38
39
  require 'twterm/tab/scrollable'
39
40
  require 'twterm/tab/statuses_tab'
@@ -54,6 +55,6 @@ require 'twterm/version'
54
55
 
55
56
  module Twterm
56
57
  class Conf
57
- REQUIRE_VERSION = '1.0.8'
58
+ REQUIRE_VERSION = '1.0.9'
58
59
  end
59
60
  end
@@ -19,6 +19,7 @@ module Twterm
19
19
 
20
20
  mentions_tab = Tab::MentionsTab.new(client)
21
21
  TabManager.instance.add(mentions_tab)
22
+ TabManager.instance.recover_tabs
22
23
 
23
24
  Screen.instance.refresh
24
25
 
@@ -37,15 +38,17 @@ module Twterm
37
38
 
38
39
  def register_interruption_handler(&block)
39
40
  fail ArgumentError, 'no block given' unless block_given?
40
- Signal.trap(:INT) do
41
- block.call
42
- end
41
+ Signal.trap(:INT) { block.call }
43
42
  end
44
43
 
45
44
  def reset_interruption_handler
46
- Signal.trap(:INT) do
47
- exit
48
- end
45
+ Signal.trap(:INT) { App.instance.quit }
46
+ end
47
+
48
+ def quit
49
+ Curses.close_screen
50
+ TabManager.instance.dump_tabs
51
+ exit
49
52
  end
50
53
 
51
54
  private
@@ -43,9 +43,7 @@ module Twterm
43
43
 
44
44
  def stream
45
45
  @stream_client.on_friends do
46
- break if @stream_connected
47
-
48
- Notifier.instance.show_message 'Connection established'
46
+ Notifier.instance.show_message 'Connection established' unless @stream_connected
49
47
  @stream_connected = true
50
48
  end
51
49
 
@@ -117,13 +115,19 @@ module Twterm
117
115
  end
118
116
  end
119
117
 
118
+ def list(list_id)
119
+ send_request do
120
+ yield List.new(@rest_client.list(list_id))
121
+ end
122
+ end
123
+
120
124
  def lists
121
125
  send_request do
122
126
  yield @rest_client.lists.map { |list| List.new(list) }
123
127
  end
124
128
  end
125
129
 
126
- def list(list)
130
+ def list_timeline(list)
127
131
  fail ArgumentError, 'argument must be an instance of List class' unless list.is_a? List
128
132
  send_request do
129
133
  yield @rest_client.list_timeline(list.id, count: 100).select(&@mute_filter).map(&CREATE_STATUS_PROC)
@@ -144,11 +148,12 @@ module Twterm
144
148
 
145
149
  def show_user(query)
146
150
  send_request do
147
- begin
148
- user = User.new(@rest_client.user(query))
149
- rescue Twitter::Error::NotFound
150
- user = nil
151
- end
151
+ user =
152
+ begin
153
+ User.new(@rest_client.user(query))
154
+ rescue Twitter::Error::NotFound
155
+ nil
156
+ end
152
157
  yield user
153
158
  end
154
159
  end
@@ -249,9 +254,11 @@ module Twterm
249
254
  begin
250
255
  block.call
251
256
  rescue Twitter::Error => e
252
- Notifier.instance.show_error 'Failed to send request'
253
- sleep 10
254
- retry if e.message == 'getaddrinfo: nodename nor servname provided, or not known'
257
+ Notifier.instance.show_error "Failed to send request: #{e.message}"
258
+ if e.message == 'getaddrinfo: nodename nor servname provided, or not known'
259
+ sleep 10
260
+ retry
261
+ end
255
262
  end
256
263
  end
257
264
  end
@@ -2,8 +2,17 @@ module Twterm
2
2
  class List
3
3
  attr_reader :id, :name, :slug, :full_name, :mode, :description, :member_count, :subscriber_count
4
4
 
5
+ @@instances = {}
6
+
5
7
  def initialize(list)
6
8
  @id = list.id
9
+ update!(list)
10
+
11
+ @@instances[@id] = self
12
+ self
13
+ end
14
+
15
+ def update!(list)
7
16
  @name = list.name
8
17
  @slug = list.slug
9
18
  @full_name = list.full_name
@@ -11,10 +20,32 @@ module Twterm
11
20
  @description = list.description.is_a?(Twitter::NullObject) ? '' : list.description
12
21
  @member_count = list.member_count
13
22
  @subscriber_count = list.subscriber_count
23
+
24
+ self
14
25
  end
15
26
 
16
27
  def ==(other)
17
28
  other.is_a?(self.class) && id == other.id
18
29
  end
30
+
31
+ def self.new(list)
32
+ instance = find(list.id)
33
+ instance.nil? ? super : instance.update!(list)
34
+ end
35
+
36
+ def self.find(id)
37
+ @@instances[id]
38
+ end
39
+
40
+ def self.find_or_fetch(id)
41
+ instance = find(id)
42
+ (yield(instance) && return) if instance
43
+
44
+ Client.current.list(id) { |list| yield list }
45
+ end
46
+
47
+ def self.all
48
+ @@instances.values
49
+ end
19
50
  end
20
51
  end
@@ -44,7 +44,7 @@ module Twterm
44
44
  Tweetbox.instance.compose
45
45
  return
46
46
  when 'Q'
47
- exit
47
+ App.instance.quit
48
48
  when '/'
49
49
  # filter
50
50
  else
@@ -89,14 +89,24 @@ module Twterm
89
89
  end
90
90
 
91
91
  def in_reply_to_status(&block)
92
- block.call(nil) if @in_reply_to_status_id.nil?
92
+ if @in_reply_to_status_id.nil?
93
+ block.call(nil)
94
+ return
95
+ end
93
96
 
94
97
  status = Status.find(@in_reply_to_status_id)
95
- block.call(status) unless status.nil?
98
+ unless status.nil?
99
+ block.call(status)
100
+ return
101
+ end
96
102
 
97
103
  Client.current.show_status(@in_reply_to_status_id, &block)
98
104
  end
99
105
 
106
+ def replies
107
+ Status.all.select { |s| s.in_reply_to_status_id == id }
108
+ end
109
+
100
110
  def retweeted_by
101
111
  User.find(@retweeted_by_user_id)
102
112
  end
@@ -122,6 +132,13 @@ module Twterm
122
132
  @@instances[id]
123
133
  end
124
134
 
135
+ def find_or_fetch(id)
136
+ instance = find(id)
137
+ (yield(instance) && return) if instance
138
+
139
+ Client.current.show_status(id) { |status| yield status }
140
+ end
141
+
125
142
  def parse_time(time)
126
143
  (time.is_a?(String) ? Time.parse(time) : time.dup).localtime
127
144
  end
@@ -3,7 +3,7 @@ module Twterm
3
3
  module Base
4
4
  include Curses
5
5
 
6
- attr_reader :title
6
+ attr_accessor :title
7
7
 
8
8
  def initialize
9
9
  @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx - 30, 3, 0)
@@ -2,30 +2,48 @@ module Twterm
2
2
  module Tab
3
3
  class ConversationTab
4
4
  include StatusesTab
5
+ include Dumpable
5
6
 
6
7
  attr_reader :status
7
8
 
8
- def initialize(status)
9
- fail ArgumentError, 'argument must be an instance of Status class' unless status.is_a? Status
10
-
9
+ def initialize(status_id)
11
10
  @title = 'Conversation'
12
-
13
11
  super()
14
- prepend(status)
15
- Thread.new { fetch_reply(status) }
12
+
13
+ Status.find_or_fetch(status_id) do |status|
14
+ @status = status
15
+
16
+ append(status)
17
+ move_to_top
18
+ Thread.new { fetch_in_reply_to_status(status) }
19
+ Thread.new { fetch_replies(status) }
20
+ end
16
21
  end
17
22
 
18
- def fetch_reply(status)
19
- status.in_reply_to_status do |reply|
20
- return if reply.nil?
21
- append(reply)
22
- fetch_reply(reply)
23
+ def fetch_in_reply_to_status(status)
24
+ status.in_reply_to_status do |in_reply_to|
25
+ return if in_reply_to.nil?
26
+ append(in_reply_to)
27
+ sort
28
+ Thread.new { fetch_in_reply_to_status(in_reply_to) }
29
+ end
30
+ end
31
+
32
+ def fetch_replies(status)
33
+ status.replies.each do |reply|
34
+ prepend(reply)
35
+ sort
36
+ Thread.new { fetch_replies(reply) }
23
37
  end
24
38
  end
25
39
 
26
40
  def ==(other)
27
41
  other.is_a?(self.class) && status == other.status
28
42
  end
43
+
44
+ def dump
45
+ @status.id
46
+ end
29
47
  end
30
48
  end
31
49
  end
@@ -0,0 +1,21 @@
1
+ module Twterm
2
+ module Tab
3
+ module Dumpable
4
+ def dump
5
+ fail NotImplementedError 'dump method must be implemented'
6
+ end
7
+
8
+ def self.included(klass)
9
+ klass.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def recover(title, arg)
14
+ tab = new(arg)
15
+ tab.title = title
16
+ tab
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,23 +2,25 @@ module Twterm
2
2
  module Tab
3
3
  class ListTab
4
4
  include StatusesTab
5
+ include Dumpable
5
6
 
6
7
  attr_reader :list
7
8
 
8
- def initialize(list)
9
- fail ArgumentError, 'argument must be an instance of List class' unless list.is_a? List
10
-
9
+ def initialize(list_id)
11
10
  super()
12
11
 
13
- @list = list
14
- @title = @list.full_name
15
- fetch { move_to_top }
16
- @auto_reloader = Scheduler.new(300) { fetch }
12
+ List.find_or_fetch(list_id) do |list|
13
+ @list = list
14
+ @title = @list.full_name
15
+ TabManager.instance.refresh_window
16
+ fetch { move_to_top }
17
+ @auto_reloader = Scheduler.new(300) { fetch }
18
+ end
17
19
  end
18
20
 
19
21
  def fetch
20
22
  client = Client.current
21
- client.list(@list) do |statuses|
23
+ client.list_timeline(@list) do |statuses|
22
24
  statuses.reverse.each(&method(:prepend))
23
25
  sort
24
26
  yield if block_given?
@@ -26,13 +28,17 @@ module Twterm
26
28
  end
27
29
 
28
30
  def close
29
- @auto_reloader.kill
31
+ @auto_reloader.kill if @auto_reloader
30
32
  super
31
33
  end
32
34
 
33
35
  def ==(other)
34
36
  other.is_a?(self.class) && list == other.list
35
37
  end
38
+
39
+ def dump
40
+ @list.id
41
+ end
36
42
  end
37
43
  end
38
44
  end
@@ -19,7 +19,7 @@ module Twterm
19
19
  case key
20
20
  when 10
21
21
  return true if current_list.nil?
22
- list_tab = Tab::ListTab.new(current_list)
22
+ list_tab = Tab::ListTab.new(current_list.id)
23
23
  TabManager.instance.switch(list_tab)
24
24
  else
25
25
  return false
@@ -25,8 +25,9 @@ module Twterm
25
25
 
26
26
  input_thread = Thread.new do
27
27
  close_screen
28
+ CompletionManager.instance.set_default_mode!
28
29
  puts "\ninput search query"
29
- query = (readline('input query > ') || '').strip
30
+ query = (readline('> ') || '').strip
30
31
  resetter.call
31
32
 
32
33
  tab = query.nil? || query.empty? ? Tab::New::Start.new : Tab::SearchTab.new(query)
@@ -31,15 +31,18 @@ module Twterm
31
31
  screen_name = (readline('> @') || '').strip
32
32
  resetter.call
33
33
 
34
- Client.current.show_user(screen_name) do |user|
35
- if screen_name.nil? || screen_name.empty? || user.nil?
36
- Notifier.instance.show_error 'User not found' if user.nil?
37
- tab = Tab::New::Start.new
38
- else
39
- tab = Tab::UserTab.new(user)
34
+ if screen_name.nil? || screen_name.empty?
35
+ TabManager.instance.switch(Tab::New::Start.new)
36
+ else
37
+ Client.current.show_user(screen_name) do |user|
38
+ if user.nil?
39
+ Notifier.instance.show_error 'User not found'
40
+ tab = Tab::New::Start.new
41
+ else
42
+ tab = Tab::UserTab.new(user.id)
43
+ end
44
+ TabManager.instance.switch(tab)
40
45
  end
41
-
42
- TabManager.instance.switch(tab)
43
46
  end
44
47
  end
45
48
 
@@ -108,7 +108,7 @@ module Twterm
108
108
  0
109
109
  else
110
110
  height = @window.maxy
111
- [height * (last - index + 1) / count, 1].max
111
+ [height * (last - offset + 1) / count, 1].max
112
112
  end
113
113
  end
114
114
 
@@ -2,6 +2,7 @@ module Twterm
2
2
  module Tab
3
3
  class SearchTab
4
4
  include StatusesTab
5
+ include Dumpable
5
6
 
6
7
  attr_reader :query
7
8
 
@@ -12,6 +13,7 @@ module Twterm
12
13
  @title = "\"#{@query}\""
13
14
 
14
15
  fetch { move_to_top }
16
+ @auto_reloader = Scheduler.new(300) { fetch }
15
17
  end
16
18
 
17
19
  def fetch
@@ -21,9 +23,18 @@ module Twterm
21
23
  end
22
24
  end
23
25
 
26
+ def close
27
+ @auto_reloader.kill if @auto_reloader
28
+ super
29
+ end
30
+
24
31
  def ==(other)
25
32
  other.is_a?(self.class) && query == other.query
26
33
  end
34
+
35
+ def dump
36
+ @query
37
+ end
27
38
  end
28
39
  end
29
40
  end
@@ -82,7 +82,7 @@ module Twterm
82
82
  def show_user
83
83
  return if highlighted_status.nil?
84
84
  user = highlighted_status.user
85
- user_tab = Tab::UserTab.new(user)
85
+ user_tab = Tab::UserTab.new(user.id)
86
86
  TabManager.instance.add_and_show(user_tab)
87
87
  end
88
88
 
@@ -95,7 +95,7 @@ module Twterm
95
95
 
96
96
  def show_conversation
97
97
  return if highlighted_status.nil?
98
- tab = Tab::ConversationTab.new(highlighted_status)
98
+ tab = Tab::ConversationTab.new(highlighted_status.id)
99
99
  TabManager.instance.add_and_show(tab)
100
100
  end
101
101
 
@@ -112,6 +112,8 @@ module Twterm
112
112
 
113
113
  @window.clear
114
114
 
115
+ return if offset < 0
116
+
115
117
  statuses.reverse.drop(offset).each.with_index(offset) do |status, i|
116
118
  formatted_lines = status.split(@window.maxx - 4).count
117
119
  if current_line + formatted_lines + 2 > @window.maxy
@@ -2,19 +2,21 @@ module Twterm
2
2
  module Tab
3
3
  class UserTab
4
4
  include StatusesTab
5
+ include Dumpable
5
6
 
6
7
  attr_reader :user
7
8
 
8
- def initialize(user)
9
- fail ArgumentError, 'argument must be an instance of User class' unless user.is_a? User
10
-
9
+ def initialize(user_id)
11
10
  super()
12
11
 
13
- @user = user
14
- @title = "@#{user.screen_name}"
12
+ User.find_or_fetch(user_id) do |user|
13
+ @user = user
14
+ @title = "@#{@user.screen_name}"
15
+ TabManager.instance.refresh_window
15
16
 
16
- fetch { move_to_top }
17
- @auto_reloader = Scheduler.new(120) { fetch }
17
+ fetch { move_to_top }
18
+ @auto_reloader = Scheduler.new(120) { fetch }
19
+ end
18
20
  end
19
21
 
20
22
  def fetch
@@ -26,13 +28,17 @@ module Twterm
26
28
  end
27
29
 
28
30
  def close
29
- @auto_reloader.kill
31
+ @auto_reloader.kill if @auto_reloader
30
32
  super
31
33
  end
32
34
 
33
35
  def ==(other)
34
36
  other.is_a?(self.class) && user == other.user
35
37
  end
38
+
39
+ def dump
40
+ @user.id
41
+ end
36
42
  end
37
43
  end
38
44
  end
@@ -3,6 +3,8 @@ module Twterm
3
3
  include Singleton
4
4
  include Curses
5
5
 
6
+ DUMPED_TABS_FILE = "#{ENV['HOME']}/.twterm/dumped_tabs"
7
+
6
8
  def initialize
7
9
  @tabs = []
8
10
  @index = 0
@@ -78,6 +80,28 @@ module Twterm
78
80
  end
79
81
  end
80
82
 
83
+ def recover_tabs
84
+ return unless File.exist? DUMPED_TABS_FILE
85
+
86
+ data = YAML.load(File.read(DUMPED_TABS_FILE))
87
+ data.each do |klass, title, arg|
88
+ tab = klass.recover(title, arg)
89
+ add(tab)
90
+ end
91
+ rescue
92
+ Notifier.instance.show_error 'Failed to recover tabs'
93
+ end
94
+
95
+ def dump_tabs
96
+ data = @tabs.each_with_object([]) do |tab, arr|
97
+ next unless tab.is_a? Tab::Dumpable
98
+ arr << [tab.class, tab.title, tab.dump]
99
+ end
100
+ File.open(DUMPED_TABS_FILE, 'w', 0600) do |f|
101
+ f.write data.to_yaml
102
+ end
103
+ end
104
+
81
105
  def refresh_window
82
106
  @window.clear
83
107
  current_tab_id = current_tab.object_id
@@ -57,6 +57,13 @@ module Twterm
57
57
  @@instances[id]
58
58
  end
59
59
 
60
+ def self.find_or_fetch(id)
61
+ instance = find(id)
62
+ (yield(instance) && return) if instance
63
+
64
+ Client.current.show_user(id) { |user| yield user }
65
+ end
66
+
60
67
  def self.cleanup
61
68
  referenced_users = Status.all.map(&:user)
62
69
  referenced_users.each(&:touch!)
@@ -1,3 +1,3 @@
1
1
  module Twterm
2
- VERSION = '1.0.8'
2
+ VERSION = '1.0.9'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twterm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8
4
+ version: 1.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryota Kameoka
@@ -159,6 +159,7 @@ files:
159
159
  - lib/twterm/status.rb
160
160
  - lib/twterm/tab/base.rb
161
161
  - lib/twterm/tab/conversation_tab.rb
162
+ - lib/twterm/tab/dumpable.rb
162
163
  - lib/twterm/tab/exceptions.rb
163
164
  - lib/twterm/tab/favorites.rb
164
165
  - lib/twterm/tab/list_tab.rb