This is an automated email from the ASF dual-hosted git repository.

dongjoon pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/spark-connect-swift.git


The following commit(s) were added to refs/heads/main by this push:
     new 69a4ac4  [SPARK-51825] Add `SparkFileUtils`
69a4ac4 is described below

commit 69a4ac450cd12d482cd0056f36ac110617ea6df1
Author: Dongjoon Hyun <dongj...@apache.org>
AuthorDate: Fri Apr 18 13:18:16 2025 +0900

    [SPARK-51825] Add `SparkFileUtils`
    
    ### What changes were proposed in this pull request?
    
    This PR aims to add `SparkFileUtils` like
    - 
https://github.com/apache/spark/blob/master/common/utils/src/main/scala/org/apache/spark/util/SparkFileUtils.scala
    
    ### Why are the changes needed?
    
    This is required to add more features like `addArtifact`.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, this is a new addition.
    
    ### How was this patch tested?
    
    Pass the CIs.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No.
    
    Closes #64 from dongjoon-hyun/SPARK-51825.
    
    Authored-by: Dongjoon Hyun <dongj...@apache.org>
    Signed-off-by: Dongjoon Hyun <dongj...@apache.org>
---
 Sources/SparkConnect/SparkFileUtils.swift         | 111 ++++++++++++++++++++++
 Tests/SparkConnectTests/SparkFileUtilsTests.swift |  69 ++++++++++++++
 2 files changed, 180 insertions(+)

diff --git a/Sources/SparkConnect/SparkFileUtils.swift 
b/Sources/SparkConnect/SparkFileUtils.swift
new file mode 100644
index 0000000..5cfd504
--- /dev/null
+++ b/Sources/SparkConnect/SparkFileUtils.swift
@@ -0,0 +1,111 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+import Foundation
+
+/// Utility functions like `org.apache.spark.util.SparkFileUtils`.
+public enum SparkFileUtils {
+
+  /// Return a well-formed URL for the file described by a user input string.
+  ///
+  /// If the supplied path does not contain a scheme, or is a relative path, 
it will be
+  /// converted into an absolute path with a file:// scheme.
+  ///
+  /// - Parameter path: A path string.
+  /// - Returns: An URL
+  static func resolveURL(_ path: String) -> URL? {
+    if let url = URL(string: path) {
+      if url.scheme != nil {
+        return url.absoluteURL
+      }
+
+      // make sure to handle if the path has a fragment (applies to yarn
+      // distributed cache)
+      if let fragment = url.fragment {
+        var components = URLComponents()
+        components.scheme = "file"
+        components.path = (path as NSString).expandingTildeInPath
+        components.fragment = fragment
+        return components.url?.absoluteURL
+      }
+    }
+    return URL(fileURLWithPath: (path as 
NSString).expandingTildeInPath).absoluteURL
+  }
+
+  /// Lists files recursively.
+  /// - Parameter directory: <#directory description#>
+  /// - Returns: <#description#>
+  static func recursiveList(directory: URL) -> [URL] {
+    let fileManager = FileManager.default
+    var results: [URL] = []
+    if let enumerator = fileManager.enumerator(at: directory, 
includingPropertiesForKeys: nil) {
+      for case let fileURL as URL in enumerator {
+        results.append(fileURL)
+      }
+    }
+    return results
+  }
+
+  /// Create a directory given the abstract pathname
+  /// - Parameter url: An URL location.
+  /// - Returns: Return true if the directory is successfully created; 
otherwise, return false.
+  static func createDirectory(at url: URL) -> Bool {
+    let fileManager = FileManager.default
+    do {
+      try fileManager.createDirectory(at: url, withIntermediateDirectories: 
true)
+      var isDir: ObjCBool = false
+      let exists = fileManager.fileExists(atPath: url.path, isDirectory: 
&isDir)
+      return exists && isDir.boolValue
+    } catch {
+      print("Failed to create directory: \(url.path), error: \(error)")
+      return false
+    }
+  }
+
+  /// Create a temporary directory inside the given parent directory.
+  /// - Parameters:
+  ///   - root: A parent directory.
+  ///   - namePrefix: A prefix for a new directory name.
+  /// - Returns: An URL for the created directory
+  static func createDirectory(root: String, namePrefix: String = "spark") -> 
URL {
+    let tempDir = URL(fileURLWithPath: root).appendingPathComponent(
+      "\(namePrefix)-\(UUID().uuidString)")
+    _ = createDirectory(at: tempDir)
+    return tempDir
+  }
+
+  /// Create a new temporary directory prefixed with `spark` inside 
``NSTemporaryDirectory``.
+  /// - Returns: An URL for the created directory
+  static func createTempDir() -> URL {
+    let dir = createDirectory(root: NSTemporaryDirectory(), namePrefix: 
"spark")
+
+    return dir
+  }
+
+  /// Delete a file or directory and its contents recursively.
+  /// Throws an exception if deletion is unsuccessful.
+  /// - Parameter url: An URL location.
+  static func deleteRecursively(_ url: URL) throws {
+    let fileManager = FileManager.default
+    if fileManager.fileExists(atPath: url.path) {
+      try fileManager.removeItem(at: url)
+    } else {
+      throw SparkConnectError.InvalidArgumentException
+    }
+  }
+}
diff --git a/Tests/SparkConnectTests/SparkFileUtilsTests.swift 
b/Tests/SparkConnectTests/SparkFileUtilsTests.swift
new file mode 100644
index 0000000..642b6ee
--- /dev/null
+++ b/Tests/SparkConnectTests/SparkFileUtilsTests.swift
@@ -0,0 +1,69 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+import Foundation
+import Testing
+
+@testable import SparkConnect
+
+/// A test suite for `SparkFileUtils`
+struct SparkFileUtilsTests {
+  let fm = FileManager.default
+
+  @Test
+  func resolveURI() async throws {
+    let fileNameURL = SparkFileUtils.resolveURL("jar1")
+    #expect(fileNameURL!.absoluteString == 
"file://\(fm.currentDirectoryPath)/jar1")
+
+    let homeUrl = SparkFileUtils.resolveURL("~/jar1")
+    #expect(homeUrl!.absoluteString == 
"\(fm.homeDirectoryForCurrentUser.absoluteString)jar1")
+
+    let absolutePath = SparkFileUtils.resolveURL("file:/jar1")
+    #expect(absolutePath!.absoluteString == "file:/jar1")
+
+    let hdfsPath = SparkFileUtils.resolveURL("hdfs:/root/spark.jar")
+    #expect(hdfsPath!.absoluteString == "hdfs:/root/spark.jar")
+
+    let s3aPath = SparkFileUtils.resolveURL("s3a:/bucket/spark.jar")
+    #expect(s3aPath!.absoluteString == "s3a:/bucket/spark.jar")
+  }
+
+  @Test
+  func directory() async throws {
+    // This tests three functions.
+    // createTempDir -> createDirectory(root: String, namePrefix: String = 
"spark")
+    // -> createDirectory(at: URL)
+    let dir = SparkFileUtils.createTempDir()
+
+    var isDir: ObjCBool = false
+    let exists = fm.fileExists(atPath: dir.path(), isDirectory: &isDir)
+    #expect(exists && isDir.boolValue)
+
+    #expect(SparkFileUtils.recursiveList(directory: dir).isEmpty)
+
+    let emptyData = Data()
+    try emptyData.write(to: URL(string: dir.absoluteString + "/1")!)
+
+    #expect(SparkFileUtils.recursiveList(directory: dir).count == 1)
+
+    try SparkFileUtils.deleteRecursively(dir)
+
+    #expect(!fm.fileExists(atPath: dir.path(), isDirectory: &isDir))
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@spark.apache.org
For additional commands, e-mail: commits-h...@spark.apache.org

Reply via email to