Skip to content

Commit f754182

Browse files
committed
feat: Add GitObjectLayout strategy
- Implements GitObjectLayout for generating git-style directory paths from hashes - Adds unit tests for layout generation - Fixes redundant public modifier warning in HashAlgorithmProtocol
1 parent 6e10648 commit f754182

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

Sources/DesignAlgorithmsKit/Algorithms/Hashing/HashAlgorithmProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public extension HashAlgorithmProtocol {
3333
/// - Returns: Hash value as Data, or empty Data if UTF-8 conversion fails
3434
/// - Note: UTF-8 conversion failure returns empty Data, which will hash to a valid hash value.
3535
/// This path is testable by creating strings that fail UTF-8 conversion (rare but possible).
36-
public static func hash(string: String) -> Data {
36+
static func hash(string: String) -> Data {
3737
guard let data = string.data(using: .utf8) else {
3838
// UTF-8 conversion failed - return hash of empty data
3939
// This is a valid fallback that ensures we always return a hash
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// GitObjectLayout.swift
3+
// DesignAlgorithmsKit
4+
//
5+
// Created for DesignAlgorithmsKit
6+
//
7+
8+
import Foundation
9+
10+
/// Git-Style Directory Layout Strategy
11+
///
12+
/// Implements the directory layout strategy used by Git for loose objects,
13+
/// where a content hash is split into a directory (prefix) and filename (remainder).
14+
///
15+
/// Example:
16+
/// Hash: "a1b2c3d4..."
17+
/// Directory: "a1"
18+
/// Filename: "b2c3d4..."
19+
/// Path: "a1/b2c3d4..."
20+
public struct GitObjectLayout {
21+
22+
/// The length of the prefix used for the directory name
23+
public static let prefixLength = 2
24+
25+
/// Generates the layout components for a given hash.
26+
/// - Parameter hash: The hex string representation of the hash.
27+
/// - Returns: A tuple containing the directory, filename, and combined relative path.
28+
/// If the hash is too short (<= prefixLength), returns empty directory and the hash as filename.
29+
public static func layout(for hash: String) -> (directory: String, filename: String, path: String) {
30+
guard hash.count > Self.prefixLength else {
31+
return (directory: "", filename: hash, path: hash)
32+
}
33+
34+
let prefixIndex = hash.index(hash.startIndex, offsetBy: Self.prefixLength)
35+
let prefix = String(hash[..<prefixIndex])
36+
let suffix = String(hash[prefixIndex...])
37+
38+
// Ensure path separator is handled by caller or returned as standard relative path
39+
let path = "\(prefix)/\(suffix)"
40+
41+
return (directory: prefix, filename: suffix, path: path)
42+
}
43+
44+
/// Generates the relative path for a given hash.
45+
/// - Parameter hash: The hex string representation of the hash.
46+
/// - Returns: The relative path string (e.g., "ab/cdef...").
47+
public static func path(for hash: String) -> String {
48+
return layout(for: hash).path
49+
}
50+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import XCTest
2+
@testable import DesignAlgorithmsKit
3+
4+
final class GitObjectLayoutTests: XCTestCase {
5+
6+
func testLayoutGeneration() {
7+
let hash = "a1b2c3d4e5f6"
8+
let layout = GitObjectLayout.layout(for: hash)
9+
10+
XCTAssertEqual(layout.directory, "a1")
11+
XCTAssertEqual(layout.filename, "b2c3d4e5f6")
12+
XCTAssertEqual(layout.path, "a1/b2c3d4e5f6")
13+
}
14+
15+
func testShortHash() {
16+
let hash = "ab"
17+
let layout = GitObjectLayout.layout(for: hash)
18+
19+
// Expect behavior for short hashes (implementation specific: no split)
20+
XCTAssertEqual(layout.path, "ab")
21+
XCTAssertEqual(layout.directory, "")
22+
XCTAssertEqual(layout.filename, "ab")
23+
24+
let hash2 = "a"
25+
let layout2 = GitObjectLayout.layout(for: hash2)
26+
XCTAssertEqual(layout2.path, "a")
27+
XCTAssertEqual(layout2.directory, "")
28+
XCTAssertEqual(layout2.filename, "a")
29+
}
30+
31+
func testPathHelper() {
32+
let hash = "1234567890"
33+
XCTAssertEqual(GitObjectLayout.path(for: hash), "12/34567890")
34+
}
35+
36+
func testStandardGitHash() {
37+
// Example SHA-1 hash
38+
let hash = "5e80dc522e0327ba4944d180bbf261904e545805"
39+
let layout = GitObjectLayout.layout(for: hash)
40+
41+
XCTAssertEqual(layout.directory, "5e")
42+
XCTAssertEqual(layout.filename, "80dc522e0327ba4944d180bbf261904e545805")
43+
XCTAssertEqual(layout.path, "5e/80dc522e0327ba4944d180bbf261904e545805")
44+
}
45+
}

0 commit comments

Comments
 (0)