Skip to content

Commit cc82c2c

Browse files
authored
Merge pull request #1 from enuance/CPFeature-003-BranchParseMode
Cp feature 003 branch parse mode
2 parents 2cc4096 + d1555f2 commit cc82c2c

File tree

12 files changed

+715
-142
lines changed

12 files changed

+715
-142
lines changed

README.md

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ ___
88

99
CommitPrefix is a simple command line tool that helps you to easily prefix your commit messages. The common use case for this is tagging your commit messages with a Jira (or other issue tracking software) ticket number. The desired prefix is stored within the .git folder and picked up by a generated commit-message hook. This allows you to write your ticket number (or any other prefix) once. From then on all commit messages will be prepended with the saved prefix.
1010

11+
There's also a branch parse mode that allows commitPrefix to parse the current branch you're on for a valid issue numbers and use them as prefixes for your next commit. The modes can be switched back and forth arbitrarily and used along with any self defined prefixes.
12+
1113
Prefixes can be re-assigned or deleted at any time. Additionally, this is a git repository specific tool, meaning that stored prefixes are specific to the repository you're in.
1214

1315
The actions that can be done are:
1416

15-
* Store a commit prefix
16-
* Delete the currently stored prefix
17-
* View the currently stored prefix
17+
* Store an arbitrary number of commit prefixes
18+
* Generate prefixes based on your current branch
19+
* Delete the currently stored prefixes
20+
* View the current mode and stored prefixes
1821

1922
___
2023
### -- Installation --
@@ -68,26 +71,45 @@ To use commitPrefix you need to have your working directory set to one that has
6871

6972
To **store** a prefix
7073
```zsh
71-
% commitPrefix SamplePrefix-001
74+
% commitPrefix SamplePrefix-001,SamplePrefix-002
75+
76+
# Output
77+
CommitPrefix STORED [SamplePrefix-001][SamplePrefix-002]
78+
```
79+
80+
To change mode to **branchParse**
81+
```zsh
82+
% git checkout ENG-342-SomeFeatureBranchLinkedToENG-101
83+
% commitPrefix -b eng
84+
85+
# Output
86+
CommitPrefix MODE BRANCH_PARSE eng
87+
```
88+
89+
To **view** the current prefixes and mode
90+
```zsh
91+
% commitPrefix
7292

7393
# Output
74-
CommitPrefix saved: [SamplePrefix-001]
94+
CommitPrefix MODE BRANCH_PARSE
95+
- branch prefixes: [SamplePrefix-001][SamplePrefix-002]
96+
- stored prefixes: [ENG-342][ENG-101]
7597
```
7698

77-
To **view** a prefix
99+
To change back to **normal** mode
78100
```zsh
79-
% commitPrefix --view
101+
% commitPrefix -n
80102

81103
# Output
82-
CommitPrefix: [SamplePrefix-001]
104+
CommitPrefix MODE NORMAL
83105
```
84106

85107
To **delete** a prefix
86108
```zsh
87-
% commitPrefix --delete
109+
% commitPrefix -d
88110

89111
# Output
90-
CommitPrefix Deleted
112+
CommitPrefix DELETED
91113
```
92114

93115
You can also view these command along with shortend version by using the `--help` tag.

Sources/CommitPrefix/CLIArguments.swift

