twterm 1.3.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -18
  3. data/bin/twterm +1 -1
  4. data/lib/twterm/app.rb +120 -30
  5. data/lib/twterm/client.rb +10 -13
  6. data/lib/twterm/completion_mamanger.rb +11 -6
  7. data/lib/twterm/direct_message.rb +6 -28
  8. data/lib/twterm/direct_message_composer.rb +10 -5
  9. data/lib/twterm/direct_message_manager.rb +5 -6
  10. data/lib/twterm/event/notification/abstract_notification.rb +27 -0
  11. data/lib/twterm/event/notification/error.rb +13 -0
  12. data/lib/twterm/event/notification/info.rb +13 -0
  13. data/lib/twterm/event/notification/success.rb +13 -0
  14. data/lib/twterm/event/notification/warning.rb +13 -0
  15. data/lib/twterm/event_dispatcher.rb +1 -1
  16. data/lib/twterm/extensions/array.rb +5 -0
  17. data/lib/twterm/extensions/enumerator/lazy.rb +3 -0
  18. data/lib/twterm/extensions/string.rb +0 -4
  19. data/lib/twterm/friendship.rb +1 -85
  20. data/lib/twterm/image/between.rb +31 -0
  21. data/lib/twterm/image/blank_line.rb +21 -0
  22. data/lib/twterm/image/bold.rb +31 -0
  23. data/lib/twterm/image/brackets.rb +21 -0
  24. data/lib/twterm/image/color.rb +45 -0
  25. data/lib/twterm/image/empty.rb +21 -0
  26. data/lib/twterm/image/horizontal_sequential_image.rb +48 -0
  27. data/lib/twterm/image/parens.rb +21 -0
  28. data/lib/twterm/image/string_image.rb +38 -0
  29. data/lib/twterm/image/vertical_sequential_image.rb +43 -0
  30. data/lib/twterm/image.rb +107 -0
  31. data/lib/twterm/key_mapper/abstract_key_mapper.rb +51 -0
  32. data/lib/twterm/key_mapper/app_key_mapper.rb +13 -0
  33. data/lib/twterm/key_mapper/cursor_key_mapper.rb +13 -0
  34. data/lib/twterm/key_mapper/general_key_mapper.rb +18 -0
  35. data/lib/twterm/key_mapper/no_such_command.rb +20 -0
  36. data/lib/twterm/key_mapper/no_such_key.rb +16 -0
  37. data/lib/twterm/key_mapper/status_key_mapper.rb +18 -0
  38. data/lib/twterm/key_mapper/tab_key_mapper.rb +31 -0
  39. data/lib/twterm/key_mapper.rb +127 -0
  40. data/lib/twterm/list.rb +0 -31
  41. data/lib/twterm/notifier.rb +7 -7
  42. data/lib/twterm/repository/abstract_entity_repository.rb +41 -0
  43. data/lib/twterm/repository/abstract_expirable_entity_repository.rb +35 -0
  44. data/lib/twterm/repository/abstract_repository.rb +64 -0
  45. data/lib/twterm/repository/direct_message_repository.rb +14 -0
  46. data/lib/twterm/repository/friendship_repository.rb +108 -0
  47. data/lib/twterm/repository/hashtag_repository.rb +39 -0
  48. data/lib/twterm/repository/list_repository.rb +14 -0
  49. data/lib/twterm/repository/status_repository.rb +36 -0
  50. data/lib/twterm/repository/user_repository.rb +22 -0
  51. data/lib/twterm/rest_client.rb +107 -63
  52. data/lib/twterm/screen.rb +21 -15
  53. data/lib/twterm/search_query_window.rb +139 -0
  54. data/lib/twterm/status.rb +14 -108
  55. data/lib/twterm/streaming_client.rb +13 -12
  56. data/lib/twterm/tab/base.rb +48 -8
  57. data/lib/twterm/tab/direct_message/conversation.rb +53 -52
  58. data/lib/twterm/tab/direct_message/conversation_list.rb +46 -45
  59. data/lib/twterm/tab/dumpable.rb +3 -3
  60. data/lib/twterm/tab/key_assignments_cheatsheet.rb +58 -57
  61. data/lib/twterm/tab/loadable.rb +20 -0
  62. data/lib/twterm/tab/new/list.rb +32 -43
  63. data/lib/twterm/tab/new/search.rb +31 -44
  64. data/lib/twterm/tab/new/start.rb +44 -55
  65. data/lib/twterm/tab/new/user.rb +15 -12
  66. data/lib/twterm/tab/rate_limit_status.rb +84 -0
  67. data/lib/twterm/tab/scrollable.rb +39 -19
  68. data/lib/twterm/tab/searchable.rb +133 -0
  69. data/lib/twterm/tab/statuses/base.rb +139 -129
  70. data/lib/twterm/tab/statuses/conversation.rb +26 -15
  71. data/lib/twterm/tab/statuses/favorites.rb +10 -8
  72. data/lib/twterm/tab/statuses/home.rb +10 -9
  73. data/lib/twterm/tab/statuses/list_timeline.rb +12 -8
  74. data/lib/twterm/tab/statuses/mentions.rb +17 -11
  75. data/lib/twterm/tab/statuses/search.rb +8 -5
  76. data/lib/twterm/tab/statuses/user_timeline.rb +11 -8
  77. data/lib/twterm/tab/user_list_management.rb +109 -0
  78. data/lib/twterm/tab/user_tab.rb +125 -126
  79. data/lib/twterm/tab/users/base.rb +39 -41
  80. data/lib/twterm/tab/users/followers.rb +9 -6
  81. data/lib/twterm/tab/users/friends.rb +9 -6
  82. data/lib/twterm/tab_manager.rb +64 -40
  83. data/lib/twterm/tweetbox.rb +18 -13
  84. data/lib/twterm/uri_opener.rb +2 -1
  85. data/lib/twterm/user.rb +2 -110
  86. data/lib/twterm/version.rb +1 -1
  87. data/lib/twterm/view.rb +30 -0
  88. data/lib/twterm.rb +3 -9
  89. data/spec/fixtures/status.json +107 -0
  90. data/spec/fixtures/user.json +102 -0
  91. data/spec/spec_helper.rb +7 -0
  92. data/spec/supports/shared_examples/abstract_key_mapper.rb +17 -0
  93. data/spec/twterm/extension/enumerator/lazy_spec.rb +11 -0
  94. data/spec/twterm/friendship_spec.rb +0 -102
  95. data/spec/twterm/image/blank_line_spec.rb +11 -0
  96. data/spec/twterm/image/brackets_spec.rb +12 -0
  97. data/spec/twterm/image/color_spec.rb +22 -0
  98. data/spec/twterm/image/empry_spec.rb +11 -0
  99. data/spec/twterm/image/horizontal_sequential_image_spec.rb +15 -0
  100. data/spec/twterm/image/parens_spec.rb +12 -0
  101. data/spec/twterm/image/string_image_spec.rb +12 -0
  102. data/spec/twterm/image/vertical_sequential_image_spec.rb +14 -0
  103. data/spec/twterm/image_spec.rb +65 -0
  104. data/spec/twterm/key_mapper/abstract_key_mapper_spec.rb +21 -0
  105. data/spec/twterm/key_mapper/app_key_mapper_spec.rb +7 -0
  106. data/spec/twterm/key_mapper/status_key_mapper_spec.rb +7 -0
  107. data/spec/twterm/key_mapper/tab_key_mapper_spec.rb +7 -0
  108. data/spec/twterm/repository/friendship_repository_spec.rb +108 -0
  109. data/spec/twterm/status_spec.rb +58 -0
  110. data/spec/twterm/user_spec.rb +94 -0
  111. data/twterm.gemspec +13 -10
  112. metadata +129 -35
  113. data/lib/twterm/event/notification.rb +0 -33
  114. data/lib/twterm/extensions/integer.rb +0 -5
  115. data/lib/twterm/filter_query_window.rb +0 -91
  116. data/lib/twterm/filterable_list.rb +0 -41
  117. data/lib/twterm/history/base.rb +0 -21
  118. data/lib/twterm/history/hashtag.rb +0 -13
  119. data/lib/twterm/history/savable.rb +0 -37
  120. data/lib/twterm/history/screen_name.rb +0 -11
  121. data/lib/twterm/promise.rb +0 -143
