termtter 1.6.0 → 1.7.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.
Files changed (98) hide show
  1. data/.gitignore +5 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +41 -75
  4. data/VERSION +1 -0
  5. data/bin/termtter +9 -2
  6. data/doc/Termtter-1.0-Release-Note-English.txt +37 -0
  7. data/doc/Termtter-1.0-Release-Note.txt +37 -0
  8. data/lib/plugins/another_prompt.rb +8 -8
  9. data/lib/plugins/ar.rb +102 -0
  10. data/lib/plugins/async.rb +1 -1
  11. data/lib/plugins/babelfish.rb +34 -0
  12. data/lib/plugins/confirm.rb +2 -0
  13. data/lib/plugins/crypt.rb +44 -0
  14. data/lib/plugins/defaults/auto_reload.rb +1 -1
  15. data/lib/plugins/defaults/command_line.rb +34 -17
  16. data/lib/plugins/defaults/confirm.rb +30 -0
  17. data/lib/plugins/defaults/hashtag.rb +1 -1
  18. data/lib/plugins/defaults/irb.rb +30 -0
  19. data/lib/plugins/defaults/keyword.rb +58 -0
  20. data/lib/plugins/defaults/list.rb +155 -0
  21. data/lib/plugins/defaults/plugin.rb +59 -0
  22. data/lib/plugins/defaults/retweet.rb +75 -23
  23. data/lib/plugins/defaults/standard_commands.rb +60 -87
  24. data/lib/plugins/defaults/standard_completion.rb +25 -15
  25. data/lib/plugins/defaults/stdout.rb +49 -10
  26. data/lib/plugins/defaults/switch.rb +1 -1
  27. data/lib/plugins/defaults/users.rb +63 -0
  28. data/lib/plugins/draft.rb +58 -0
  29. data/lib/plugins/expand-tinyurl.rb +5 -9
  30. data/lib/plugins/favotter.rb +1 -1
  31. data/lib/plugins/footer.rb +22 -0
  32. data/lib/plugins/friends.rb +5 -4
  33. data/lib/plugins/g.rb +9 -16
  34. data/lib/plugins/gem_install.rb +24 -0
  35. data/lib/plugins/gist.rb +20 -0
  36. data/lib/plugins/grass.rb +1 -1
  37. data/lib/plugins/gyazo.rb +78 -0
  38. data/lib/plugins/http_server.rb +1 -1
  39. data/lib/plugins/hugeurl.rb +6 -13
  40. data/lib/plugins/irc_gw.rb +15 -11
  41. data/lib/plugins/me.rb +1 -1
  42. data/lib/plugins/notify-send.rb +1 -1
  43. data/lib/plugins/notify-send3.rb +1 -1
  44. data/lib/plugins/open.rb +1 -1
  45. data/lib/plugins/open_url.rb +5 -1
  46. data/lib/plugins/pool.rb +1 -1
  47. data/lib/plugins/random.rb +1 -1
  48. data/lib/plugins/reply_retweet.rb +42 -0
  49. data/lib/plugins/screen-notify.rb +1 -1
  50. data/lib/plugins/sl.rb +3 -3
  51. data/lib/plugins/storage.rb +7 -10
  52. data/lib/plugins/storage/sqlite3.rb +155 -0
  53. data/lib/plugins/storage/status.rb +2 -0
  54. data/lib/plugins/stream.rb +1 -1
  55. data/lib/plugins/tinyurl.rb +3 -9
  56. data/lib/plugins/trends.rb +2 -2
  57. data/lib/plugins/truncate.rb +1 -1
  58. data/lib/plugins/w3mimg.rb +1 -1
  59. data/lib/termtter.rb +19 -20
  60. data/lib/termtter/active_rubytter.rb +4 -0
  61. data/lib/termtter/api.rb +22 -5
  62. data/lib/termtter/client.rb +55 -40
  63. data/lib/termtter/command.rb +3 -2
  64. data/lib/termtter/config_setup.rb +1 -1
  65. data/lib/termtter/config_template.erb +5 -0
  66. data/lib/termtter/default_config.rb +18 -0
  67. data/lib/termtter/hookable.rb +1 -0
  68. data/lib/termtter/httppool.rb +44 -0
  69. data/lib/termtter/memory_cache.rb +32 -0
  70. data/lib/termtter/optparse.rb +8 -15
  71. data/lib/termtter/rubytter_proxy.rb +65 -4
  72. data/lib/termtter/system_extensions.rb +40 -9
  73. data/lib/termtter/task.rb +2 -1
  74. data/spec/plugins/defaults/hashtag_spec.rb +8 -7
  75. data/spec/plugins/defaults/list_spec.rb +33 -0
  76. data/spec/plugins/defaults/plugin_spec.rb +17 -0
  77. data/spec/plugins/defaults/retweet_spec.rb +205 -0
  78. data/spec/plugins/draft_spec.rb +59 -0
  79. data/spec/plugins/expand-tinyurl_spec.rb +21 -0
  80. data/spec/plugins/footer_spec.rb +50 -0
  81. data/spec/plugins/storage/sqlite3_spec.rb +41 -0
  82. data/spec/termtter/api_spec.rb +1 -1
  83. data/spec/termtter/client_spec.rb +21 -21
  84. data/spec/termtter/command_spec.rb +8 -8
  85. data/spec/termtter/config_spec.rb +2 -2
  86. data/spec/termtter/memory_cache_spec.rb +20 -0
  87. data/spec/termtter/optparse_spec.rb +1 -1
  88. data/spec/termtter/rubytter_proxy_spec.rb +38 -0
  89. data/spec/termtter/system_extensions_spec.rb +25 -23
  90. data/spec/termtter/task_manager_spec.rb +1 -1
  91. data/spec/termtter_spec.rb +4 -2
  92. metadata +88 -19
  93. data/lib/plugins/defaults/lists.rb +0 -14
  94. data/lib/plugins/irb.rb +0 -6
  95. data/lib/plugins/storage/DB.rb +0 -37
  96. data/lib/termtter/version.rb +0 -4
  97. data/spec/plugins/defaults/lists_spec.rb +0 -34
  98. data/spec/plugins/storage/DB_spec_.rb +0 -12
