mirror of
https://github.com/zero-peak/ZeroOmega.git
synced 2025-01-22 15:08:12 -05:00
Add SwitchyOmega rule list format. Fix #71.
This commit is contained in:
parent
56457519ab
commit
ec5a35682c
@ -52,7 +52,51 @@ module.exports = exports =
|
||||
list.push({condition: cond, profileName: profile, source: source})
|
||||
# Exclusive rules have higher priority, so they come first.
|
||||
return exclusive_rules.concat normal_rules
|
||||
|
||||
'Switchy':
|
||||
omegaPrefix: '[SwitchyOmega Conditions'
|
||||
detect: (text) ->
|
||||
text = text.trim()
|
||||
if strStartsWith(text, exports['Switchy'].omegaPrefix)
|
||||
return true
|
||||
return
|
||||
|
||||
parse: (text, matchProfileName, defaultProfileName) ->
|
||||
text = text.trim()
|
||||
switchy = exports['Switchy']
|
||||
parser = 'parseOmega'
|
||||
if not strStartsWith(text, switchy.omegaPrefix)
|
||||
if text[0] == '#' or text.indexOf('\n#') >= 0
|
||||
parser = 'parseLegacy'
|
||||
return switchy[parser](text, matchProfileName, defaultProfileName)
|
||||
|
||||
# For the omega rule list format, please see the following wiki page:
|
||||
# https://github.com/FelisCatus/SwitchyOmega/wiki/SwitchyOmega-conditions-format
|
||||
compose: ({rules, defaultProfileName}, {withResult, useExclusive} = {}) ->
|
||||
eol = '\r\n'
|
||||
ruleList = '[SwitchyOmega Conditions]' + eol
|
||||
useExclusive ?= not withResult
|
||||
if withResult
|
||||
ruleList += '@with result' + eol + eol
|
||||
else
|
||||
ruleList += eol
|
||||
for rule in rules
|
||||
line = Conditions.str(rule.condition)
|
||||
if line[0] == '#' or line[0] == '+'
|
||||
# Escape leading # to avoid being detected as legacy format.
|
||||
# Reserve leading + for condition results.
|
||||
line = ': ' + line
|
||||
if useExclusive and rule.profileName == defaultProfileName
|
||||
line = '!' + line
|
||||
else if withResult
|
||||
# TODO(catus): What if rule.profileName contains ' +' or new lines?
|
||||
line += ' +' + rule.profileName
|
||||
ruleList += line + eol
|
||||
if withResult
|
||||
# TODO(catus): Also special chars and sequences in defaultProfileName.
|
||||
ruleList += '* +' + defaultProfileName + eol
|
||||
return ruleList
|
||||
|
||||
conditionFromLegacyWildcard: (pattern) ->
|
||||
if pattern[0] == '@'
|
||||
pattern = pattern.substring(1)
|
||||
@ -70,8 +114,7 @@ module.exports = exports =
|
||||
conditionType: 'UrlWildcardCondition'
|
||||
pattern: pattern
|
||||
|
||||
parse: (text, matchProfileName, defaultProfileName) ->
|
||||
text = text.trim()
|
||||
parseLegacy: (text, matchProfileName, defaultProfileName) ->
|
||||
normal_rules = []
|
||||
exclusive_rules = []
|
||||
begin = false
|
||||
@ -107,3 +150,58 @@ module.exports = exports =
|
||||
list.push({condition: cond, profileName: profile, source: source})
|
||||
# Exclusive rules have higher priority, so they come first.
|
||||
return exclusive_rules.concat normal_rules
|
||||
|
||||
parseOmega: (text, matchProfileName, defaultProfileName) ->
|
||||
rules = []
|
||||
rulesWithDefaultProfile = []
|
||||
withResult = false
|
||||
for line in text.split(/\n|\r/)
|
||||
line = line.trim()
|
||||
continue if line.length == 0
|
||||
switch line[0]
|
||||
when '[' # Header line: Ignore.
|
||||
continue
|
||||
when ';' # Comment line: Ignore.
|
||||
continue
|
||||
when '@' # Directive line:
|
||||
iSpace = line.indexOf(' ')
|
||||
iSpace = line.length if iSpace < 0
|
||||
directive = line.substr(1, iSpace - 1)
|
||||
line = line.substr(iSpace + 1).trim()
|
||||
switch directive.toUpperCase()
|
||||
when 'WITH'
|
||||
feature = line.toUpperCase()
|
||||
if feature == 'RESULT' or feature == 'RESULTS'
|
||||
withResult = true
|
||||
continue
|
||||
|
||||
source = null
|
||||
if line[0] == '!'
|
||||
profile = if withResult then null else defaultProfileName
|
||||
source = line
|
||||
line = line.substr(1)
|
||||
else if withResult
|
||||
iSpace = line.lastIndexOf(' +')
|
||||
if iSpace < 0
|
||||
throw new Error("Missing result profile name: " + line)
|
||||
profile = line.substr(iSpace + 2).trim()
|
||||
line = line.substr(0, iSpace).trim()
|
||||
defaultProfileName = profile if line == '*'
|
||||
else
|
||||
profile = matchProfileName
|
||||
|
||||
cond = Conditions.fromStr(line)
|
||||
if not cond
|
||||
throw new Error("Invalid rule: " + line)
|
||||
|
||||
rule = {condition: cond, profileName: profile, source: source ? line}
|
||||
rules.push(rule)
|
||||
if not profile
|
||||
rulesWithDefaultProfile.push(rule)
|
||||
|
||||
if withResult
|
||||
if not defaultProfileName
|
||||
throw new Error("Missing default rule with catch-all '*' condition!")
|
||||
for rule in rulesWithDefaultProfile
|
||||
rule.profileName = defaultProfileName
|
||||
return rules
|
||||
|
@ -234,3 +234,163 @@ describe 'RuleList', ->
|
||||
conditionType: 'UrlWildcardCondition'
|
||||
pattern: 'http://www.example.com/*'
|
||||
)
|
||||
|
||||
describe 'Switchy (omega format)', ->
|
||||
parse = RuleList['Switchy'].parse
|
||||
compose = RuleList['Switchy'].compose
|
||||
it 'should parse empty rule lists', ->
|
||||
list = compose {rules: []}
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.have.length(0)
|
||||
it 'should ignore comment lines.', ->
|
||||
list = compose {rules: []}
|
||||
list += ';*.example.com \r\n'
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.have.length(0)
|
||||
it 'should compose and parse HostWildcardCondition', ->
|
||||
rule =
|
||||
source: '*.example.com'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*.example.com'
|
||||
profileName: 'match'
|
||||
list = compose({rules: [rule], defaultProfileName: 'notmatch'})
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.have.length(1)
|
||||
result[0].should.eql(rule)
|
||||
it 'should compose and parse HostRegexCondition', ->
|
||||
rule =
|
||||
source: 'HostRegex: ^http://www\.example\.com/.*'
|
||||
condition:
|
||||
conditionType: 'HostRegexCondition',
|
||||
pattern: '^http://www\.example\.com/.*'
|
||||
profileName: 'match'
|
||||
list = compose({rules: [rule], defaultProfileName: 'notmatch'})
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.have.length(1)
|
||||
result[0].should.eql(rule)
|
||||
it 'should compose and parse disabled rules', ->
|
||||
rule =
|
||||
source: 'Disabled: *.example.com'
|
||||
condition:
|
||||
conditionType: 'FalseCondition',
|
||||
pattern: '*.example.com'
|
||||
profileName: 'match'
|
||||
list = compose({rules: [rule], defaultProfileName: 'notmatch'})
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.have.length(1)
|
||||
result[0].should.eql(rule)
|
||||
it 'should compose and parse exclusive rules', ->
|
||||
rule =
|
||||
source: '!*.example.com'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*.example.com'
|
||||
profileName: 'notmatch'
|
||||
list = compose({rules: [rule], defaultProfileName: 'notmatch'})
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.have.length(1)
|
||||
result[0].should.eql(rule)
|
||||
it 'should parse multiple conditions', ->
|
||||
rules = [{
|
||||
source: '*.example.com'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*.example.com'
|
||||
profileName: 'match'
|
||||
}, {
|
||||
source: '*.example.org'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*.example.org'
|
||||
profileName: 'match'
|
||||
}]
|
||||
list = compose({rules: rules, defaultProfileName: 'notmatch'})
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.eql(rules)
|
||||
it 'should respect the top-down order of conditions', ->
|
||||
rules = [{
|
||||
source: 'b.example.com'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: 'b.example.com'
|
||||
profileName: 'match'
|
||||
}, {
|
||||
source: '!a.example.org'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: 'a.example.org'
|
||||
profileName: 'notmatch'
|
||||
}]
|
||||
list = compose({rules: rules, defaultProfileName: 'notmatch'})
|
||||
result = parse(list, 'match', 'notmatch')
|
||||
result.should.eql(rules)
|
||||
it 'should add a default rule when results are enabled', ->
|
||||
list = compose(
|
||||
{rules: [], defaultProfileName: 'notmatch'}
|
||||
{withResult: true}
|
||||
)
|
||||
list.split(/\r|\n/).should.contain('@with result')
|
||||
result = parse(list, 'ignored', 'alsoIgnored')
|
||||
result.should.have.length(1)
|
||||
result[0].should.eql({
|
||||
source: '*'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*'
|
||||
profileName: 'notmatch',
|
||||
})
|
||||
it 'should compose and parse conditions with results', ->
|
||||
rules = [{
|
||||
source: 'b.example.com'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: 'b.example.com'
|
||||
profileName: 'abc'
|
||||
}, {
|
||||
source: 'a.example.org'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: 'a.example.org'
|
||||
profileName: 'def'
|
||||
}]
|
||||
list = compose(
|
||||
{rules: rules, defaultProfileName: 'ghi'}
|
||||
{withResult: true}
|
||||
)
|
||||
result = parse(list, 'ignored', 'alsoIgnored')
|
||||
rules.push({
|
||||
source: '*'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*'
|
||||
profileName: 'ghi',
|
||||
})
|
||||
result.should.eql(rules)
|
||||
it 'should compose and parse exclusive conditions with results', ->
|
||||
rules = [{
|
||||
source: '!b.example.com'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: 'b.example.com'
|
||||
profileName: 'default profile'
|
||||
}, {
|
||||
source: 'a.example.org'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: 'a.example.org'
|
||||
profileName: 'some profile'
|
||||
}]
|
||||
list = compose(
|
||||
{rules: rules, defaultProfileName: 'default profile'}
|
||||
{withResult: true, useExclusive: true}
|
||||
)
|
||||
result = parse(list, 'ignored', 'alsoIgnored')
|
||||
rules.push({
|
||||
source: '*'
|
||||
condition:
|
||||
conditionType: 'HostWildcardCondition',
|
||||
pattern: '*'
|
||||
profileName: 'default profile',
|
||||
})
|
||||
result.should.eql(rules)
|
||||
|
Loading…
Reference in New Issue
Block a user