@@ -0,0 +1,84 @@
1
+ require 'twterm/tab/loadable'
2
+
3
+ module Twterm
4
+ module Tab
5
+ class RateLimitStatus < Tab::Base
6
+ include Loadable
7
+ include Scrollable
8
+
9
+ @@mutex = Mutex.new
10
+ @@status = nil
11
+
12
+ def initialize(app, client)
13
+ super(app, client)
14
+
15
+ scroller.set_no_cursor_mode!
16
+
17
+ @ticker = Scheduler.new(1) { render }
18
+
19
+ @scheduler = Scheduler.new(10) { fetch }
20
+
21
+ fetch.then { initially_loaded! }
22
+ end
23
+
24
+ def close
25
+ @scheduler.kill
26
+ @ticker.kill
27
+ super
28
+ end
29
+
30
+ def drawable_item_count
31
+ window.maxy - 2
32
+ end
33
+
34
+ def image
35
+ return Image.string('Loading...') unless initially_loaded?
36
+
37
+ items
38
+ .drop(scroller.offset)
39
+ .take(drawable_item_count)
40
+ .reduce(Image.empty, :|)
41
+ end
42
+
43
+ def items
44
+ return [] unless initially_loaded?
45
+
46
+ @@status[:resources].flat_map do |category, limits|
47
+ [
48
+ !Image.string(category.to_s.gsub('_', ' ')).color(:green),
49
+ Image.blank_line,
50
+ *limits.flat_map do |endpoint, data|
51
+ limit, remaining = data[:limit], data[:remaining]
52
+ t = Time.at(data[:reset])
53
+ diff = (t - Time.now).round
54
+
55
+ [
56
+ Image.string(" #{endpoint}").color(:cyan) - (
57
+ limit == remaining \
58
+ ? Image.empty
59
+ : Image.string(" (Resets #{diff.positive? ? "in #{diff} seconds" : 'soon...'})")),
60
+ Image.string(' ') - Image.remaining_resource(remaining, limit, 50) - Image.string(" #{remaining}/#{limit}"),
61
+ Image.blank_line,
62
+ ]
63
+ end,
64
+ Image.blank_line,
65
+ ]
66
+ end
67
+ end
68
+
69
+ def respond_to_key(key)
70
+ scroller.respond_to_key(key)
71
+ end
72
+
73
+ def title
74
+ 'Rate Limit Status'.freeze
75
+ end
76
+
77
+ private
78
+
79
+ def fetch
80
+ client.rate_limit_status.then { |status| @@mutex.synchronize { @@status = status } }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -9,9 +9,9 @@ module Twterm
9
9
  def scroller
10
10
  return @scroller unless @scroller.nil?
11
11
 
12
- @scroller = Scroller.new
12
+ @scroller = self.class::Scroller.new(self)
13
13
  @scroller.delegate = self
14
- @scroller.after_move { refresh }
14
+ @scroller.after_move { render }
15
15
  @scroller
16
16
  end
17
17
 
@@ -21,10 +21,9 @@ module Twterm
21
21
  items.count
22
22
  end
23
23
 
24
- private
25
-
26
24
  class Scroller
27
25
  extend Forwardable
26
+ include Publisher
28
27
 
29
28
  attr_reader :index, :offset
30
29
 
@@ -35,19 +34,21 @@ module Twterm
35
34
  add_hook(:after_move, &block)
36
35
  end
37
36
 
38
- def current_item
39
- items[index]
37
+ def current_index?(i)
38
+ index == offset + i
40
39
  end
41
40
 
42
- def current_item?(i)
43
- index == offset + i
41
+ def current_item
42
+ items[index]
44
43
  end
45
44
 
46
45
  def no_cursor_mode?
47
46
  !!@no_cursor_mode
48
47
  end
49
48
 
50
- def initialize
49
+ def initialize(tab)
50
+ @tab = tab
51
+
51
52
  @index = 0
52
53
  @offset = 0
53
54
  @no_cursor_mode = false
@@ -58,7 +59,6 @@ module Twterm
58
59
  end
59
60
 
60
61
  def item_appended!
61
- @index -= 1
62
62
  @offset -= 1 if @offset > 0
63
63
  end
64
64
 
@@ -69,7 +69,7 @@ module Twterm
69
69
  end
70
70
 
71
71
  def move_down
72
- return if count == 0 || index == count - 1
72
+ return if count == 0 || index == count - 1 || (no_cursor_mode? && offset + drawable_item_count >= count)
73
73
  # return when there are no items or cursor is at the bottom
74
74
 
75
75
  @index += 1
@@ -81,7 +81,7 @@ module Twterm
81
81
  def move_to_bottom
82
82
  return if count == 0 || index == count - 1
83
83
 
84
- @index = count - 1
84
+ @index = no_cursor_mode? ? count - drawable_item_count : count - 1
85
85
  @offset = [count - drawable_item_count + 1, 0].max
86
86
 
87
87
  @offset += 1 until last_item_shown?
@@ -89,6 +89,16 @@ module Twterm
89
89
  hook :after_move
90
90
  end
91
91
 
92
+ def move_to(n)
93
+ if nth_item_drawable?(n)
94
+ @index = n
95
+ else
96
+ @index = @offset = n
97
+ end
98
+
99
+ hook :after_move
100
+ end
101
+
92
102
  def move_to_top
93
103
  return if count.zero? || index.zero?
94
104
 
@@ -108,23 +118,31 @@ module Twterm
108
118
  end
109
119
 
110
120
  def nth_item_drawable?(n)
111
- n.between?(offset, offset + drawable_item_count)
121
+ n.between?(offset, offset + drawable_item_count - 1)
112
122
  end
113
123
 
114
124
  def respond_to_key(key)
125
+ k = KeyMapper.instance
126
+
115
127
  case key
116
- when ?d, 4
128
+ when k[:general, :page_down]
117
129
  10.times { move_down }
118
- when ?g
130
+ when k[:general, :top]
119
131
  move_to_top
120
- when ?G
132
+ when k[:general, :bottom]
121
133
  move_to_bottom
122
- when ?j, 14, Curses::Key::DOWN
134
+ when k[:general, :down], Curses::Key::DOWN
123
135
  move_down
124
- when ?k, 16, Curses::Key::UP
136
+ when k[:general, :up], Curses::Key::UP
125
137
  move_up
126
- when ?u, 21
138
+ when k[:general, :page_up]
127
139
  10.times { move_up }
140
+ when k[:cursor, :top_of_window]
141
+ move_to(offset)
142
+ when k[:cursor, :middle_of_window]
143
+ move_to((2 * offset + [drawable_item_count, total_item_count - offset].min - 1) / 2)
144
+ when k[:cursor, :bottom_of_window]
145
+ move_to(offset + [drawable_item_count, total_item_count - offset].min - 1)
128
146
  else
129
147
  return false
130
148
  end
@@ -138,6 +156,8 @@ module Twterm
138
156
 
139
157
  private
140
158
 
159
+ attr_reader :tab
160
+
141
161
  def add_hook(name, &block)
142
162
  @hooks ||= {}
143
163
  @hooks[name] = block
@@ -0,0 +1,133 @@
1
+ require 'twterm/event/notification/info'
2
+ require 'twterm/event/notification/warning'
3
+ require 'twterm/search_query_window'
4
+
5
+ module Twterm
6
+ module Tab
7
+ module Searchable
8
+ include Scrollable
9
+ extend Forwardable
10
+
11
+ def_delegators :scroller, :search_query
12
+
13
+ def matches?(item, query)
14
+ raise NotImplementedError, '`matches?` method must be implemented'
15
+ end
16
+
17
+ class Scroller < Scrollable::Scroller
18
+ extend Forwardable
19
+ include Publisher
20
+
21
+ attr_reader :search_query
22
+
23
+ def_delegators :search_query_window,
24
+ :searching_backward!, :searching_backward?,
25
+ :searching_down!, :searching_down?,
26
+ :searching_forward!, :searching_forward?,
27
+ :searching_up!, :searching_up?
28
+
29
+ def initialize(*)
30
+ super
31
+ @search_query = ''
32
+ end
33
+
34
+ def find_next
35
+ searching_forward!
36
+
37
+ search_query_window.render_last_query
38
+ search
39
+ end
40
+
41
+ def find_previous
42
+ searching_backward!
43
+
44
+ search_query_window.render_last_query
45
+ search
46
+ end
47
+
48
+ def respond_to_key(key)
49
+ k = KeyMapper.instance
50
+
51
+ return if super
52
+
53
+ case key
54
+ when k[:tab, :find_next]
55
+ find_next
56
+ when k[:tab, :find_previous]
57
+ find_previous
58
+ when k[:tab, :search_down]
59
+ search_down
60
+ when k[:tab, :search_up]
61
+ search_up
62
+ else
63
+ return false
64
+ end
65
+
66
+ true
67
+ end
68
+
69
+ def search_up
70
+ searching_up!
71
+ searching_forward!
72
+ ask
73
+
74
+ return if search_query.empty?
75
+
76
+ find_next
77
+ end
78
+
79
+ def search_down
80
+ searching_down!
81
+ searching_forward!
82
+ ask
83
+
84
+ return if search_query.empty?
85
+
86
+ find_next
87
+ end
88
+
89
+ private
90
+
91
+ def ask
92
+ @search_query = search_query_window.input || ''
93
+ end
94
+
95
+ def search
96
+ @search_query = search_query_window.last_query if search_query.empty?
97
+
98
+ if @search_query.empty?
99
+ event = Event::Notification::Info.new("search query is empty. Press '/' or '?' to start searching.")
100
+ publish(event)
101
+ return
102
+ end
103
+
104
+ f = searching_down? ^ searching_forward? ? -> xs { xs.reverse } : -> xs { xs }
105
+ previous_index = index
106
+
107
+ xs = [
108
+ *f.(items.each_with_index.drop(index.succ) + items.each_with_index.take(index)),
109
+ [current_item, index]
110
+ ]
111
+ _, index = xs.find { |x, _| tab.matches?(x, search_query) }
112
+
113
+ if index.nil?
114
+ publish(Event::Notification::Warning.new("No matches found: \"#{search_query}\""))
115
+ else
116
+ hit_bottom = Event::Notification::Info.new('search hit BOTTOM, continuing at TOP')
117
+ hit_top = Event::Notification::Info.new('search hit TOP, continuing at BOTTOM')
118
+
119
+ publish(hit_bottom) if (searching_down? ^ searching_backward?) && index <= previous_index
120
+ publish(hit_top) if (searching_up? ^ searching_backward?) && index >= previous_index
121
+ move_to(index)
122
+ end
123
+ end
124
+
125
+ def search_query_window
126
+ SearchQueryWindow.instance
127
+ end
128
+
129
+ alias_method :count, :total_item_count
130
+ end
131
+ end
132
+ end
133
+ end