Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
gajim
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Weblate
gajim
Commits
1a327414
Commit
1a327414
authored
15 years ago
by
red-agent
Browse files
Options
Downloads
Patches
Plain Diff
Moderate refactoring and parser/adapter enhancements
parent
4dae0bde
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
src/commands/framework.py
+160
-38
160 additions, 38 deletions
src/commands/framework.py
src/commands/implementation.py
+24
-18
24 additions, 18 deletions
src/commands/implementation.py
src/commands/middleware.py
+0
-9
0 additions, 9 deletions
src/commands/middleware.py
with
184 additions
and
65 deletions
src/commands/framework.py
+
160
−
38
View file @
1a327414
...
...
@@ -23,7 +23,7 @@ from types import FunctionType, UnicodeType, TupleType, ListType, BooleanType
from
inspect
import
getargspec
from
operator
import
itemgetter
class
Command
InternalError
(
Exception
):
class
InternalError
(
Exception
):
pass
class
CommandError
(
Exception
):
...
...
@@ -46,13 +46,14 @@ class Command(object):
ARG_USAGE_PATTERN
=
'
Usage: %s %s
'
def
__init__
(
self
,
handler
,
usage
,
source
,
raw
,
extra
,
empty
,
expand_short
):
def
__init__
(
self
,
handler
,
usage
,
source
,
raw
,
extra
,
overlap
,
empty
,
expand_short
):
self
.
handler
=
handler
self
.
usage
=
usage
self
.
source
=
source
self
.
raw
=
raw
self
.
extra
=
extra
self
.
overlap
=
overlap
self
.
empty
=
empty
self
.
expand_short
=
expand_short
...
...
@@ -120,7 +121,7 @@ class Command(object):
# Removing self from arguments specification. Command handler should
# normally be an instance method.
if
spec_args
.
pop
(
0
)
!=
'
self
'
:
raise
Command
InternalError
(
"
First argument must be self
"
)
raise
InternalError
(
"
First argument must be self
"
)
return
spec_args
,
spec_kwargs
,
var_args
,
var_kwargs
...
...
@@ -142,9 +143,14 @@ class Command(object):
for
key
,
value
in
spec_kwargs
:
letter
=
key
[
0
]
key
=
key
.
replace
(
'
_
'
,
'
-
'
)
value
=
(
'
=%s
'
%
value
)
if
not
isinstance
(
value
,
BooleanType
)
else
str
()
if
isinstance
(
value
,
BooleanType
):
value
=
str
()
elif
isinstance
(
value
,
(
TupleType
,
ListType
)):
value
=
'
={%s}
'
%
'
,
'
.
join
(
value
)
else
:
value
=
'
=%s
'
%
value
if
letter
not
in
letters
:
kwargs
.
append
(
'
-(-%s)%s%s
'
%
(
letter
,
key
[
1
:],
value
))
...
...
@@ -155,9 +161,12 @@ class Command(object):
usage
=
str
()
args
=
str
()
if
len
(
spec_args
)
==
1
and
self
.
raw
:
args
+=
(
'
(|%s|)
'
if
self
.
empty
else
'
|%s|
'
)
%
spec_args
[
0
]
elif
spec_args
or
var_args
or
sp_extra
:
if
self
.
raw
:
spec_len
=
len
(
spec_args
)
-
1
if
spec_len
:
args
+=
(
'
<%s>
'
%
'
,
'
.
join
(
spec_args
[:
spec_len
]))
+
'
'
args
+=
(
'
(|%s|)
'
if
self
.
empty
else
'
|%s|
'
)
%
spec_args
[
-
1
]
else
:
if
spec_args
:
args
+=
'
<%s>
'
%
'
,
'
.
join
(
spec_args
)
if
var_args
or
sp_extra
:
...
...
@@ -185,8 +194,10 @@ class Dispatcher(type):
hosted
=
{}
def
__init__
(
cls
,
name
,
bases
,
dct
):
Dispatcher
.
check_if_dispatchable
(
bases
,
dct
)
Dispatcher
.
check_if_hostable
(
bases
,
dct
)
dispatchable
=
Dispatcher
.
check_if_dispatchable
(
bases
,
dct
)
hostable
=
Dispatcher
.
check_if_hostable
(
bases
,
dct
)
cls
.
check_if_conformed
(
dispatchable
,
hostable
)
if
Dispatcher
.
is_suitable
(
cls
,
dct
):
Dispatcher
.
register_processor
(
cls
)
...
...
@@ -207,20 +218,27 @@ class Dispatcher(type):
def
check_if_dispatchable
(
cls
,
bases
,
dct
):
dispatcher
=
dct
.
get
(
'
DISPATCHED_BY
'
)
if
not
dispatcher
:
return
return
False
if
dispatcher
not
in
bases
:
raise
CommandInternalError
(
"
Should be dispatched by the same processor it inherits from
"
)
raise
InternalError
(
"
Should be dispatched by the same processor it inherits from
"
)
return
True
@classmethod
def
check_if_hostable
(
cls
,
bases
,
dct
):
hosters
=
dct
.
get
(
'
HOSTED_BY
'
)
if
not
hosters
:
return
return
False
if
not
isinstance
(
hosters
,
(
TupleType
,
ListType
)):
hosters
=
(
hosters
,)
for
hoster
in
hosters
:
if
hoster
not
in
bases
:
raise
CommandInternalError
(
"
Should be hosted by the same processors it inherits from
"
)
raise
InternalError
(
"
Should be hosted by the same processors it inherits from
"
)
return
True
@classmethod
def
check_if_conformed
(
cls
,
dispatchable
,
hostable
):
if
dispatchable
and
hostable
:
raise
InternalError
(
"
Processor can not be dispatchable and hostable at the same time
"
)
@classmethod
def
register_processor
(
cls
,
proc
):
...
...
@@ -261,7 +279,7 @@ class Dispatcher(type):
if
name
not
in
cls
.
table
[
proc
]:
cls
.
table
[
proc
][
name
]
=
command
else
:
raise
Command
InternalError
(
"
Command with name %s already exists
"
%
name
)
raise
InternalError
(
"
Command with name %s already exists
"
%
name
)
@classmethod
def
register_adhocs
(
cls
,
proc
):
hosters
=
proc
.
HOSTED_BY
...
...
@@ -421,6 +439,13 @@ class CommandProcessor(object):
position
=
match
.
span
()
args
.
append
((
body
,
position
))
# In rare occasions quoted options are being captured, while they should
# not be. This fixes the problem by finding options which intersect with
# arguments and removing them.
for
key
,
value
,
position
in
opts
[:]:
if
intersects_args
(
position
):
opts
.
remove
((
key
,
value
,
position
))
return
args
,
opts
@classmethod
...
...
@@ -441,20 +466,85 @@ class CommandProcessor(object):
cases) then this option will be treated as a switch, that is an option
which does not take an argument. Argument preceded by a switch will be
treated just like a normal positional argument.
If keyword argument
'
s initial value is a sequence (tuple or a string)
then possible values of the option will be restricted to one of the
values given by the sequence.
"""
spec_args
,
spec_kwargs
,
var_args
,
var_kwargs
=
command
.
extract_arg_spec
()
spec_kwargs
=
dict
(
spec_kwargs
)
norm_kwargs
=
dict
(
spec_kwargs
)
# Quite complex piece of neck-breaking logic to extract raw arguments if
# there is more, then one positional argument specified by the command.
# In case if it's just one argument which is the collector this is
# fairly easy. But when it's more then one argument - the neck-breaking
# logic of how to retrieve residual arguments as a raw, all in one piece
# string, kicks on.
if
command
.
raw
:
if
len
(
spec_args
)
==
1
and
not
spec_kwargs
and
not
var_args
and
not
var_kwargs
:
if
arguments
or
command
.
empty
:
return
(
arguments
,),
{}
raise
CommandError
(
"
Can not be used without arguments
"
,
command
)
raise
CommandInternalError
(
"
Raw command must define no more then one argument
"
)
if
spec_kwargs
or
var_args
or
var_kwargs
:
raise
InternalError
(
"
Raw commands should define only positional arguments
"
)
if
arguments
:
spec_fix
=
1
if
command
.
source
else
0
spec_len
=
len
(
spec_args
)
-
spec_fix
arguments_end
=
len
(
arguments
)
-
1
# If there are any optional arguments given they should be
# either an unquoted postional argument or part of the raw
# argument. So we find all optional arguments that can possibly
# be unquoted argument and append them as is to the args.
for
key
,
value
,
(
start
,
end
)
in
opts
[:
spec_len
]:
if
value
:
end
-=
len
(
value
)
+
1
args
.
append
((
arguments
[
start
:
end
],
(
start
,
end
)))
args
.
append
((
value
,
(
end
,
end
+
len
(
value
)
+
1
)))
else
:
args
.
append
((
arguments
[
start
:
end
],
(
start
,
end
)))
# We need in-place sort here because after manipulations with
# options order of arguments might be wrong and we just can't
# have more complex logic to not let that happen.
args
.
sort
(
key
=
itemgetter
(
1
))
if
spec_len
>
1
:
stopper
,
(
start
,
end
)
=
args
[
spec_len
-
2
]
raw
=
arguments
[
end
:]
raw
=
raw
.
strip
()
or
None
if
not
raw
and
not
command
.
empty
:
raise
CommandError
(
"
Missing arguments
"
,
command
)
# Discard residual arguments and all of the options as raw
# command does not support options and if an option is given
# it is rather a part of a raw argument.
args
=
args
[:
spec_len
-
1
]
opts
=
[]
args
.
append
((
raw
,
(
end
,
arguments_end
)))
elif
spec_len
==
1
:
args
=
[(
arguments
,
(
0
,
arguments_end
))]
else
:
raise
InternalError
(
"
Raw command must define a collector
"
)
else
:
if
command
.
empty
:
args
.
append
((
None
,
(
0
,
0
)))
else
:
raise
CommandError
(
"
Missing arguments
"
,
command
)
# The first stage of transforming options we have got to a format that
# can be used to associate them with declared keyword arguments.
# Substituting dashes (-) in their names with underscores (_).
for
index
,
(
key
,
value
,
position
)
in
enumerate
(
opts
):
if
'
-
'
in
key
:
opts
[
index
]
=
(
key
.
replace
(
'
-
'
,
'
_
'
),
value
,
position
)
# The second stage of transforming options to an associatable state.
# Expanding short, one-letter options to a verbose ones, if
# corresponding optin has been given.
if
command
.
expand_short
:
expanded
=
[]
for
spec_key
,
spec_value
in
spec
_kwargs
.
iteritems
():
for
spec_key
,
spec_value
in
norm
_kwargs
.
iteritems
():
letter
=
spec_key
[
0
]
if
len
(
spec_key
)
>
1
else
None
if
letter
and
letter
not
in
expanded
:
for
index
,
(
key
,
value
,
position
)
in
enumerate
(
opts
):
...
...
@@ -463,8 +553,10 @@ class CommandProcessor(object):
opts
[
index
]
=
(
spec_key
,
value
,
position
)
break
# Detect switches and set their values accordingly. If any of them
# carries a value - append it to args.
for
index
,
(
key
,
value
,
position
)
in
enumerate
(
opts
):
if
isinstance
(
spec
_kwargs
.
get
(
key
),
BooleanType
):
if
isinstance
(
norm
_kwargs
.
get
(
key
),
BooleanType
):
opts
[
index
]
=
(
key
,
True
,
position
)
if
value
:
args
.
append
((
value
,
position
))
...
...
@@ -479,18 +571,40 @@ class CommandProcessor(object):
args
=
map
(
lambda
(
arg
,
position
):
arg
,
args
)
opts
=
map
(
lambda
(
key
,
value
,
position
):
(
key
,
value
),
opts
)
# If command has extra option enabled - collect all extra arguments and
# pass them to a last positional argument command defines as a list.
if
command
.
extra
:
if
not
var_args
:
positional_len
=
len
(
spec_args
)
-
(
1
if
not
command
.
source
else
2
)
extra
=
args
[
positional_len
:]
args
=
args
[:
positional_len
]
spec_fix
=
1
if
not
command
.
source
else
2
spec_len
=
len
(
spec_args
)
-
spec_fix
extra
=
args
[
spec_len
:]
args
=
args
[:
spec_len
]
args
.
append
(
extra
)
else
:
raise
CommandInternalError
(
"
Can not have both, extra and *args
"
)
for
index
,
(
key
,
value
)
in
enumerate
(
opts
):
if
'
-
'
in
key
:
opts
[
index
]
=
(
key
.
replace
(
'
-
'
,
'
_
'
),
value
)
raise
InternalError
(
"
Can not have both, extra and *args
"
)
# Detect if positional arguments overlap keyword arguments. If so and
# this is allowed by command options - then map them directly to their
# options, so they can get propert further processings.
spec_fix
=
1
if
command
.
source
else
0
spec_len
=
len
(
spec_args
)
-
spec_fix
if
len
(
args
)
>
spec_len
:
if
command
.
overlap
:
overlapped
=
args
[
spec_len
:]
args
=
args
[:
spec_len
]
for
arg
,
(
spec_key
,
spec_value
)
in
zip
(
overlapped
,
spec_kwargs
):
opts
.
append
((
spec_key
,
arg
))
else
:
raise
CommandError
(
"
Excessive arguments
"
,
command
)
# Detect every contraint sequences and ensure that if corresponding
# options are given - they contain proper values, within constraint
# range.
for
key
,
value
in
opts
:
initial
=
norm_kwargs
.
get
(
key
)
if
isinstance
(
initial
,
(
TupleType
,
ListType
))
and
value
not
in
initial
:
if
value
:
raise
CommandError
(
"
Wrong argument
"
,
command
)
# We need to encode every keyword argument to a simple string, not the
# unicode one, because ** expansion does not support it.
...
...
@@ -498,9 +612,13 @@ class CommandProcessor(object):
if
isinstance
(
key
,
UnicodeType
):
opts
[
index
]
=
(
key
.
encode
(
cls
.
ARG_ENCODING
),
value
)
# Inject the source arguments as a string as a first argument, if
# command has enabled the corresponding option.
if
command
.
source
:
args
.
insert
(
0
,
arguments
)
# Return *args and **kwargs in the form suitable for passing to a
# command handlers and being expanded.
return
tuple
(
args
),
dict
(
opts
)
def
process_as_command
(
self
,
text
):
...
...
@@ -515,11 +633,7 @@ class CommandProcessor(object):
text
=
text
.
strip
()
parts
=
text
.
split
(
'
'
,
1
)
if
len
(
parts
)
>
1
:
name
,
arguments
=
parts
else
:
name
,
arguments
=
parts
[
0
],
None
name
,
arguments
=
parts
if
len
(
parts
)
>
1
else
(
parts
[
0
],
None
)
flag
=
self
.
looks_like_command
(
text
,
name
,
arguments
)
if
flag
is
not
None
:
...
...
@@ -593,6 +707,10 @@ def command(*names, **kwargs):
cases only because of some Python limitations on this - arguments can
'
t be
mapped correctly when there are keyword arguments present.
If overlap=True is given - then if extra=False and there is extra arguments
given to the command - they will be mapped as if they were values for the
keyword arguments, in the order they are defined.
If expand_short=True is given - then if command receives one-letter
options (like -v or -f) they will be expanded to a verbose ones (like
--verbose or --file) if the latter are defined as a command optional
...
...
@@ -607,11 +725,15 @@ def command(*names, **kwargs):
source
=
kwargs
.
get
(
'
source
'
,
False
)
raw
=
kwargs
.
get
(
'
raw
'
,
False
)
extra
=
kwargs
.
get
(
'
extra
'
,
False
)
overlap
=
kwargs
.
get
(
'
overlap
'
,
False
)
empty
=
kwargs
.
get
(
'
empty
'
,
False
)
expand_short
=
kwargs
.
get
(
'
expand_short
'
,
True
)
if
extra
and
overlap
:
raise
InternalError
(
"
Extra and overlap options can not be used together
"
)
def
decorator
(
handler
):
command
=
Command
(
handler
,
usage
,
source
,
raw
,
extra
,
empty
,
expand_short
)
command
=
Command
(
handler
,
usage
,
source
,
raw
,
extra
,
overlap
,
empty
,
expand_short
)
# Extract and inject native name while making sure it is going to be the
# first one in the list.
...
...
This diff is collapsed.
Click to expand it.
src/commands/implementation.py
+
24
−
18
View file @
1a327414
...
...
@@ -46,7 +46,7 @@ class CommonCommands(ChatMiddleware):
"""
self
.
chat_buttons_set_visible
(
not
self
.
hide_chat_buttons
)
@command
@command
(
overlap
=
True
)
def
help
(
self
,
command
=
None
,
all
=
False
):
"""
Show help on a given command or a list of available commands if -(-a)ll is
...
...
@@ -85,6 +85,15 @@ class CommonCommands(ChatMiddleware):
"""
self
.
send
(
"
/me %s
"
%
action
)
@command
(
raw
=
True
,
empty
=
True
)
def
test
(
self
,
one
,
two
,
three
):
self
.
echo
(
one
)
self
.
echo
(
two
)
self
.
echo
(
three
)
from
pprint
import
pformat
return
"
Locals:
\n
%s
"
%
pformat
(
locals
())
class
ChatCommands
(
CommonCommands
):
"""
Here defined commands will be unique to a chat. Use it as a hoster to provide
...
...
@@ -147,13 +156,12 @@ class GroupChatCommands(CommonCommands):
else
:
raise
CommandError
(
_
(
"
Nickname not found
"
))
@command
(
'
msg
'
)
def
message
(
self
,
nick
,
*
a_message
):
@command
(
'
msg
'
,
raw
=
True
)
def
message
(
self
,
nick
,
a_message
):
"""
Open a private chat window with a specified occupant and send him a
message
"""
a_message
=
self
.
collect
(
a_message
,
False
)
nicks
=
gajim
.
contacts
.
get_nick_list
(
self
.
account
,
self
.
room_jid
)
if
nick
in
nicks
:
self
.
on_send_pm
(
nick
=
nick
,
msg
=
a_message
)
...
...
@@ -170,21 +178,21 @@ class GroupChatCommands(CommonCommands):
else
:
return
self
.
subject
@command
def
invite
(
self
,
jid
,
*
reason
):
@command
(
raw
=
True
,
empty
=
True
)
def
invite
(
self
,
jid
,
reason
):
"""
Invite a user to a room for a reason
"""
reason
=
self
.
collect
(
reason
)
self
.
connection
.
send_invite
(
self
.
room_jid
,
jid
,
reason
)
return
_
(
"
Invited %s to %s
"
)
%
(
jid
,
self
.
room_jid
)
@command
def
join
(
self
,
jid
,
*
nick
):
@command
(
raw
=
True
,
empty
=
True
)
def
join
(
self
,
jid
,
nick
):
"""
Join a group chat given by a jid, optionally using given nickname
"""
nick
=
self
.
collect
(
nick
)
or
self
.
nick
if
not
nick
:
nick
=
self
.
nick
if
'
@
'
not
in
jid
:
jid
=
jid
+
'
@
'
+
gajim
.
get_server_from_jid
(
self
.
room_jid
)
...
...
@@ -204,28 +212,26 @@ class GroupChatCommands(CommonCommands):
"""
self
.
parent_win
.
remove_tab
(
self
,
self
.
parent_win
.
CLOSE_COMMAND
,
reason
)
@command
def
ban
(
self
,
who
,
*
reason
):
@command
(
raw
=
True
,
empty
=
True
)
def
ban
(
self
,
who
,
reason
):
"""
Ban user by a nick or a jid from a groupchat
If given nickname is not found it will be treated as a jid.
"""
reason
=
self
.
collect
(
reason
,
none
=
False
)
if
who
in
gajim
.
contacts
.
get_nick_list
(
self
.
account
,
self
.
room_jid
):
contact
=
gajim
.
contacts
.
get_gc_contact
(
self
.
account
,
self
.
room_jid
,
who
)
who
=
contact
.
jid
self
.
connection
.
gc_set_affiliation
(
self
.
room_jid
,
who
,
'
outcast
'
,
reason
)
self
.
connection
.
gc_set_affiliation
(
self
.
room_jid
,
who
,
'
outcast
'
,
reason
or
str
()
)
@command
def
kick
(
self
,
who
,
*
reason
):
@command
(
raw
=
True
,
empty
=
True
)
def
kick
(
self
,
who
,
reason
):
"""
Kick user by a nick from a groupchat
"""
reason
=
self
.
collect
(
reason
,
none
=
False
)
if
not
who
in
gajim
.
contacts
.
get_nick_list
(
self
.
account
,
self
.
room_jid
):
raise
CommandError
(
_
(
"
Nickname not found
"
))
self
.
connection
.
gc_set_role
(
self
.
room_jid
,
who
,
'
none
'
,
reason
)
self
.
connection
.
gc_set_role
(
self
.
room_jid
,
who
,
'
none
'
,
reason
or
str
()
)
@command
def
names
(
self
,
verbose
=
False
):
...
...
This diff is collapsed.
Click to expand it.
src/commands/middleware.py
+
0
−
9
View file @
1a327414
...
...
@@ -97,15 +97,6 @@ class ChatMiddleware(CommandProcessor):
"""
self
.
save_sent_message
(
text
)
def
collect
(
self
,
arguments
,
empty
=
True
,
separator
=
'
'
,
none
=
True
):
"""
Might come in handy in case if you want to map some arguments and
collect the rest of them into a string.
"""
if
not
empty
and
not
arguments
:
raise
CommandError
(
_
(
"
Missing argument
"
))
return
None
if
not
arguments
and
none
else
separator
.
join
(
arguments
)
@property
def
connection
(
self
):
"""
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment