palbo-lijab 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.
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