6
6
import re
7
7
from shutil import rmtree
8
8
9
- import regex
10
9
from django .conf import settings
11
10
from django .core .files .storage import get_storage_class
12
11
from django .db import models
58
57
get_bitbucket_username_repo ,
59
58
get_github_username_repo ,
60
59
get_gitlab_username_repo ,
60
+ match_regex ,
61
61
)
62
62
from readthedocs .builds .version_slug import VersionSlugField
63
63
from readthedocs .config import LATEST_CONFIGURATION_VERSION
@@ -952,6 +952,7 @@ class VersionAutomationRule(PolymorphicModel, TimeStampedModel):
952
952
MAKE_VERSION_PUBLIC_ACTION = 'make-version-public'
953
953
MAKE_VERSION_PRIVATE_ACTION = 'make-version-private'
954
954
SET_DEFAULT_VERSION_ACTION = 'set-default-version'
955
+ BUILD_EXTERNAL_VERSION = 'build-external-version'
955
956
956
957
ACTIONS = (
957
958
(ACTIVATE_VERSION_ACTION , _ ('Activate version' )),
@@ -960,10 +961,24 @@ class VersionAutomationRule(PolymorphicModel, TimeStampedModel):
960
961
(MAKE_VERSION_PRIVATE_ACTION , _ ('Make version private' )),
961
962
(SET_DEFAULT_VERSION_ACTION , _ ('Set version as default' )),
962
963
(DELETE_VERSION_ACTION , _ ('Delete version (on branch/tag deletion)' )),
964
+ (BUILD_EXTERNAL_VERSION , _ ('Build version' )),
963
965
)
964
966
965
- allowed_actions_on_create = {}
966
- allowed_actions_on_delete = {}
967
+ allowed_actions_on_create = {
968
+ ACTIVATE_VERSION_ACTION : actions .activate_version ,
969
+ HIDE_VERSION_ACTION : actions .hide_version ,
970
+ MAKE_VERSION_PUBLIC_ACTION : actions .set_public_privacy_level ,
971
+ MAKE_VERSION_PRIVATE_ACTION : actions .set_private_privacy_level ,
972
+ SET_DEFAULT_VERSION_ACTION : actions .set_default_version ,
973
+ }
974
+
975
+ allowed_actions_on_delete = {
976
+ DELETE_VERSION_ACTION : actions .delete_version ,
977
+ }
978
+
979
+ allowed_actions_on_external_versions = {
980
+ BUILD_EXTERNAL_VERSION : actions .build_external_version ,
981
+ }
967
982
968
983
project = models .ForeignKey (
969
984
Project ,
@@ -1035,16 +1050,18 @@ def run(self, version, *args, **kwargs):
1035
1050
Run an action if `version` matches the rule.
1036
1051
1037
1052
:type version: readthedocs.builds.models.Version
1038
- :returns: True if the action was performed
1053
+ :returns: A tuple of (boolean, ANY), where the first element
1054
+ indicates if the action was performed, and the second is the result
1055
+ returned by the action.
1039
1056
"""
1040
1057
if version .type == self .version_type :
1041
- match , result = self .match (version , self .get_match_arg ())
1058
+ match , result = self .match (version , self .get_match_arg (), * args , ** kwargs )
1042
1059
if match :
1043
- self .apply_action (version , result )
1044
- return True
1045
- return False
1060
+ action_result = self .apply_action (version , result )
1061
+ return True , action_result
1062
+ return False , None
1046
1063
1047
- def match (self , version , match_arg ):
1064
+ def match (self , version , match_arg , * args , ** kwargs ):
1048
1065
"""
1049
1066
Returns True and the match result if the version matches the rule.
1050
1067
@@ -1067,10 +1084,11 @@ def apply_action(self, version, match_result):
1067
1084
action = (
1068
1085
self .allowed_actions_on_create .get (self .action )
1069
1086
or self .allowed_actions_on_delete .get (self .action )
1087
+ or self .allowed_actions_on_external_versions .get (self .action )
1070
1088
)
1071
1089
if action is None :
1072
1090
raise NotImplementedError
1073
- action (version , match_result , self .action_arg )
1091
+ return action (version , match_result , self .action_arg )
1074
1092
1075
1093
def move (self , steps ):
1076
1094
"""
@@ -1172,52 +1190,16 @@ def __str__(self):
1172
1190
1173
1191
class RegexAutomationRule (VersionAutomationRule ):
1174
1192
1175
- TIMEOUT = 1 # timeout in seconds
1176
-
1177
- allowed_actions_on_create = {
1178
- VersionAutomationRule .ACTIVATE_VERSION_ACTION : actions .activate_version ,
1179
- VersionAutomationRule .HIDE_VERSION_ACTION : actions .hide_version ,
1180
- VersionAutomationRule .MAKE_VERSION_PUBLIC_ACTION : actions .set_public_privacy_level ,
1181
- VersionAutomationRule .MAKE_VERSION_PRIVATE_ACTION : actions .set_private_privacy_level ,
1182
- VersionAutomationRule .SET_DEFAULT_VERSION_ACTION : actions .set_default_version ,
1183
- }
1184
-
1185
- allowed_actions_on_delete = {
1186
- VersionAutomationRule .DELETE_VERSION_ACTION : actions .delete_version ,
1187
- }
1188
-
1189
1193
class Meta :
1190
1194
proxy = True
1191
1195
1192
- def match (self , version , match_arg ):
1193
- """
1194
- Find a match using regex.search.
1195
-
1196
- .. note::
1197
-
1198
- We use the regex module with the timeout
1199
- arg to avoid ReDoS.
1200
-
1201
- We could use a finite state machine type of regex too,
1202
- but there isn't a stable library at the time of writting this code.
1203
- """
1204
- try :
1205
- match = regex .search (
1206
- match_arg ,
1207
- version .verbose_name ,
1208
- # Compatible with the re module
1209
- flags = regex .VERSION0 ,
1210
- timeout = self .TIMEOUT ,
1211
- )
1212
- return bool (match ), match
1213
- except TimeoutError :
1214
- log .warning (
1215
- 'Timeout while parsing regex. pattern=%s, input=%s' ,
1216
- match_arg , version .verbose_name ,
1217
- )
1218
- except Exception as e :
1219
- log .info ('Error parsing regex: %s' , e )
1220
- return False , None
1196
+ def match (self , version , match_arg , * args , ** kwargs ):
1197
+ version_name = version .verbose_name
1198
+ if version .is_external :
1199
+ version_data = kwargs ['version_data' ]
1200
+ version_name = version_data .source_branch
1201
+ result = match_regex (match_arg , version_name )
1202
+ return bool (result ), result
1221
1203
1222
1204
def get_edit_url (self ):
1223
1205
return reverse (
0 commit comments