Skip to content

Commit

Permalink
0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre Houston committed Mar 20, 2020
0 parents commit c976af5
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
34 changes: 34 additions & 0 deletions Package.resolved
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
}
31 changes: 31 additions & 0 deletions Package.swift
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"),
]),
]
)
36 changes: 36 additions & 0 deletions README.md
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()
```
87 changes: 87 additions & 0 deletions Sources/RenameCommand/RenameCommand.swift
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)
}
}

0 comments on commit c976af5

Please sign in to comment.