palbo-lijab 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/lijab ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'lijab'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'lijab'
8
+ end
9
+
data/ext/extconf.rb ADDED
@@ -0,0 +1,20 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ dir_config("readline")
5
+
6
+ $headers = ["stdio.h", "readline/readline.h"]
7
+
8
+ exit unless have_library("readline", "readline")
9
+
10
+ $headers.each { |h| exit unless have_header(h) }
11
+
12
+ %w{"rl_line_buffer"
13
+ "rl_insert_text"
14
+ "rl_parse_and_bind"
15
+ "rl_redisplay"}.each { |f| exit unless have_func(f, $headers) }
16
+
17
+ %w{"rl_pre_input_hook"
18
+ "rl_getc_function"}.each { |f| exit unless have_var(f, $headers) }
19
+
20
+ create_makefile("readline_extra")
@@ -0,0 +1,172 @@
1
+
2
+ #include <stdio.h>
3
+
4
+ #include <readline/readline.h>
5
+
6
+ #include "ruby.h"
7
+ #include "rubyio.h"
8
+
9
+ #define PRE_INPUT_PROC "pre_input_proc"
10
+ static ID pre_input_proc;
11
+
12
+ #define CHAR_INPUT_PROC "char_input_proc"
13
+ static ID char_input_proc;
14
+
15
+
16
+ VALUE mReadlineExtra = Qnil;
17
+
18
+ void Init_readline_extra();
19
+
20
+ static VALUE readline_extra_s_get_line_buffer(VALUE self);
21
+ static VALUE readline_extra_s_set_line_buffer(VALUE self, VALUE str);
22
+ static VALUE readline_extra_insert_text(VALUE self, VALUE text);
23
+ static VALUE readline_extra_parse_and_bind(VALUE self, VALUE text);
24
+ static VALUE readline_extra_redisplay(VALUE self);
25
+
26
+ static VALUE readline_extra_s_set_pre_input_proc(VALUE self, VALUE proc);
27
+ static VALUE readline_extra_s_get_pre_input_proc(VALUE self);
28
+ static int readline_extra_on_pre_input_hook(void);
29
+
30
+ int readline_extra_getc(FILE *stream);
31
+ static VALUE readline_extra_s_set_char_input_proc(VALUE self, VALUE proc);
32
+ static VALUE readline_extra_s_get_char_input_proc(VALUE self);
33
+ static int readline_extra_on_char_input_hook(int c);
34
+
35
+ void Init_readline_extra()
36
+ {
37
+ mReadlineExtra = rb_define_module("Readline");
38
+
39
+ pre_input_proc = rb_intern(PRE_INPUT_PROC);
40
+ rl_pre_input_hook = readline_extra_on_pre_input_hook;
41
+
42
+ rb_define_singleton_method(mReadlineExtra, "pre_input_proc=", readline_extra_s_set_pre_input_proc, 1);
43
+ rb_define_singleton_method(mReadlineExtra, "pre_input_proc", readline_extra_s_get_pre_input_proc, 0);
44
+
45
+ rl_getc_function = readline_extra_getc;
46
+ char_input_proc = rb_intern(CHAR_INPUT_PROC);
47
+
48
+ rb_define_singleton_method(mReadlineExtra, "char_input_proc=", readline_extra_s_set_char_input_proc, 1);
49
+ rb_define_singleton_method(mReadlineExtra, "char_input_proc", readline_extra_s_get_char_input_proc, 0);
50
+
51
+ rb_define_module_function(mReadlineExtra, "line_buffer", readline_extra_s_get_line_buffer, 0);
52
+ rb_define_module_function(mReadlineExtra, "line_buffer=", readline_extra_s_set_line_buffer, 1);
53
+ rb_define_module_function(mReadlineExtra, "insert_text", readline_extra_insert_text, 1);
54
+ rb_define_module_function(mReadlineExtra, "parse_and_bind", readline_extra_parse_and_bind, 1);
55
+ rb_define_module_function(mReadlineExtra, "redisplay", readline_extra_redisplay, 0);
56
+
57
+ }
58
+
59
+ static VALUE readline_extra_s_get_line_buffer(VALUE self)
60
+ {
61
+ if (rl_line_buffer)
62
+ return rb_tainted_str_new2(rl_line_buffer);
63
+ else
64
+ return rb_tainted_str_new2("");
65
+ }
66
+
67
+ static VALUE readline_extra_s_set_line_buffer(VALUE self, VALUE str)
68
+ {
69
+ rl_replace_line(RSTRING(str)->ptr, 1);
70
+ rl_point = rl_end;
71
+
72
+ return readline_extra_s_get_line_buffer(self);
73
+ }
74
+
75
+ static VALUE readline_extra_insert_text(VALUE self, VALUE text)
76
+ {
77
+ rl_insert_text(RSTRING(text)->ptr);
78
+ return self;
79
+ }
80
+
81
+ // doesn't seem to work
82
+ static VALUE readline_extra_parse_and_bind(VALUE self, VALUE text)
83
+ {
84
+ rl_parse_and_bind(RSTRING(text)->ptr);
85
+ return self;
86
+ }
87
+
88
+ static VALUE readline_extra_redisplay(VALUE self)
89
+ {
90
+ rl_redisplay();
91
+ return self;
92
+ }
93
+
94
+ static int
95
+ readline_extra_on_pre_input_hook(void)
96
+ {
97
+ VALUE proc;
98
+
99
+ proc = rb_attr_get(mReadlineExtra, pre_input_proc);
100
+
101
+ if (NIL_P(proc))
102
+ return -1;
103
+
104
+ rb_funcall(proc, rb_intern("call"), 0);
105
+
106
+ return 0;
107
+ }
108
+
109
+ static VALUE
110
+ readline_extra_s_set_pre_input_proc(VALUE self, VALUE proc)
111
+ {
112
+ rb_secure(4);
113
+
114
+ if (!NIL_P(proc) && !rb_respond_to(proc, rb_intern("call")))
115
+ rb_raise(rb_eArgError, "argument must respond to `call'");
116
+
117
+ return rb_ivar_set(mReadlineExtra, pre_input_proc, proc);
118
+ }
119
+
120
+ static VALUE
121
+ readline_extra_s_get_pre_input_proc(VALUE self)
122
+ {
123
+ rb_secure(4);
124
+ return rb_attr_get(mReadlineExtra, pre_input_proc);
125
+ }
126
+
127
+ int readline_extra_getc(FILE *stream)
128
+ {
129
+ int c;
130
+ c = rl_getc(stream);
131
+ return readline_extra_on_char_input_hook(c);
132
+ }
133
+
134
+ static int
135
+ readline_extra_on_char_input_hook(int c)
136
+ {
137
+ VALUE proc, ret;
138
+
139
+ proc = rb_attr_get(mReadlineExtra, char_input_proc);
140
+
141
+ if (NIL_P(proc))
142
+ return c;
143
+
144
+ ret = rb_funcall(proc, rb_intern("call"), 1, INT2FIX(c));
145
+
146
+ if(ret == Qnil)
147
+ return 0;
148
+
149
+ if(!FIXNUM_P(ret))
150
+ rb_raise(rb_eTypeError, "Readline::char_input_proc must return nil or a Fixnum");
151
+
152
+ return (int)FIX2INT(ret);
153
+ }
154
+
155
+ static VALUE
156
+ readline_extra_s_set_char_input_proc(VALUE self, VALUE proc)
157
+ {
158
+ rb_secure(4);
159
+
160
+ if (!NIL_P(proc) && !rb_respond_to(proc, rb_intern("call")))
161
+ rb_raise(rb_eArgError, "argument must respond to `call'");
162
+
163
+ return rb_ivar_set(mReadlineExtra, char_input_proc, proc);
164
+ }
165
+
166
+ static VALUE
167
+ readline_extra_s_get_char_input_proc(VALUE self)
168
+ {
169
+ rb_secure(4);
170
+ return rb_attr_get(mReadlineExtra, char_input_proc);
171
+ }
172
+
@@ -0,0 +1,101 @@
1
+
2
+ module Lijab
3
+ module Commands
4
+
5
+ module ContactsCommandMixin
6
+ SORTBY = ["status", "alpha"]
7
+
8
+ def completer(line)
9
+ sortby = line.split[1] || ""
10
+ if SORTBY.grep(sortby).empty?
11
+ SORTBY.grep(/^#{Regexp.escape(sortby)}/)
12
+ end
13
+ end
14
+
15
+ def group_contacts(contacts)
16
+ grouped = {}
17
+ contacts.each do |jid,contact|
18
+ groups = contact.roster_item.groups
19
+ if groups.empty?
20
+ (grouped["<no group>"] ||= []) << contact
21
+ else
22
+ groups.each { |g| (grouped[g] ||= []) << contact }
23
+ end
24
+ end
25
+
26
+ grouped = grouped.sort_by { |g,c| g }
27
+ end
28
+
29
+ def print_contacts(sort_by_status=false, online_only=false)
30
+ if sort_by_status
31
+ contacts = Main.contacts.sort { |a, b| -(a[1].presence <=> b[1].presence) }
32
+ else
33
+ contacts = Main.contacts.sort_by { |j,c| c.simple_name }
34
+ end
35
+
36
+ if Config.opts[:show_groups_in_contact_list]
37
+ grouped = group_contacts(contacts)
38
+ else
39
+ grouped = {nil => contacts.map { |j,c| c }}
40
+ end
41
+
42
+ s = []
43
+ grouped.each do |group, contactz|
44
+ if online_only
45
+ next unless contactz.any? { |c| c.online? }
46
+ end
47
+
48
+ s << " #{group} ".on_blue if group
49
+ contactz.each do |contact|
50
+ unless online_only && !contact.online?
51
+ main = contact.presence
52
+ s << "* #{contact.simple_name} #{main.pretty(true)} " \
53
+ "(#{main.priority || 0}) [#{main.from || contact.jid}]"
54
+
55
+ if online_only && contact.roster_item.presences.length > 1
56
+ contact.roster_item.presences.each do |p|
57
+ if p.from != main.from
58
+ s << " #{p.from} #{p.pretty(true)} (#{p.priority || 0})"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ Out::put(s.join("\n")) unless s.empty?
67
+ end
68
+
69
+ end
70
+
71
+ Command.define :contacts do
72
+ usage "/contacts [status|alpha]"
73
+ description "Show a list of all contacts. Sorted alphabetically or by status."
74
+
75
+ SORTBY = ["status", "alpha"]
76
+
77
+ def run(args)
78
+ print_contacts(args.split[0] == "status")
79
+ end
80
+
81
+ class << self
82
+ include ContactsCommandMixin
83
+ end
84
+ end
85
+
86
+ Command.define :who do
87
+ usage "/who [status|alpha]"
88
+ description "Show a list of online contacts. Sorted alphabetically or by status."
89
+
90
+ def run(args)
91
+ print_contacts(args.split[0] == "status", true)
92
+ end
93
+
94
+ class << self
95
+ include ContactsCommandMixin
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module Lijab
3
+ module Commands
4
+
5
+ Command.define :set do
6
+ usage "/set <option> [<value>]"
7
+ description "Modify the options. Print the current value if no <value> is given.\n" \
8
+ "See #{Config.files[:config]} for the available options."
9
+ def run(args)
10
+ option, value = args.split(nil, 2).strip
11
+
12
+ option = option.to_sym if option
13
+
14
+ if !(Config.opts[option].is_a?(String) ||
15
+ Config.opts[option].is_a?(Numeric) ||
16
+ [true, false, nil].include?(Config.opts[option])) &&
17
+ Config.opts.key?(option)
18
+ raise CommandError, %{can't change "#{option} with /set"}
19
+ elsif !Config.opts.key?(option)
20
+ raise CommandError, %{no such option "#{option}"}
21
+ end
22
+
23
+ if value && !value.empty?
24
+ begin
25
+ val = YAML.load(value)
26
+ #raise TypeError unless val.is_a?(Config.opts[option].class)
27
+ Config.opts[option] = val
28
+ rescue
29
+ Out::error("invalid value", false)
30
+ end
31
+ else
32
+ Out::put(YAML.dump(Config.opts[option])[4..-1].chomp)
33
+ end
34
+
35
+ end
36
+
37
+ def completer(line)
38
+ option = line.split(nil, 2).strip[1] || ""
39
+ Config.opts.keys.find_all do |k|
40
+ k.to_s =~ /^#{Regexp.escape(option)}/ &&
41
+ (Config.opts[k].is_a?(String) ||
42
+ Config.opts[k].is_a?(Numeric) ||
43
+ [true, false, nil].include?(Config.opts[k]))
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,132 @@
1
+
2
+ module Lijab
3
+ module Commands
4
+
5
+ Command.define :help do
6
+ usage "/help [<command> | commands]"
7
+ description "Get some help."
8
+
9
+ def run(args)
10
+ if args.empty?
11
+ s = %Q{
12
+ When in doubt, hit <tab>.
13
+
14
+ Some general hints:
15
+
16
+ Run "lijab -a <name>" to connect to the account named <name>.
17
+
18
+ Tab on an empty line will try to complete online contacts.
19
+ If there are no online contact matches for what you typed, offline contacts will also be
20
+ considered.
21
+
22
+ You can tab-complete specific resources of a contact by typing the contact name
23
+ followed by an @ character, e.g. somecontact@<tab> will complete all the available
24
+ resources for the contact and a message can be sent to that specific resource.
25
+
26
+ Config/logs folder is at #{Config.basedir}
27
+
28
+ Put your custom commands in #{Config.dirs[:commands]}
29
+ Check out the files in <install-path>/lib/lijab/commands/ for some examples.
30
+
31
+ Put your custom hooks in #{Config.dirs[:hooks]}
32
+
33
+ Send mails to quuxbaz@gmail.com to complain about the lack of documentation :-)
34
+
35
+ }.gsub!(/^ */, '')
36
+ Out::put(s)
37
+ else
38
+ if args == "commands"
39
+ Out::put
40
+ Commands::registered.each do |name, cmd|
41
+ Out::put(%{#{cmd.usage || "/#{name}"}}.magenta)
42
+ Out::put("#{cmd.description}\n\n")
43
+ end
44
+ else
45
+ cmd = Commands::get(args)
46
+ if cmd
47
+ s = "usage: #{cmd.usage}\n\n" if cmd.usage
48
+ s = "#{s}#{cmd.description}"
49
+ Out::put(s)
50
+ else
51
+ raise CommandError, %(No such command "#{args}")
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def completer(line)
58
+ help_cmd, rest = line.split(" ", 2)
59
+ rest ||= ""
60
+
61
+ m = "commands" =~ /^#{Regexp.escape(rest)}/ ? ["commands"] : []
62
+
63
+ rest = "/#{rest}"
64
+
65
+ m += Commands::completer(rest).map { |c| c[1..-1] } if rest.split(" ", 2).length == 1
66
+ end
67
+ end
68
+
69
+ Command.define :history do
70
+ usage "/history [<contact>] [<limit>]"
71
+ description "Show the message history with a <contact>, or all the contacts."
72
+
73
+ def run(args)
74
+ contact, limit = args.split(" ", 2).strip
75
+ limit ||= 10
76
+
77
+ if contact
78
+ raise CommandError, %(No contact named "#{contact}) unless Main.contacts.key?(contact)
79
+ m = Main.contacts[contact].history.last(limit.to_i)
80
+ else
81
+ m = HistoryHandler::last(limit.to_i)
82
+ end
83
+ Out::history(*m)
84
+ end
85
+
86
+ class << self
87
+ include ContactCompleterMixin
88
+ end
89
+ end
90
+
91
+ Command.define :multiline do
92
+ usage "/multiline <contact> [<first_line>]"
93
+ description "Enter multiline mode, meaning, send a multiline message to a contact.\n" \
94
+ "Ctrl-d in an empty line exits multiline mode and sends the message."
95
+
96
+ def run(args)
97
+ contact, first_line = args.split(" ", 2).strip
98
+ first_line = "#{contact}: #{first_line}"
99
+ InputHandler::multiline(true, first_line)
100
+ end
101
+
102
+ class << self
103
+ include ContactCompleterMixin
104
+ end
105
+ end
106
+
107
+ Command.define :quit do
108
+ usage "/quit"
109
+ description "Quit lijab"
110
+
111
+ def run(args)
112
+ Main.quit
113
+ end
114
+ end
115
+
116
+ # TODO: make a generic option changer?
117
+ Command.define :show_status_changes do
118
+ usage "/show_status_changes yes|no"
119
+ description "Enable/disable printing the contacts' status changes. Can get quite spammish."
120
+
121
+ def run(args)
122
+ if !args || args.empty?
123
+ Out::put(Config.opts[:show_status_changes] ? "yes" : "no")
124
+ else
125
+ Config.opts[:show_status_changes] = args.strip == "yes"
126
+ end
127
+ end
128
+ end
129
+
130
+ end
131
+ end
132
+
@@ -0,0 +1,63 @@
1
+
2
+ module Lijab
3
+ module Commands
4
+
5
+ Command.define :priority do
6
+ usage "/priority [<priority>]"
7
+ description "Change the jabber priority. Number must be between -127 and 127.\n" \
8
+ "Show current priority if no argument is given."
9
+
10
+ def run(args)
11
+ if args.strip.empty?
12
+ Out::put("Current priority is #{Main.presence.priority}")
13
+ return
14
+ end
15
+
16
+ begin
17
+ p = Integer(args)
18
+ rescue ArgumentError
19
+ raise Commands::CommandError, %{"#{args}" is not a valid integer}
20
+ end
21
+
22
+ raise Commands::CommandError, "priority must be between -127 and 127" unless (-127..127).include?(p)
23
+
24
+ Main.set_priority(p)
25
+ end
26
+ end
27
+
28
+ Command.define :status do
29
+ usage "/status [available|away|chat|xa|dnd|invisible] [<message>]"
30
+ description "Set your status.\n" \
31
+ "If no status is given, keep the current and set the status message.\n" \
32
+ "If no message is given, keep the current status and clear the message.\n" \
33
+ "If no arguments are given, print the current status."
34
+
35
+ STATUSES = ["available", "away", "chat", "xa", "dnd", "invisible"]
36
+
37
+ def run(args)
38
+ status, message = args.split(" ", 2).strip
39
+
40
+ unless status
41
+ p = Main.presence
42
+ Out::put("#{Config.jid} (#{p.priority || 0}) #{p.pretty(true)}")
43
+ return
44
+ end
45
+
46
+ unless STATUSES.include?(status)
47
+ message = "#{status} #{message}".strip
48
+ status = nil
49
+ end
50
+
51
+ Main.set_status(status && status.to_sym, message)
52
+ end
53
+
54
+ def completer(line)
55
+ status = line.split[1] || ""
56
+ if STATUSES.grep(status).empty?
57
+ STATUSES.grep(/^#{Regexp.escape(status)}/)
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,78 @@
1
+
2
+ module Lijab
3
+ module Commands
4
+ Command.define :add do
5
+ usage "/add <user@server>"
6
+ description "Add a user to your roster."
7
+
8
+ def run(args)
9
+ Main.contacts.add(args)
10
+ Out::put("subscription request sent to #{args}")
11
+ end
12
+ end
13
+
14
+ Command.define :remove do
15
+ usage "/remove <user@server>"
16
+ description "Remove a user from your roster."
17
+
18
+ def run(args)
19
+ unless Main.contacts.remove(args)
20
+ raise CommandError, "no contact found for #{args}"
21
+ end
22
+ end
23
+
24
+ class << self
25
+ include ContactCompleterMixin
26
+ end
27
+ end
28
+
29
+ # TODO: <user@server | all>
30
+ Command.define :requests do
31
+ usage "/requests [accept|accept_and_add|decline <user@server>]"
32
+ description "Accept/decline a user's request to see your status.\n" \
33
+ "Print pending requests if no argument given." \
34
+
35
+ ACTIONS = ["accept", "accept_and_add", "decline"]
36
+
37
+ def run(args)
38
+ action, addr = args.split(nil, 2).strip
39
+
40
+ if action
41
+ unless ACTIONS.include?(action)
42
+ raise CommandError, "action must be accept, accept_and_add or decline"
43
+ end
44
+ raise CommandError, "need the user's address" unless addr and !addr.empty?
45
+
46
+ if ["accept", "accept_and_add"].include?(action)
47
+ success = Main.contacts.process_request(addr, :accept)
48
+ Main.contacts.add(addr) if success && action == "accept_and_add"
49
+ else
50
+ success = Main.contacts.process_request(addr, :decline)
51
+ end
52
+
53
+ raise CommandError, "no pending request from #{addr}" unless success
54
+ else
55
+ if Main.contacts.has_subscription_requests?
56
+ Out::put("pending requests from:")
57
+ Out::put(Main.contacts.subscription_requests.join("\n"))
58
+ else
59
+ Out::put("no pending requests")
60
+ end
61
+ end
62
+ end
63
+
64
+ def completer(line)
65
+ _, action, addr = line.split(nil, 3)
66
+
67
+ if !addr
68
+ ACTIONS.grep(/^#{Regexp.escape(action)}/)
69
+ elsif addr && ACTIONS.include?(action)
70
+ Main.contacts.subscription_requests.grep(/^#{Regexp.escape(addr)}/).map do |c|
71
+ "#{action} #{c}"
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ end
78
+ end