1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

# Copyright 2013 The Distro Tracker Developers 

# See the COPYRIGHT file at the top-level directory of this distribution and 

# at https://deb.li/DTAuthors 

# 

# This file is part of Distro Tracker. It is subject to the license terms 

# in the LICENSE file found in the top-level directory of this 

# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

# including this file, may be copied, modified, propagated, or distributed 

# except according to the terms contained in the LICENSE file. 

""" 

The module defining common functionality and base classes for all email control 

commands. 

""" 

 

import re 

 

from django.conf import settings 

from django.core.exceptions import ValidationError 

from django.core.validators import EmailValidator 

 

DISTRO_TRACKER_CONTROL_EMAIL = settings.DISTRO_TRACKER_CONTROL_EMAIL 

 

 

class MetaCommand(type): 

""" 

Meta class for Distro Tracker Commands. 

 

Transforms the :py:attr:`Command.REGEX_LIST` given in a Command sublclass 

to include all aliases of the command. When implementing a 

:py:class:`Command` subclass, it is not necessary to include a separate 

regex for each command alias or a long one listing every option. 

""" 

def __init__(cls, name, bases, dct): # noqa 

if not getattr(cls, 'META', None): 

return 

joined_aliases = '|'.join( 

alias 

for alias in [cls.META['name']] + cls.META.get('aliases', []) 

) 

cls.REGEX_LIST = tuple( 

'^(?:' + joined_aliases + ')' + regex 

for regex in cls.REGEX_LIST 

) 

 

 

class Command(metaclass=MetaCommand): 

""" 

Base class for commands. Instances of this class can be used for no-op 

commands. 

""" 

__metaclass__ = MetaCommand 

 

META = {} 

""" 

Meta information about the command, given as key/value pairs. Expected 

keys are: 

- ``description`` - Description of the command which will be shown in the 

help output 

- ``name`` - Name of the command. Makes it possible to match command lines 

in control messages to command classes since each command line starts 

with the name of the command. 

- ``aliases`` - List of alternative names for the command 

- ``position`` - Preferred position in the help output 

""" 

REGEX_LIST = () 

""" 

A list of regular expressions which, when matched to a string, identify 

a command. Additionally, any named group in the regular expression should 

exactly match the name of the parameter in the constructor of the command. 

If unnamed groups are used, their order must be the same as the order of 

parameters in the constructor of the command. 

This is very similar to how Django handles linking views and URLs. 

 

It is only necessary to list the part of the command's syntax to 

capture the parameters, while the name and all aliases given in the META 

dict are automatically assumed when matching a string to the command. 

""" 

 

def __init__(self, *args): 

self._sent_mails = [] 

self.out = [] 

 

def __call__(self): 

""" 

The base class delegates execution to the appropriate :py:meth:`handle` 

method and handles the reply. 

""" 

self.handle() 

return self.render_reply() 

 

def handle(self): 

""" 

Performs the necessary steps to execute the command. 

""" 

pass 

 

def is_valid(self): 

return True 

 

def get_command_text(self, *args): 

""" 

Returns a string representation of the command. 

""" 

return ' '.join((self.META.get('name', '#'), ) + args) 

 

@classmethod 

def match_line(cls, line): 

""" 

Class method to check whether the given line matches the command. 

 

:param line: The line to check whether it matches the command. 

""" 

for pattern in cls.REGEX_LIST: 

match = re.match(pattern, line, re.IGNORECASE) 

if match: 

return match 

 

def render_reply(self): 

""" 

Returns a string representing the command's reply. 

""" 

return '\n'.join(self.out) 

 

def reply(self, message, *args): 

""" 

Adds a message to the command's reply. 

 

:param message: Message to include in the reply 

:type message: string 

""" 

self.out.append(message % args) 

 

def warning(self, message, *args): 

""" 

Adds a warning to the command's reply. 

 

:param message: Message to include in the reply 

:type message: string 

""" 

self.out.append('Warning: ' + message % args) 

 

def error(self, message, *args): 

""" 

Adds an error message to the command's reply. 

 

:param message: Message to include in the reply 

:type message: string 

""" 

self.out.append("Error: " + message % args) 

 

def list_reply(self, items, bullet='*'): 

""" 

Includes a list of items in the reply. Each item is converted to a 

string before being output. 

 

:param items: An iterable of items to be included in the form of a list 

in the reply. 

:param bullet: The character to be used as the "bullet" of the list. 

""" 

for item in items: 

self.reply(bullet + ' ' + str(item)) 

 

@staticmethod 

def validate_email(email): 

validate = EmailValidator() 

try: 

validate(email) 

return True 

except ValidationError: 

return False