mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
220 lines
6.4 KiB
Swift
220 lines
6.4 KiB
Swift
//
|
|
// RulesStore.swift
|
|
// SwiftFormat
|
|
//
|
|
// Created by Vincent Bernier on 28-01-18.
|
|
// Copyright 2018 Nick Lockwood
|
|
//
|
|
// Distributed under the permissive MIT license
|
|
// Get the latest version from here:
|
|
//
|
|
// https://github.com/nicklockwood/SwiftFormat
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
//
|
|
|
|
import Foundation
|
|
import NaturalLanguage
|
|
|
|
extension UserDefaults {
|
|
static let groupDomain = "com.charcoaldesign.SwiftFormat"
|
|
|
|
func clearAll(in domainName: String) {
|
|
persistentDomain(forName: domainName)?.forEach {
|
|
removeObject(forKey: $0.key)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Rule {
|
|
let name: String
|
|
var isEnabled: Bool
|
|
}
|
|
|
|
extension Rule: Comparable {
|
|
/// Looks up and returns a format rule, if found.
|
|
var formatRule: FormatRule? {
|
|
FormatRules.byName[name]
|
|
}
|
|
|
|
var isDeprecated: Bool {
|
|
formatRule?.isDeprecated == true
|
|
}
|
|
|
|
/// Space-separated, lowercased text terms that this rule might by found by.
|
|
var searchableText: String {
|
|
var items = [name]
|
|
if let formatRule {
|
|
items.append(formatRule.help.keywords.joined(separator: " "))
|
|
items.append(formatRule.options.joined(separator: " "))
|
|
items.append(formatRule.sharedOptions.joined(separator: " "))
|
|
}
|
|
return items.joined(separator: " ").lowercased()
|
|
}
|
|
|
|
static func < (lhs: Rule, rhs: Rule) -> Bool {
|
|
if lhs.name == rhs.name {
|
|
return lhs.isEnabled
|
|
}
|
|
|
|
return lhs.name < rhs.name
|
|
}
|
|
}
|
|
|
|
private extension Rule {
|
|
init(_ ruleRep: (String, Bool)) {
|
|
self.init(name: ruleRep.0, isEnabled: ruleRep.1)
|
|
}
|
|
}
|
|
|
|
struct RulesStore {
|
|
private typealias RuleName = String
|
|
private typealias RuleIsEnabled = Bool
|
|
private typealias RulesRepresentation = [RuleName: RuleIsEnabled]
|
|
private let rulesKey = "rules"
|
|
private let store: UserDefaults
|
|
|
|
private static var defaultStore: UserDefaults = {
|
|
guard let defaults = UserDefaults(suiteName: UserDefaults.groupDomain) else {
|
|
fatalError("The UserDefaults Store is invalid")
|
|
}
|
|
return defaults
|
|
}()
|
|
|
|
init(_ store: UserDefaults = RulesStore.defaultStore) {
|
|
self.store = store
|
|
setupDefaultValuesIfNeeded()
|
|
}
|
|
|
|
var rules: [Rule] {
|
|
load()
|
|
.map { Rule($0) }
|
|
.filter { !$0.isDeprecated }
|
|
}
|
|
|
|
func save(_ rule: Rule) {
|
|
save([rule])
|
|
}
|
|
|
|
func save(_ rules: [Rule]) {
|
|
var active = Set<String>()
|
|
var disabled = Set<String>()
|
|
|
|
for rule in rules {
|
|
if rule.isEnabled {
|
|
active.insert(rule.name)
|
|
} else {
|
|
disabled.insert(rule.name)
|
|
}
|
|
}
|
|
save(active: active, disabled: disabled)
|
|
}
|
|
|
|
func restore(_ rules: [Rule]) {
|
|
clear()
|
|
save(rules)
|
|
addNewRulesIfNeeded()
|
|
}
|
|
|
|
func resetRulesToDefaults() {
|
|
let allRuleNames = Set(FormatRules.all.map(\.name))
|
|
let disabledRules = Set(FormatRules.disabledByDefault.map(\.name))
|
|
let activeRules = allRuleNames.subtracting(disabledRules)
|
|
|
|
clear()
|
|
save(active: activeRules, disabled: disabledRules)
|
|
}
|
|
}
|
|
|
|
// MARK: - Business Rules
|
|
|
|
extension RulesStore {
|
|
private func setupDefaultValuesIfNeeded() {
|
|
// check if first time
|
|
if store.value(forKey: rulesKey) == nil {
|
|
resetRulesToDefaults()
|
|
} else {
|
|
addNewRulesIfNeeded()
|
|
}
|
|
}
|
|
|
|
private func addNewRulesIfNeeded() {
|
|
let currentRules = load()
|
|
let currentRuleNames = Set(currentRules.keys)
|
|
let allRuleNames = Set(FormatRules.all.map(\.name))
|
|
let newRuleNames = allRuleNames.subtracting(currentRuleNames)
|
|
if newRuleNames.isEmpty {
|
|
return
|
|
}
|
|
|
|
let disabledRules = Set(FormatRules.disabledByDefault.map(\.name))
|
|
var rules = currentRules
|
|
for newRuleName in newRuleNames {
|
|
rules[newRuleName] = !disabledRules.contains(newRuleName)
|
|
}
|
|
|
|
save(rules)
|
|
}
|
|
}
|
|
|
|
// MARK: - Store Interactions
|
|
|
|
extension RulesStore {
|
|
private func clear() {
|
|
store.set(nil, forKey: rulesKey)
|
|
}
|
|
|
|
private func load() -> RulesRepresentation {
|
|
guard let rules = store.value(forKey: rulesKey) as? RulesRepresentation else {
|
|
return RulesRepresentation()
|
|
}
|
|
return rules
|
|
}
|
|
|
|
/// Save the provided rules
|
|
/// Will only override the rules in the params
|
|
private func save(active: Set<String>, disabled: Set<String>) {
|
|
var rules = load()
|
|
active.forEach { rules[$0] = true }
|
|
disabled.forEach { rules[$0] = false }
|
|
save(rules)
|
|
}
|
|
|
|
/// Will replace the rules with the param
|
|
private func save(_ rules: RulesRepresentation) {
|
|
store.set(rules, forKey: rulesKey)
|
|
}
|
|
}
|
|
|
|
private extension String {
|
|
/// Returns search-relevant keywords, ignoring prepositions, conjunctions, etc.
|
|
var keywords: [String] {
|
|
let tagger = NLTagger(tagSchemes: [.lexicalClass])
|
|
tagger.string = self
|
|
var results = [String]()
|
|
tagger.enumerateTags(in: startIndex ..< endIndex, unit: .word, scheme: .lexicalClass) { tag, range in
|
|
if [.verb, .noun, .adjective, .adverb, .otherWord].contains(tag) {
|
|
results.append(String(self[range]))
|
|
}
|
|
return true
|
|
}
|
|
return results
|
|
}
|
|
}
|