Lines changed: 139 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -30,108 +30,200 @@ import SPMUtility
3030
public struct CLIArguments {
3131

3232
public enum UserCommand {
33-
case delete
34-
case view
35-
case newEntry(entry: String)
33+
case viewState
34+
case outputPrefixes
35+
case deletePrefixes
36+
case modeNormal
37+
case modeBranchParse(validator: String)
38+
case newPrefixes(value: String)
39+
}
40+
41+
private enum ParsedCommand {
42+
case viewState
43+
case outputPrefixes
44+
case deletePrefixes
45+
case modeNormal
46+
case modeBranchParse
47+
case userEntry(value: String)
3648
}
3749

3850
private let parser: ArgumentParser
3951
private let rawArgs: [String]
4052

41-
private let delete: OptionArgument<Bool>
42-
private let view: OptionArgument<Bool>
43-
private let newEntry: PositionalArgument<[String]>
53+
private let outputPrefixes: OptionArgument<Bool>
54+
private let deletePrefixes: OptionArgument<Bool>
55+
private let modeNormal: OptionArgument<Bool>
56+
private let modeBranchParse: OptionArgument<Bool>
57+
private let userEntry: PositionalArgument<[String]>
4458

4559
public init(arguments: [String] = CommandLine.arguments) {
4660
// The first argument specifies the path of the executable file
4761
self.rawArgs = Array(arguments.dropFirst())
4862
let argBuilder = ArgumentBuilder()
4963
self.parser = argBuilder.buildParser()
50-
self.delete = argBuilder.buildDeleteArgument(parser: parser)
51-
self.view = argBuilder.buildViewArgument(parser: parser)
52-
self.newEntry = argBuilder.buildNewEntryArgument(parser: parser)
64+
65+
self.outputPrefixes = argBuilder.buildOutputArgument(parser: parser)
66+
self.deletePrefixes = argBuilder.buildDeleteArgument(parser: parser)
67+
self.modeNormal = argBuilder.buildNormalArgument(parser: parser)
68+
self.modeBranchParse = argBuilder.buildBranchParseArgument(parser: parser)
69+
self.userEntry = argBuilder.buildUserEntryArgument(parser: parser)
5370
}
5471

72+
private func singleCommandParse(_ allCommands: [ParsedCommand]) throws -> UserCommand {
73+
precondition(allCommands.count == 1, "Intended for single Parsed Command only!")
74+
guard let foundCommand = allCommands.first else {
75+
throw CPError.userCommandNotRecognized
76+
}
77+
78+
switch foundCommand {
79+
case .outputPrefixes:
80+
return .outputPrefixes
81+
case .deletePrefixes:
82+
return .deletePrefixes
83+
case .modeNormal:
84+
return .modeNormal
85+
case .userEntry(value: let prefixes):
86+
return .newPrefixes(value: prefixes)
87+
default:
88+
throw CPError.userCommandNotRecognized
89+
}
90+
}
91+
92+
private func doubleCommandParse(_ allCommands: [ParsedCommand]) throws -> UserCommand {
93+
precondition(allCommands.count == 2, "Intended for two Parsed Commands only!")
94+
let firstCommand = allCommands[0]
95+
let secondCommand = allCommands[1]
96+
97+
switch (firstCommand, secondCommand) {
98+
case (.modeBranchParse, .userEntry(value: let validator)):
99+
return .modeBranchParse(validator: validator)
100+
case (.userEntry(value: let validator), .modeBranchParse):
101+
return .modeBranchParse(validator: validator)
102+
default:
103+
throw CPError.userCommandNotRecognized
104+
}
105+
}
106+
55107
func getCommand() throws -> UserCommand {
56108
guard let parsedArgs = try? parser.parse(rawArgs) else {
57109
throw CPError.userCommandNotRecognized
58110
}
59111

60-
var allCommands = [UserCommand]()
112+
var allCommands = [ParsedCommand]()
61113

62-
parsedArgs.get(delete).map { _ in allCommands.append(.delete) }
63-
parsedArgs.get(view).map { _ in allCommands.append(.view) }
64-
try parsedArgs.get(newEntry).map { userEntry in
65-
66-
guard userEntry.count < 2 else {
67-
throw CPError.newEntryShouldNotHaveSpaces
68-
}
69-
70-
guard let theEntry = userEntry.first else {
71-
throw CPError.emptyEntry
72-
}
73-
74-
guard !theEntry.isEmpty else {
75-
throw CPError.emptyEntry
76-
}
77-
78-
allCommands.append(.newEntry(entry: theEntry))
79-
80-
}
114+
parsedArgs.get(outputPrefixes).map { _ in allCommands.append(.outputPrefixes) }
115+
parsedArgs.get(deletePrefixes).map { _ in allCommands.append(.deletePrefixes) }
116+
parsedArgs.get(modeNormal).map { _ in allCommands.append(.modeNormal) }
117+
parsedArgs.get(modeBranchParse).map { _ in allCommands.append(.modeBranchParse) }
81118

82-
guard allCommands.count < 2 else {
83-
throw CPError.multipleArguments
119+
try parsedArgs.get(userEntry).map { userEntry in
120+
let noMoreThanOneEntry = userEntry.count < 2
121+
guard noMoreThanOneEntry else { throw CPError.newEntryShouldNotHaveSpaces }
122+
guard let theEntry = userEntry.first else { throw CPError.emptyEntry }
123+
allCommands.append(.userEntry(value: theEntry))
84124
}
85125

86-
guard let command = allCommands.first else {
87-
throw CPError.userCommandNotRecognized
126+
switch allCommands.count {
127+
case 0:
128+
return .viewState
129+
case 1:
130+
return try singleCommandParse(allCommands)
131+
case 2:
132+
return try doubleCommandParse(allCommands)
133+
default:
134+
throw CPError.multipleArguments
88135
}
89136

90-
return command
91137
}
92138

93139
}
94140

95141
private struct ArgumentBuilder {
96142

97143
let usage: String = """
98-
<Commit Prefix Description>
144+
[<PrefixValue1>,<PrefixValue2>,<PrefixValue3>...] [-o | --output] [-d | --delete]
145+
[-n | -normal] [ -b | --branchParse <ValidatorValue> ]
99146
"""
100147

101148
let overview: String = """
102-
The CommitPrefix stores a desired prefix for your commit messages.
103-
It stores it within the .git folder of the current repository. A
104-
commit-msg hook is also generated and stored within the .git
105-
folder which is used to prefix the commit message.
149+
150+
The CommitPrefix stores a desired set of prefixes for your commit messages. It
151+
stores it within the .git folder of the current repository. A commit-msg hook is
152+
also generated and stored within the .git folder which is used to prefix the
153+
commit message.
154+
155+
Modes:
156+
CommitPrefix has two modes, normal and branch parse.
157+
158+
- NORMAL
159+
example: commitPrefix <PrefixValue1>,<PrefixValue2>,<PrefixValue3>...
160+
161+
You can add normal prefixes by entering comma seperated values. These values will
162+
be parsed as prefixes and prepended to future commit messages.
163+
164+
- BRANCH_PARSE
165+
example commitPrefix -b <ValidatorValue>
166+
167+
Branch parse mode checks the current branch for an issue number validated by the
168+
value passed in as an argument. For example if you passed in a validator value of
169+
"eng" and your current branch was named ENG-342-SomeFeatureBranchLinkedToENG-101,
170+
commitPrefix will pickup [ENG-342] and [ENG-101] as branch prefixes to be
171+
prepended to you next commit along with any other normal prefixes you might have.
172+
173+
You can change back to NORMAL mode by entering:
174+
example: commitPrefix -n
175+
176+
To view the current state of prefixes and mode, enter:
177+
example: commitPrefix
106178
"""
107179

108180
func buildParser() -> ArgumentParser {
109181
ArgumentParser(usage: usage, overview: overview)
110182
}
111-
183+
184+
func buildOutputArgument(parser: ArgumentParser) -> OptionArgument<Bool> {
185+
return parser.add(
186+
option: "--output",
187+
shortName: "-o",
188+
kind: Bool.self,
189+
usage: "Outputs the full, formated prefix to standard output",
190+
completion: nil
191+
)
192+
}
193+
112194
func buildDeleteArgument(parser: ArgumentParser) -> OptionArgument<Bool> {
113195
return parser.add(
114196
option: "--delete",
115197
shortName: "-d",
116198
kind: Bool.self,
117-
usage: "Deletes the stored prefix",
199+
usage: "Deletes the stored prefixes",
118200
completion: nil
119201
)
120202
}
121203

122-
func buildViewArgument(parser: ArgumentParser) -> OptionArgument<Bool> {
204+
func buildNormalArgument(parser: ArgumentParser) -> OptionArgument<Bool> {
205+
return parser.add(
206+
option: "--normal",
207+
shortName: "-n",
208+
kind: Bool.self,
209+
usage: "Sets the mode to NORMAL",
210+
completion: nil
211+
)
212+
}
213+
214+
func buildBranchParseArgument(parser: ArgumentParser) -> OptionArgument<Bool> {
123215
return parser.add(
124-
option: "--view",
125-
shortName: "-v",
216+
option: "--branchParse",
217+
shortName: "-b",
126218
kind: Bool.self,
127-
usage: "Display the currently stored prefix",
219+
usage: "Sets the mode to BRANCH_PARSE. Requires a validator argument",
128220
completion: nil
129221
)
130222
}
131223

132-
func buildNewEntryArgument(parser: ArgumentParser) -> PositionalArgument<[String]> {
224+
func buildUserEntryArgument(parser: ArgumentParser) -> PositionalArgument<[String]> {
133225
return parser.add(
134-
positional: "NewEntry",
226+
positional: "UserEntry",
135227
kind: [String].self,
136228
optional: true,
137229
usage: nil,

Sources/CommitPrefix/CPDebugPrint.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ private let isDebugMode = false
3535
/// A Debug Printer that only prints in debug mode
3636
public func cpDebugPrint(_ value: Any, file: String = #file, line: Int = #line, function: String = #function) {
3737
guard isDebugMode else { return }
38-
print("/n", "********** Commit Prefix Debug **********")
38+
print("********** Commit Prefix Debug **********")
3939
print("File: \(file)")
4040
print("Line: \(line)")
4141
print("Function: \(function)")
4242
print("value: ", value)
43-
print("*****************************************", "/n")
43+
print("*****************************************")
4444
}

Sources/CommitPrefix/CPError.swift

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ enum CPError: Error {
3636
case fileReadWriteError
3737
case directoryNotFound(name: String, path: String)
3838
case hookReadWriteError
39-
case expectedYesOrNo
39+
case branchValidatorFormatError
4040

4141
var message: String {
4242
switch self {
@@ -47,7 +47,7 @@ enum CPError: Error {
4747
case .emptyEntry:
4848
return "Your entry is empty."
4949
case .multipleArguments:
50-
return "Multiple arguments entered. Only one at a time is supported."
50+
return "Too many arguments entered. Only two at a time is supported."
5151
case .notAGitRepo(currentLocation: let location):
5252
return "Not in a git repo or at the root of one: \(location)"
5353
case .fileReadWriteError:
@@ -56,8 +56,35 @@ enum CPError: Error {
5656
return "Directory named \(name) was not found at \(path)"
5757
case .hookReadWriteError:
5858
return "An error occured while reading or writing to the commit-msg hook"
59+
case .branchValidatorFormatError:
60+
return "The branch validator must be at least two characters long "
61+
+ "and contain no numbers or spaces"
62+
}
63+
64+
}
65+
66+
}
67+
68+
enum CPTermination: Error {
69+
70+
case overwriteCancelled
71+
case expectedYesOrNo
72+
case branchValidatorNotPresent
73+
case invalidBranchPrefix(validator: String)
74+
75+
var message: String {
76+
switch self {
77+
case .overwriteCancelled:
78+
return "Overwrite is cancelled"
5979
case .expectedYesOrNo:
60-
return "expected y or n. The transaction has been cancelled."
80+
return "Expected y or n. The transaction has been cancelled."
81+
case .branchValidatorNotPresent:
82+
return "Attempting to provide a branch prefix without a branch validator"
83+
case .invalidBranchPrefix(validator: let validator):
84+
return """
85+
Your branch does not begin with \(validator) and is invalid.
86+
Either change your branch name or use commitPrefix in non-branch mode.
87+
"""
6188
}
6289

6390
}

0 commit comments

Comments
 (0)