@@ -1,5 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ warn '"confirm" plugin is deprecated. You can use "config.confirm" option instead it.'
4
+
3
5
  config.plugins.confirm.set_default(:commands, [:update, :reply, :direct, :retweet])
4
6
  config.plugins.confirm.set_default(
5
7
  :conditions,
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'openssl'
4
+
5
+ config.plugins.crypt.set_default(:salt, 'No Termtter, No Life.')
6
+
7
+ def encrypt(msg, salt)
8
+ encoder = OpenSSL::Cipher::DES.new
9
+ encoder.encrypt
10
+ encoder.pkcs5_keyivgen(salt)
11
+ (encoder.update(msg) + encoder.final).unpack('H*').to_s
12
+ end
13
+
14
+ def decrypt(msg, salt)
15
+ decoder = OpenSSL::Cipher::DES.new
16
+ decoder.decrypt
17
+ decoder.pkcs5_keyivgen(salt)
18
+ decoder.update([msg].pack('H*')) + decoder.final
19
+ end
20
+
21
+ Termtter::Client.register_command(
22
+ :name => :crypt,
23
+ :help => ['crypt TEXT', 'Post a encrypted message'],
24
+ :exec_proc => lambda {|arg|
25
+ msg = arg
26
+ prefix = '{DES}'
27
+ status = prefix + encrypt(msg, config.plugins.crypt.salt)
28
+ Termtter::API.twitter.update(status)
29
+ puts "=> #{status}"
30
+ status
31
+ }
32
+ )
33
+
34
+ Termtter::Client::register_hook(
35
+ :name => :decrypt_message,
36
+ :point => :filter_for_output,
37
+ :exec_proc => lambda do |statuses, event|
38
+ statuses.each do |s|
39
+ s.text.gsub!(/^\{DES\}(.*)/) do |m|
40
+ "{decrypt}" + decrypt($1, config.plugins.crypt.salt)
41
+ end
42
+ end
43
+ end
44
+ )
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  auto_reload_proc = lambda do
3
3
  begin
4
- Termtter::Client.call_commands('reload -r')
4
+ Termtter::Client.execute('reload -r')
5
5
  rescue TimeoutError
6
6
  # do nothing
7
7
  rescue Exception => e
@@ -31,7 +31,7 @@ module Termtter
31
31
  Client.get_hooks('prepare_command').each {|hook|
32
32
  command_text = hook.call(command_text)
33
33
  }
34
- Client.call_commands(command_text)
34
+ Client.execute(command_text)
35
35
  rescue TimeoutError
36
36
  puts TermColor.parse("<red>Time out :(</red>")
37
37
  end
@@ -62,30 +62,47 @@ module Termtter
62
62
  @input_thread.join
63
63
  end
64
64
 
65
+ def do_completion(input)
66
+ input = input.sub(/^\s*/, '')
67
+
68
+ words = []
69
+
70
+ words = Client.commands.values.
71
+ inject([]) {|array, command| array + [command.name] + command.aliases}.
72
+ map(&:to_s).
73
+ grep(/^\s*#{Regexp.quote(input)}/)
74
+
75
+ if words.empty?
76
+ command = Client.find_command(input)
77
+ words = command ? command.complement(input) : []
78
+ end
79
+
80
+ if words.empty?
81
+ Client.get_hooks(:completion).each do |hook|
82
+ words << hook.call(input) rescue nil
83
+ end
84
+ end
85
+
86
+ words.flatten.compact
87
+ rescue Exception => e
88
+ Client.handle_error(e)
89
+ end
90
+
65
91
  def setup_readline
66
92
  if Readline.respond_to?(:basic_word_break_characters=)
67
93
  Readline.basic_word_break_characters= "\t\n\"\\'`><=;|&{("
68
94
  end
95
+
69
96
  Readline.completion_case_fold = true
70
- Readline.completion_proc = lambda {|input|
71
- begin
72
- words = Client.commands.map {|name, command| command.complement(input) }.flatten.compact
73
-
74
- if words.empty?
75
- Client.get_hooks(:completion).each do |hook|
76
- words << hook.call(input) rescue nil
77
- end
78
- end
79
97
 
80
- words.flatten.compact
81
- rescue Exception => e
82
- Client.handle_error(e)
83
- end
84
- }
98
+ Readline.completion_proc = lambda {|input| do_completion(input) }
99
+
85
100
  vi_or_emacs = config.editing_mode
86
101
  unless vi_or_emacs.empty?
87
102
  Readline.__send__("#{vi_or_emacs}_editing_mode")
88
103
  end
104
+
105
+ Readline.rl_parse_and_bind('TAB: menu-complete')
89
106
  end
90
107
 
91
108
  def trap_setting()
@@ -95,7 +112,7 @@ module Termtter
95
112
  begin
96
113
  system "stty", stty_save
97
114
  ensure
98
- Client.call_commands('exit')
115
+ Client.execute('exit')
99
116
  end
100
117
  end
101
118
  trap("CONT") do
@@ -107,7 +124,7 @@ module Termtter
107
124
  end
108
125
  end
109
126
 
110
- Client.register_hook(:initialize_command_line, :point => :launched) do
127
+ Client.register_hook(:initialize_command_line, :point => :init_command_line) do
111
128
  CommandLine.start
112
129
  end
113
130
 
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ config.set_default(:confirm, false)
3
+
4
+ def confirm(message)
5
+ if config.confirm && !Termtter::Client.confirm(message)
6
+ raise Termtter::CommandCanceled
7
+ end
8
+ end
9
+
10
+ Termtter::RubytterProxy.register_hook(:confirm_update, :point => :pre_update) do |*args|
11
+ confirm("update #{args.first}")
12
+ args
13
+ end
14
+
15
+ Termtter::RubytterProxy.register_hook(:confirm_direct_message, :point => :pre_direct_message) do |*args|
16
+ confirm("direct #{args[0]} #{args[1]}")
17
+ args
18
+ end
19
+
20
+ Termtter::RubytterProxy.register_hook(:confirm_retweet, :point => :pre_retweet) do |*args|
21
+ status = Termtter::API.twitter.show(args.first)
22
+ confirm("retweet #{status.text}")
23
+ args
24
+ end
25
+
26
+ Termtter::RubytterProxy.register_hook(:confirm_delete, :point => :pre_remove_status) do |*args|
27
+ status = Termtter::API.twitter.show(args.first)
28
+ confirm("delete #{status.text}")
29
+ args
30
+ end
@@ -12,7 +12,7 @@ module Termtter::Client
12
12
  register_command(:raw_update) do |args|
13
13
  temp = public_storage[:hashtags]
14
14
  public_storage[:hashtags].clear
15
- call_commands "update #{args}"
15
+ execute "update #{args}"
16
16
  public_storage[:hashtags] = temp
17
17
  end
18
18
 
@@ -0,0 +1,30 @@
1
+ require 'irb'
2
+
3
+ module IRB
4
+ def self.start_session(binding)
5
+ unless @__irb_initialized
6
+ IRB.setup(nil)
7
+ @__irb_initialized = true
8
+ end
9
+
10
+ workspace = WorkSpace.new(binding)
11
+
12
+ irb = Irb.new(workspace)
13
+
14
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
15
+ @CONF[:MAIN_CONTEXT] = irb.context
16
+
17
+ catch(:IRB_EXIT) do
18
+ irb.eval_input
19
+ end
20
+ end
21
+ end
22
+
23
+ Termtter::Client.register_command(:irb) do |arg|
24
+ begin
25
+ completion_proc = Readline.completion_proc
26
+ IRB.start_session(binding)
27
+ ensure
28
+ Readline.completion_proc = completion_proc
29
+ end
30
+ end
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'set'
3
+
4
+ config.plugins.keyword.set_default(
5
+ :highlight_patterns,
6
+ [
7
+ ['on_cyan', 'white'],
8
+ ['on_green', 'white'],
9
+ ['on_magenta', 'white'],
10
+ ['on_red', 'white'],
11
+ ['on_blue', 'white'],
12
+ ['on_black', 'white'],
13
+ ['on_white', 'white'],
14
+ ['on_yellow', 'white'],
15
+ ]
16
+ )
17
+
18
+ config.plugins.keyword.set_default(:keywords, [])
19
+
20
+ module Termtter::Client
21
+ public_storage[:keywords] ||= Set.new(config.plugins.keyword.keywords)
22
+
23
+ register_hook :highlight_keywords, :point => :pre_coloring do |text, event|
24
+ public_storage[:keywords].each_with_index do |keyword, index|
25
+ highlight_pattern = config.plugins.keyword.highlight_patterns[index % config.plugins.keyword.highlight_patterns.size]
26
+ text = text.gsub(
27
+ /(#{Regexp.quote(keyword)})/i,
28
+ "<#{highlight_pattern[0]}><#{highlight_pattern[1]}>" +
29
+ "\\1" +
30
+ "</#{highlight_pattern[1]}></#{highlight_pattern[0]}>"
31
+ )
32
+ end
33
+ text
34
+ end
35
+
36
+ register_command(
37
+ 'keyword add',
38
+ :help => ['keyword add KEYWORD', 'Add a highlight keyword']
39
+ ) do |args|
40
+ args.split(/\s+/).each do |arg|
41
+ public_storage[:keywords] << arg
42
+ end
43
+ end
44
+
45
+ register_command(
46
+ 'keyword clear',
47
+ :help => ['keyword clear', 'Clear highlight keywords']
48
+ ) do |args|
49
+ public_storage[:keywords].clear
50
+ end
51
+
52
+ register_command(
53
+ 'keyword list',
54
+ :help => ['keyword list', 'List highlight keywords']
55
+ ) do |args|
56
+ p public_storage[:keywords].to_a
57
+ end
58
+ end
@@ -0,0 +1,155 @@
1
+ module Termtter::Client
2
+ public_storage[:lists] = []
3
+
4
+ register_hook(:fetch_my_lists, :point => :launched) do
5
+ begin
6
+ public_storage[:lists] +=
7
+ Termtter::API.twitter.lists(config.user_name).lists.map(&:full_name)
8
+ rescue TimeoutError
9
+ # do nothing
10
+ rescue Exception => e
11
+ Termtter::Client.handle_error(e)
12
+ end
13
+ end
14
+
15
+ register_command(
16
+ :name => :list, :aliases => [:l],
17
+ :exec_proc => lambda {|arg|
18
+ if arg =~ /\-([\d]+)/
19
+ options = {:count => $1}
20
+ arg = arg.gsub(/\-([\d]+)/, '')
21
+ else
22
+ options = {}
23
+ end
24
+
25
+ last_error = nil
26
+ if arg.empty?
27
+ event = :list_friends_timeline
28
+ statuses = Termtter::API.twitter.home_timeline(options)
29
+ else
30
+ event = :list_user_timeline
31
+ statuses = []
32
+ Array(arg.split).each do |user|
33
+ if user =~ /\/\w+/
34
+ user_name, slug = *user.split('/')
35
+ user_name = config.user_name if user_name.empty?
36
+ user_name = normalize_as_user_name(user_name)
37
+ options[:per_page] = options[:count]
38
+ options.delete(:count)
39
+ statuses += Termtter::API.twitter.list_statuses(user_name, slug, options)
40
+ else
41
+ begin
42
+ if user =~ /^\d+$/
43
+ profile = Termtter::API.twitter.user(nil, :screen_name => user) rescue nil
44
+ unless profile
45
+ status = Termtter::API.twitter.show(user) rescue nil
46
+ user = status.user.screen_name if status
47
+ end
48
+ end
49
+ user_name = normalize_as_user_name(user.sub(/\/$/, ''))
50
+ statuses += Termtter::API.twitter.user_timeline(user_name, options)
51
+ rescue Rubytter::APIError => e
52
+ last_error = e
53
+ end
54
+ end
55
+ end
56
+ end
57
+ output(statuses, event)
58
+ raise last_error if last_error
59
+ },
60
+ :help => ["list,l [USERNAME]/[SLUG] [-COUNT]", "List the posts"]
61
+ )
62
+
63
+ register_command(
64
+ :name => 'list list',
65
+ :exec => lambda {|arg|
66
+ unless arg.empty?
67
+ user_name = normalize_as_user_name(arg)
68
+ else
69
+ user_name = config.user_name
70
+ end
71
+ # TODO: show more information of lists
72
+ lists = Termtter::API.twitter.lists(user_name).lists
73
+ public_storage[:lists] += lists.map(&:full_name)
74
+ puts lists.map{|i| i.full_name}.join("\n")
75
+ },
76
+ :help => ["lists [USERNAME]", "Show Lists"]
77
+ )
78
+
79
+ register_command(
80
+ :name => %s{list follow},
81
+ :alias => %s{list add},
82
+ :exec => lambda { |arg|
83
+ list_name, *users = arg.split(' ')
84
+ slug = list_name_to_slug(list_name)
85
+ users.each{ |screen_name|
86
+ begin
87
+ user = Termtter::API.twitter.cached_user(screen_name) || Termtter::API.twitter.user(screen_name)
88
+ Termtter::API.twitter.add_member_to_list(slug, user.id)
89
+ puts "#{slug} + #{screen_name}"
90
+ rescue => e
91
+ handle_error(e)
92
+ end
93
+ }
94
+ },
95
+ :help => ["list follow SLUG USERNAME", "Follow users to the list"]
96
+ )
97
+
98
+ register_command(
99
+ :name => %s{list remove},
100
+ :exec => lambda { |arg|
101
+ list_name, *users = arg.split(' ')
102
+ slug = list_name_to_slug(list_name)
103
+ users.each{ |screen_name|
104
+ begin
105
+ user = Termtter::API.twitter.cached_user(screen_name) || Termtter::API.twitter.user(screen_name)
106
+ Termtter::API.twitter.remove_member_from_list(slug, user.id)
107
+ puts "#{slug} - #{screen_name}"
108
+ rescue => e
109
+ handle_error(e)
110
+ end
111
+ }
112
+ },
113
+ :help => ["list remove SLUG USERNAME", "Remove user(s) from the list"]
114
+ )
115
+
116
+ register_command(
117
+ :name => %s{list create},
118
+ :exec => lambda { |arg|
119
+ slug, *options = arg.split(' ')
120
+ param = { }
121
+
122
+ OptionParser.new {|opt|
123
+ opt.on('--description VALUE') {|v| param[:description] = v }
124
+ opt.on('--private') {|v| param[:mode] = 'private' }
125
+ opt.parse(options)
126
+ }
127
+ list = Termtter::API.twitter.create_list(slug, param)
128
+ public_storage[:lists] << list.full_name
129
+ p [list.full_name, param]
130
+ },
131
+ :help => ["list create SLUG [--description VALUE] [--private]", "Create list"]
132
+ )
133
+
134
+ register_command(
135
+ :name => %s{list delete},
136
+ :exec => lambda { |arg|
137
+ return unless confirm("Are you sure?")
138
+ arg.split(' ').each{ |list_name|
139
+ begin
140
+ slug = list_name_to_slug(list_name)
141
+ list = Termtter::API.twitter.delete_list(slug)
142
+ public_storage[:lists].delete(list.full_name)
143
+ puts "#{list.full_name} deleted"
144
+ rescue => e
145
+ handle_error(e)
146
+ end
147
+ }
148
+ },
149
+ :help => ["list delete SLUG", "Delete list"]
150
+ )
151
+
152
+ def self.list_name_to_slug(list_name)
153
+ list_name[/([^\/]*)$/]
154
+ end
155
+ end
@@ -0,0 +1,59 @@
1
+ require 'fileutils'
2
+
3
+ module Termtter::Client
4
+ register_command('plugin edit') do |arg|
5
+ if /\-s/ =~ arg
6
+ arg = arg.sub('-s', '').strip
7
+ include_system_plugins = true
8
+ end
9
+
10
+ if path = search_plugin_file(arg, include_system_plugins)
11
+ open_editor(path)
12
+ plug(arg)
13
+ puts "reload => #{arg}"
14
+ else
15
+ puts TermColor.parse('<red>Plugin not found :(</red>')
16
+ end
17
+ end
18
+
19
+ register_command('plugin show') do |arg|
20
+ if path = search_plugin_file(arg, true)
21
+ puts IO.read(path)
22
+ else
23
+ puts TermColor.parse('<red>Plugin not found :(</red>')
24
+ end
25
+ end
26
+
27
+ register_command('plugin create') do |arg|
28
+ path = "#{Termtter::CONF_DIR}/plugins/#{arg}.rb"
29
+ unless File.exists?(path)
30
+ FileUtils.mkdir_p(File.dirname(path))
31
+ # TODO: insert plugin template to plugin file
32
+ open_editor(path)
33
+ plug(arg)
34
+ puts "load => #{arg}"
35
+ else
36
+ puts TermColor.parse('<red>The plugin already exists :(</red>')
37
+ end
38
+ end
39
+
40
+ def self.open_editor(path)
41
+ # TODO: change to common method or use launchy
42
+ system ENV['EDITOR'] || 'vim', path
43
+ end
44
+
45
+ def self.search_plugin_file(name, include_system_plugins = false)
46
+ regex = /#{Regexp.quote(name + '.rb')}$/
47
+ plugin_files(include_system_plugins).detect {|f| regex =~ f}
48
+ end
49
+
50
+ def self.plugin_files(include_system_plugins = false)
51
+ files = Dir["#{Termtter::CONF_DIR}/plugins/*.rb"]
52
+ files += Dir["#{File.expand_path(File.dirname(__FILE__))}/*.rb"] if include_system_plugins
53
+ files
54
+ end
55
+
56
+ # TODO: rename command 'plug' to 'plugin load' and define 'plug' as alias
57
+ # TODO: completion for plugin names
58
+ end
59
+