-
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
Pierre Houston
committed
Mar 20, 2020
0 parents
commit c976af5
Showing
5 changed files
with
193 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ |
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,34 @@ | ||
{ | ||
"object": { | ||
"pins": [ | ||
{ | ||
"package": "Files", | ||
"repositoryURL": "https://github.com/JohnSundell/Files", | ||
"state": { | ||
"branch": null, | ||
"revision": "22fe84797d499ffca911ccd896b34efaf06a50b9", | ||
"version": "4.1.1" | ||
} | ||
}, | ||
{ | ||
"package": "Regex", | ||
"repositoryURL": "https://github.com/sharplet/Regex", | ||
"state": { | ||
"branch": null, | ||
"revision": "76c2b73d4281d77fc3118391877efd1bf972f515", | ||
"version": "2.1.1" | ||
} | ||
}, | ||
{ | ||
"package": "swift-argument-parser", | ||
"repositoryURL": "https://github.com/apple/swift-argument-parser", | ||
"state": { | ||
"branch": null, | ||
"revision": "35b76bf577d3cc74820f8991894ce3bcdf024ddc", | ||
"version": "0.0.2" | ||
} | ||
} | ||
] | ||
}, | ||
"version": 1 | ||
} |
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,31 @@ | ||
// swift-tools-version:5.1 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "RenameCommand", | ||
products: [ | ||
// Products define the executables and libraries produced by a package, and make them visible to other packages. | ||
.library( | ||
name: "RenameCommand", | ||
targets: ["RenameCommand"]), | ||
], | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.2"), | ||
.package(url: "https://github.com/JohnSundell/Files", from: "4.0.0"), | ||
.package(url: "https://github.com/sharplet/Regex", from: "2.0.0"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package. A target can define a module or a test suite. | ||
// Targets can depend on other targets in this package, and on products in packages which this package depends on. | ||
.target( | ||
name: "RenameCommand", | ||
dependencies: [ | ||
.product(name: "ArgumentParser", package: "swift-argument-parser"), | ||
.product(name: "Files", package: "Files"), | ||
.product(name: "Regex", package: "Regex"), | ||
]), | ||
] | ||
) |
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,36 @@ | ||
# RenameCommand | ||
|
||
A library making it easy to make a swift command-line program for renaming files according to your own rules. | ||
|
||
It exports a struct `RenameOptions` conforming to the `ParsableArguments` protocol from Apple's `ArgumentParser`. It can be used with `@OptionGroup()` and your own `ParsableCommand` and also provides a `runRename()` function you can call within your own `run()`, doing most of the work needed. | ||
|
||
`runRename()` takes a function argument with a inout `name` parameter, you provide this function which changes `name` as desired. This is called for every file passed on the command line, with the file extension omitted if any, and the file gets renamed accordingly. | ||
|
||
`RenameOptions` defines arguments `verbose`, `silent`, `dry-run`. | ||
|
||
It works well with `swift-sh`, also the `Regex` package at http://github.com/sharplet/Regex which it extends with a convenience function for case insensitive matching. | ||
|
||
For example: | ||
|
||
```swift | ||
#!/usr/bin/swift sh | ||
import ArgumentParser // apple/swift-argument-parser | ||
import RenameCommand // @jpmhouston | ||
import Regex // @sharplet | ||
|
||
struct RenameMoviesCommand: ParsableCommand { | ||
static let configuration = CommandConfiguration(abstract: "Renames my ripped movies from their old name format to how I prefer them now.") | ||
@OptionGroup() var options: RenameCommand.RenameOptions | ||
|
||
func run() throws { | ||
try options.runRename() { name in | ||
name.replaceAll(matching: #"\."#, with: " ") | ||
name.replaceFirst(matchingIgnoringCase: " 720p", with: "") | ||
name.replaceFirst(matchingIgnoringCase: " 1080p", with: "") | ||
name.replaceFirst(matching: " ([0-9][0-9][0-9][0-9])$", with: " ($1)") | ||
} | ||
} | ||
} | ||
|
||
RenameMoviesCommand.main() | ||
``` |
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,87 @@ | ||
// | ||
// RenameCommand.swift | ||
// RenameCommand | ||
// | ||
// Created by Pierre Houston on 2020-03-19. | ||
// Copyright © 2020 Pierre Houston. All rights reserved. | ||
// | ||
|
||
import ArgumentParser | ||
import Files | ||
import Regex | ||
|
||
public struct RenameOptions: ParsableArguments { | ||
@Argument(help: "Files to rename.") | ||
public var files: [String] | ||
|
||
@Flag(name: .shortAndLong, help: "Silent output.") | ||
public var silent: Bool | ||
|
||
@Flag(name: .shortAndLong, help: "Verbose output (overrides \"--silent\").") | ||
public var verbose: Bool | ||
|
||
@Flag(name: [.long, .customLong("dry-run")], help: "Don't perform rename just output the result.") | ||
public var dryRun: Bool | ||
|
||
public init() { } // swift complains if this not present | ||
|
||
@discardableResult | ||
public func runRename(_ renameFunc: (_ name: inout String) -> Void) throws -> Int { | ||
var i = 0, nrenamed = 0 | ||
for path in files { | ||
i += 1 | ||
|
||
let file = try File(path: path) | ||
guard let parent = file.parent else { | ||
throw Files.LocationError(path: path, reason: .cannotRenameRoot) | ||
} | ||
let fileName = file.name | ||
|
||
if verbose { print("\(i). \(parent.path)\(fileName)") } | ||
|
||
var baseName = fileName | ||
var fileExtn: String? = nil | ||
let components = fileName.split(separator: ".") | ||
if let ext = components.last { | ||
fileExtn = String(ext) | ||
baseName = components.dropLast().joined(separator: ".") | ||
} | ||
|
||
renameFunc(&baseName) | ||
|
||
let replacementName = fileExtn != nil ? "\(baseName).\(fileExtn!)" : baseName | ||
|
||
if replacementName != fileName { | ||
if !dryRun { | ||
try file.rename(to: replacementName) | ||
} | ||
|
||
if verbose { print("\(String(repeating: " ", count: "\(i)".count)) renamed to \(replacementName)") } | ||
else if !silent { print("'\(fileName)' renamed to '\(replacementName)'") } | ||
nrenamed += 1 | ||
} else { | ||
if verbose { print("\(String(repeating: " ", count: "\(i)".count)) not renamed") } | ||
else if !silent && dryRun { print("'\(fileName)' not renamed") } | ||
} | ||
} | ||
return nrenamed | ||
} | ||
} | ||
|
||
// add conveniences to Regex/String+ReplaceMatching.swift | ||
extension String { | ||
public mutating func replaceFirst(matchingIgnoringCase pattern: StaticString, with template: String) { | ||
replaceFirst(matching: Regex(pattern, options: [.ignoreCase]), with: template) | ||
} | ||
public mutating func replaceAll(matchingIgnoringCase pattern: StaticString, with template: String) { | ||
replaceAll(matching: Regex(pattern, options: [.ignoreCase]), with: template) | ||
} | ||
|
||
// for completeness, however not expected to be used by users of RenameCommand | ||
public func replacingFirst(matchingIgnoringCase pattern: StaticString, with template: String) -> String { | ||
return replacingFirst(matching: Regex(pattern, options: [.ignoreCase]), with: template) | ||
} | ||
public func replacingAll(matchingIgnoringCase pattern: StaticString, with template: String) -> String { | ||
return replacingAll(matching: Regex(pattern, options: [.ignoreCase]), with: template) | ||
} | ||
} |