-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
540 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,127 @@ | ||
# Stringy | ||
|
||
A description of this package. | ||
Handy string conversions | ||
|
||
## Command Line Tool | ||
|
||
A Mac OS command line tool to perform some handy string conversions | ||
|
||
### Usage | ||
|
||
``` | ||
OVERVIEW: Handy string conversions | ||
USAGE: stringy <subcommand> | ||
OPTIONS: | ||
-h, --help Show help information. | ||
SUBCOMMANDS: | ||
camelcase Converts strings to camelCase | ||
snakecase Converts strings to snake_case. | ||
kebabcase Converts strings to kebab-case. | ||
See 'stringy help <subcommand>' for detailed help. | ||
``` | ||
|
||
### Subcommands | ||
|
||
#### camelcase | ||
|
||
``` | ||
OVERVIEW: Converts strings to camelCase | ||
E.G. Earth Sun Moon -> earthSunMoon | ||
USAGE: stringy camelcase [--invert] [<strings> ...] | ||
ARGUMENTS: | ||
<strings> | ||
OPTIONS: | ||
-i, --invert Inverts the conversion. | ||
-h, --help Show help information. | ||
``` | ||
|
||
#### snakecase | ||
|
||
``` | ||
OVERVIEW: Converts strings to snake_case. | ||
E.G. 'Snakes are slithery' -> 'snakes_are_slithery' | ||
USAGE: stringy snakecase [--invert] [<strings> ...] | ||
ARGUMENTS: | ||
<strings> | ||
OPTIONS: | ||
-i, --invert Inverts the conversion. | ||
-h, --help Show help information. | ||
``` | ||
|
||
#### snakecase | ||
|
||
``` | ||
OVERVIEW: Converts strings to kebab-case. | ||
E.G. 'Words on a stick' -> 'words-on-a-stick' | ||
USAGE: stringy kebabcase [--invert] [<strings> ...] | ||
ARGUMENTS: | ||
<strings> | ||
OPTIONS: | ||
-i, --invert Inverts the conversion. | ||
-h, --help Show help information. | ||
``` | ||
|
||
### Installation | ||
|
||
|
||
#### With [`Mint`](https://github.com/yonaskolb/Mint) | ||
|
||
```sh | ||
$ mint install salishseasoftware/stringy | ||
``` | ||
|
||
|
||
#### Manually | ||
|
||
Clone the repo then: | ||
|
||
``` | ||
$ make install | ||
``` | ||
|
||
Or using swift itself: | ||
|
||
``` | ||
$ swift build -c release | ||
$ cp .build/release/stringy /usr/local/bin/stringy | ||
``` | ||
|
||
#### With Xcode | ||
|
||
Generate the Xcode project: | ||
|
||
``` | ||
$ swift package generate-xcodeproj | ||
$ open ./Stringy.xcodeproj | ||
``` | ||
|
||
In Xcode: | ||
|
||
1. Product > Archive | ||
1. Distribute Content | ||
1. Built Products | ||
1. copy `stringy` executable to `/usr/local/bin/` or wherever you prefer. | ||
|
||
|
||
## LibStringy | ||
|
||
Swift package of handy String extensions. | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import Foundation | ||
|
||
public extension String { | ||
|
||
// MARK: - Web URL | ||
|
||
/// Extract all web urls from a string. | ||
func webURLs() -> [String] { | ||
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { | ||
return [] | ||
} | ||
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) | ||
|
||
return matches.compactMap { | ||
guard let url = $0.url, | ||
let components = URLComponents(url: url, resolvingAgainstBaseURL: false), | ||
components.url != nil, | ||
["http", "https"].contains(components.scheme) | ||
else { | ||
return nil | ||
} | ||
return url.absoluteString.isEmpty ? nil : url.absoluteString | ||
} | ||
} | ||
|
||
/// True if a string is a valid web URL, according to apple's data detectors anyway. | ||
var isWebUrl: Bool { | ||
let urls = self.webURLs() | ||
guard urls.count == 1, let found = urls.first else { return false } | ||
return found == self | ||
} | ||
|
||
// MARK: - Email address | ||
|
||
/// Extract all email addresses from a string. | ||
func emailAddresses() -> [String] { | ||
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { | ||
return [] | ||
} | ||
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) | ||
|
||
return matches.compactMap { | ||
guard let url = $0.url, | ||
let components = URLComponents(url: url, resolvingAgainstBaseURL: false), | ||
components.scheme == "mailto" | ||
else { | ||
return nil | ||
} | ||
return components.path | ||
} | ||
} | ||
|
||
/// True if a string is a valid email address, according to apple's data detectors anyway. | ||
/// | ||
/// Friends don't let friends use regex to validate email addresses | ||
/// | ||
/// * https://stackoverflow.com/questions/48055431/can-it-cause-harm-to-validate-email-addresses-with-a-regex | ||
/// * https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression | ||
/// | ||
var isEmailAddress: Bool { | ||
let emails = self.emailAddresses() | ||
guard emails.count == 1, let found = emails.first else { return false } | ||
return found == self | ||
} | ||
|
||
// MARK: - Phone number | ||
|
||
/// True if a string is a valid phone number, according to apple's data detectors anyway. | ||
var isPhoneNumber: Bool { | ||
let phoneNumbers = self.phoneNumbers() | ||
guard phoneNumbers.count == 1, let found = phoneNumbers.first else { return false } | ||
return found == self | ||
} | ||
|
||
/// Extract all phone numbers from a string. | ||
func phoneNumbers() -> [String] { | ||
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.phoneNumber.rawValue) else { | ||
return [] | ||
} | ||
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) | ||
return matches.compactMap { $0.phoneNumber } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Foundation | ||
|
||
extension String: Error, LocalizedError { | ||
public var errorDescription: String? { return self } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Foundation | ||
|
||
public extension String { | ||
/// Adds prefix to self | ||
mutating func addPrefix(_ prefix: String) { | ||
self = prefix + self | ||
} | ||
|
||
/// Returns a new string, with the prefix added | ||
func addingPrefix(_ prefix: String) -> String { | ||
return prefix + self | ||
} | ||
|
||
/// Adds a given prefix to self, if the prefix does exist in self. | ||
mutating func addPrefixIfNeeded(_ prefix: String) { | ||
guard !self.hasPrefix(prefix) else { return } | ||
self = prefix + self | ||
} | ||
|
||
/// Returns a new string, with the prefix added if does not already exist. | ||
func addingPrefixIfNeeded(_ prefix: String) -> String { | ||
guard !self.hasPrefix(prefix) else { return self } | ||
return prefix + self | ||
} | ||
} | ||
|
||
public extension Optional where Wrapped == String { | ||
/// returns nil for an empty string | ||
var nilIfEmpty: String? { | ||
guard let strongSelf = self else { | ||
return nil | ||
} | ||
return strongSelf.isEmpty ? nil : strongSelf | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import Foundation | ||
|
||
public extension StringProtocol { | ||
/// Sparate a string by word boundaries. | ||
/// | ||
/// - Returns: An array of strings, representing a list of words. | ||
func words() -> [String] { | ||
return self.substrings(options: .byWords) | ||
} | ||
|
||
/// Sparate a string by sentences. | ||
/// | ||
/// - Returns: An array of strings, representing a list of sentences. | ||
func sentences() -> [String] { | ||
return self.substrings(options: .bySentences) | ||
} | ||
|
||
/// Sparate a string by paragraphs. | ||
/// | ||
/// - Returns: An array of strings, representing a list of paragraphs. | ||
func paragraphs() -> [String] { | ||
return self.substrings(options: .byParagraphs) | ||
} | ||
|
||
/// Sparate a string by lines. | ||
/// | ||
/// - Returns: An array of strings, representing a list of lines. | ||
func lines() -> [String] { | ||
return self.substrings(options: .byLines) | ||
} | ||
|
||
/// Sparate a string eumeration options. | ||
/// | ||
/// - Parameter options: Options specifying types of substrings and enumeration styles. | ||
/// If opts is omitted or empty, body is called a single time with the range of | ||
/// the string specified by range. | ||
/// - Returns: Array of strings, separated by the specified options. | ||
func substrings(options: NSString.EnumerationOptions) -> [String] { | ||
let range: Range<String.Index> = self.startIndex ..< self.endIndex | ||
var substrings = [String]() | ||
self.enumerateSubstrings(in: range, options: options) {substr,_,_,_ in | ||
guard let substring = substr, !substring.isEmpty else { return } | ||
substrings.append(substring) | ||
} | ||
return substrings | ||
} | ||
|
||
var isCapitalized: Bool { | ||
return self.first?.isUppercase ?? false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import Foundation | ||
import ArgumentParser | ||
import LibStringy | ||
|
||
extension Stringy { | ||
struct CamelCase: ParsableCommand { | ||
static var configuration = CommandConfiguration( | ||
commandName: "camelcase", | ||
abstract: "Converts strings to camelCase", | ||
discussion: "E.G. Earth Sun Moon -> earthSunMoon" | ||
) | ||
|
||
@OptionGroup var options: Stringy.Options | ||
|
||
mutating func run() { | ||
print(options.invert ? options.string.uncamelcased() : options.string.camelcased()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
import ArgumentParser | ||
import LibStringy | ||
|
||
struct KebabCase: ParsableCommand { | ||
static var configuration = CommandConfiguration( | ||
commandName: "kebabcase", | ||
abstract: "Converts strings to kebab-case.", | ||
discussion: "E.G. 'Words on a stick' -> 'words-on-a-stick'" | ||
) | ||
|
||
@OptionGroup var options: Stringy.Options | ||
|
||
mutating func run() { | ||
print(options.invert ? options.string.unkebabcased() : options.string.kebabcased()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
import ArgumentParser | ||
import LibStringy | ||
|
||
struct SnakeCase: ParsableCommand { | ||
static var configuration = CommandConfiguration( | ||
commandName: "snakecase", | ||
abstract: "Converts strings to snake_case.", | ||
discussion: "E.G. 'Snakes are slithery' -> 'snakes_are_slithery'" | ||
) | ||
|
||
@OptionGroup var options: Stringy.Options | ||
|
||
mutating func run() { | ||
print(options.invert ? options.string.unsnakecased() : options.string.snakecased()) | ||
} | ||
} |
Oops, something went wrong.