diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 0867a06..b855b63 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -13,6 +13,9 @@ CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */; }; CA735109257BF96D00EA9CF8 /* AttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA735108257BF96D00EA9CF8 /* AttributedText.swift */; }; CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA73510C257BFCEF00EA9CF8 /* NSAttributedString+.swift */; }; + CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */ = {isa = PBXBuildFile; fileRef = CA9FF83E2594FBC000E47BAF /* Licenses.rtf */; }; + CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; }; + CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; }; CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */ = {isa = PBXBuildFile; productRef = CAA1CB2C255A5262003FD669 /* AppleAPI */; }; CAA1CB35255A5AD5003FD669 /* SignInCredentialsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */; }; CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */; }; @@ -41,11 +44,12 @@ CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */ = {isa = PBXBuildFile; productRef = CABFA9FC2592F13300380FEE /* LegibleError */; }; CABFAA2C2592FBFC00380FEE /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA2A2592FBFC00380FEE /* SettingsView.swift */; }; CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA2B2592FBFC00380FEE /* Configure.swift */; }; + CABFAA432593104F00380FEE /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA422593104F00380FEE /* AboutView.swift */; }; + CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */; }; CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD2E7A12449574E00113D76 /* XcodesApp.swift */; }; CAD2E7A42449574E00113D76 /* XcodeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD2E7A32449574E00113D76 /* XcodeListView.swift */; }; CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CAD2E7A52449575000113D76 /* Assets.xcassets */; }; CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CAD2E7A82449575000113D76 /* Preview Assets.xcassets */; }; - CAD2E7AC2449575000113D76 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CAD2E7AA2449575000113D76 /* Main.storyboard */; }; CAD2E7B82449575100113D76 /* XcodesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD2E7B72449575100113D76 /* XcodesTests.swift */; }; /* End PBXBuildFile section */ @@ -70,6 +74,10 @@ CA8FB5F8256E0F9400469DA5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; CA8FB61C256E115700469DA5 /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; }; CA8FB64D256E17B100469DA5 /* XcodesTest.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = XcodesTest.entitlements; sourceTree = ""; }; + CA9FF8242594F10700E47BAF /* AcknowledgementsGenerator */ = {isa = PBXFileReference; lastKnownFileType = folder; name = AcknowledgementsGenerator; path = Xcodes/AcknowledgementsGenerator; sourceTree = ""; }; + CA9FF83E2594FBC000E47BAF /* Licenses.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Licenses.rtf; sourceTree = ""; }; + CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollingTextView.swift; sourceTree = ""; }; + CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsView.swift; sourceTree = ""; }; CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInCredentialsView.swift; sourceTree = ""; }; CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignIn2FAView.swift; sourceTree = ""; }; CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSMSView.swift; sourceTree = ""; }; @@ -94,12 +102,13 @@ CABFA9D42592EF6300380FEE /* DECISIONS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DECISIONS.md; sourceTree = ""; }; CABFAA2A2592FBFC00380FEE /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingsView.swift; path = Xcodes/SettingsView.swift; sourceTree = SOURCE_ROOT; }; CABFAA2B2592FBFC00380FEE /* Configure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Configure.swift; path = Xcodes/Backend/Configure.swift; sourceTree = SOURCE_ROOT; }; + CABFAA422593104F00380FEE /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+InfoPlistValues.swift"; sourceTree = ""; }; CAD2E79E2449574E00113D76 /* Xcodes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Xcodes.app; sourceTree = BUILT_PRODUCTS_DIR; }; CAD2E7A12449574E00113D76 /* XcodesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodesApp.swift; sourceTree = ""; }; CAD2E7A32449574E00113D76 /* XcodeListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListView.swift; sourceTree = ""; }; CAD2E7A52449575000113D76 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CAD2E7A82449575000113D76 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - CAD2E7AB2449575000113D76 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CAD2E7AD2449575000113D76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CAD2E7AE2449575000113D76 /* Xcodes.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Xcodes.entitlements; sourceTree = ""; }; CAD2E7B32449575100113D76 /* XcodesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XcodesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -140,6 +149,16 @@ name = Frameworks; sourceTree = ""; }; + CA9FF8552595082000E47BAF /* About */ = { + isa = PBXGroup; + children = ( + CABFAA422593104F00380FEE /* AboutView.swift */, + CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */, + CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */, + ); + path = About; + sourceTree = ""; + }; CAA1CB50255A5D16003FD669 /* SignIn */ = { isa = PBXGroup; children = ( @@ -169,6 +188,7 @@ children = ( CA378F982466567600A58CE0 /* AppState.swift */, CABFAA2B2592FBFC00380FEE /* Configure.swift */, + CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */, CABFA9BA2592EEEA00380FEE /* DateFormatter+.swift */, CABFA9B22592EEEA00380FEE /* Entry+.swift */, CABFA9A92592EEE900380FEE /* Environment.swift */, @@ -190,6 +210,7 @@ CABFAA1A2592F7D900380FEE /* Frontend */ = { isa = PBXGroup; children = ( + CA9FF8552595082000E47BAF /* About */, CAA1CB50255A5D16003FD669 /* SignIn */, CABFAA142592F73000380FEE /* XcodeList */, CABFAA2A2592FBFC00380FEE /* SettingsView.swift */, @@ -201,8 +222,8 @@ isa = PBXGroup; children = ( CAD2E7A52449575000113D76 /* Assets.xcassets */, - CAD2E7AA2449575000113D76 /* Main.storyboard */, CAD2E7AD2449575000113D76 /* Info.plist */, + CA9FF83E2594FBC000E47BAF /* Licenses.rtf */, CAD2E7AE2449575000113D76 /* Xcodes.entitlements */, CA8FB64D256E17B100469DA5 /* XcodesTest.entitlements */, ); @@ -218,6 +239,7 @@ CABFA9A32592ED5700380FEE /* Apple.paw */, CABFA9A12592EAFB00380FEE /* LICENSE */, CA8FB61C256E115700469DA5 /* .github */, + CA9FF8242594F10700E47BAF /* AcknowledgementsGenerator */, CA538A0C255A4F1A00E64DD7 /* AppleAPI */, CAD2E7A02449574E00113D76 /* Xcodes */, CAD2E7B62449575100113D76 /* XcodesTests */, @@ -273,6 +295,7 @@ buildPhases = ( CAD2E79A2449574E00113D76 /* Sources */, CAD2E79B2449574E00113D76 /* Frameworks */, + CA9FF8292594F33200E47BAF /* Generate Acknowledgements */, CAD2E79C2449574E00113D76 /* Resources */, ); buildRules = ( @@ -364,8 +387,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - CAD2E7AC2449575000113D76 /* Main.storyboard in Resources */, CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */, + CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */, CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -379,12 +402,34 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + CA9FF8292594F33200E47BAF /* Generate Acknowledgements */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Generate Acknowledgements"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"${SRCROOT}/Xcodes/AcknowledgementsGenerator\"\nxcrun -sdk macosx swift run AcknowledgementsGenerator \\\n -p \"${SRCROOT}/Xcodes.xcodeproj\" \\\n -o \"${SRCROOT}/Xcodes/Resources/Licenses.rtf\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ CAD2E79A2449574E00113D76 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CA735109257BF96D00EA9CF8 /* AttributedText.swift in Sources */, + CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */, CABFA9CA2592EEEA00380FEE /* XcodeList.swift in Sources */, CA44901F2463AD34003D8213 /* Tag.swift in Sources */, CABFA9BF2592EEEA00380FEE /* URLSession+Promise.swift in Sources */, @@ -396,7 +441,9 @@ CAA1CB45255A5B60003FD669 /* SignIn2FAView.swift in Sources */, CABFA9C52592EEEA00380FEE /* FileManager+.swift in Sources */, CABFA9CD2592EEEA00380FEE /* Foundation.swift in Sources */, + CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */, CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */, + CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */, CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */, CAA1CB49255A5C97003FD669 /* SignInSMSView.swift in Sources */, CAA1CB35255A5AD5003FD669 /* SignInCredentialsView.swift in Sources */, @@ -408,6 +455,7 @@ CABFA9C72592EEEA00380FEE /* Entry+.swift in Sources */, CABFAA2C2592FBFC00380FEE /* SettingsView.swift in Sources */, CABFA9C92592EEEA00380FEE /* URLRequest+Apple.swift in Sources */, + CABFAA432593104F00380FEE /* AboutView.swift in Sources */, CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */, CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */, CA5D781E257365D6008EDE9D /* PinCodeTextView.swift in Sources */, @@ -433,17 +481,6 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - CAD2E7AA2449575000113D76 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - CAD2E7AB2449575000113D76 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ CA8FB635256E154800469DA5 /* Test */ = { isa = XCBuildConfiguration; @@ -509,7 +546,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Xcodes/ResourcesXcodesTest.entitlements; + CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/XcodesTest.entitlements; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; diff --git a/Xcodes/AcknowledgementsGenerator/.gitignore b/Xcodes/AcknowledgementsGenerator/.gitignore new file mode 100644 index 0000000..95c4320 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/Xcodes/AcknowledgementsGenerator/LICENSE b/Xcodes/AcknowledgementsGenerator/LICENSE new file mode 100644 index 0000000..3736aa2 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019 MacPaw +Copyright (c) 2020 Robots and Pencils + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Xcodes/AcknowledgementsGenerator/Package.swift b/Xcodes/AcknowledgementsGenerator/Package.swift new file mode 100644 index 0000000..c41837e --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version:5.3 + +import PackageDescription + +let package = Package( + name: "AcknowledgementsGenerator", + platforms: [.macOS(.v11)], + products: [ + .executable( + name: "AcknowledgementsGenerator", + targets: ["AcknowledgementsGenerator"] + ), + ], + dependencies: [ + ], + targets: [ + .target( + name: "AcknowledgementsGenerator", + dependencies: [] + ), + ] +) diff --git a/Xcodes/AcknowledgementsGenerator/README.md b/Xcodes/AcknowledgementsGenerator/README.md new file mode 100644 index 0000000..ac85430 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/README.md @@ -0,0 +1,5 @@ +# AcknowledgementsGenerator + +Scans an Xcode project's checked-out SPM packages for license files, then combines them into a single RTF file. + +Based on https://github.com/MacPaw/spm-licenses. diff --git a/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/CollectionExtensions.swift b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/CollectionExtensions.swift new file mode 100644 index 0000000..62c1af3 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/CollectionExtensions.swift @@ -0,0 +1,17 @@ +// +// CollectionExtensions.swift +// spm-licenses +// +// Created by Sergii Kryvoblotskyi on 11/11/19. +// Copyright © 2019 MacPaw. All rights reserved. +// + +import Foundation + +public extension Collection { + + /// Returns the element at the specified index iff it is within bounds, otherwise nil. + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/StringExtensions.swift b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/StringExtensions.swift new file mode 100644 index 0000000..aed8108 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/StringExtensions.swift @@ -0,0 +1,40 @@ +// +// StringExtensions.swift +// spm-licenses +// +// Created by Sergii Kryvoblotskyi on 11/11/19. +// Copyright © 2019 MacPaw. All rights reserved. +// + +import Foundation + +public extension String { + + var nsString: NSString { + (self as NSString) + } + + var pathExtension: String { + return nsString.pathExtension + } + + var lastPathComponent: String { + return nsString.lastPathComponent + } + + var deletingLastPathComponent: String { + return nsString.deletingLastPathComponent + } + + var stringByDeletingPathExtension: String { + return nsString.deletingPathExtension + } + + var expandingTildeInPath: String { + return nsString.expandingTildeInPath + } + + func appendingPathComponent(_ component: String) -> String { + return nsString.appendingPathComponent(component) + } +} diff --git a/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Tools/Xcode.swift b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Tools/Xcode.swift new file mode 100644 index 0000000..8710931 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Tools/Xcode.swift @@ -0,0 +1,74 @@ +// +// Xcode.swift +// spm-licenses +// +// Created by Sergii Kryvoblotskyi on 11/11/19. +// Copyright © 2019 MacPaw. All rights reserved. +// + +import Foundation + +struct Xcode { + + static var derivedDataURL: URL { + if let overridenPath = readOverridenDerivedDataPath() { + return URL(fileURLWithPath: overridenPath.expandingTildeInPath) + } + let defaultPath = "~/Library/Developer/Xcode/DerivedData/".expandingTildeInPath + return URL(fileURLWithPath: defaultPath) + } +} + +//defaults read com.apple.dt.Xcode.plist IDECustomDerivedDataLocation +//If the line returns +// +//The domain/default pair of (com.apple.dt.Xcode.plist, IDECustomDerivedDataLocation) does not exist +//it's the default path ~/Library/Developer/Xcode/DerivedData/ otherwise the custom path. +private extension Xcode { + + static func readOverridenDerivedDataPath() -> String? { + let task = Process() + let pipe = Pipe() + task.executableURL = URL(fileURLWithPath: "/usr/bin/defaults") + task.arguments = ["read","com.apple.dt.Xcode.plist", "IDECustomDerivedDataLocation"] + task.standardOutput = pipe + try? task.run() + let handle = pipe.fileHandleForReading + let data = handle.readDataToEndOfFile() + let path = String(data: data, encoding: String.Encoding.utf8) + return (path?.isEmpty ?? true) ? nil : path + } +} + +extension Xcode { + + struct Project { + let url: URL + let info: [String: Any] + var workspacePath: String? { + return info["WorkspacePath"] as? String + } + } +} + +extension Xcode.Project { + + struct License { + + let url: URL + let name: String + } +} + +extension Xcode.Project.License { + + func makeRepresentation() throws -> [String: String] { + let data = try Data(contentsOf: url) + let text = String(data: data, encoding: .utf8) ?? "" + return [ + "Title": name, + "Type": "PSGroupSpecifier", + "FooterText": text + ] + } +} diff --git a/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/main.swift b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/main.swift new file mode 100644 index 0000000..9472720 --- /dev/null +++ b/Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/main.swift @@ -0,0 +1,100 @@ +// +// main.swift +// spm-licenses +// +// Created by Sergii Kryvoblotskyi on 11/11/19. +// Copyright © 2019 MacPaw. All rights reserved. +// + +import Foundation +import AppKit + +let arguments = CommandLine.arguments +guard let projectIndex = arguments.firstIndex(of: "-p"), let projectPath = arguments[safe: projectIndex + 1] else { + print("Project path is missing. Specify -p.") + exit(EXIT_FAILURE) +} + +guard let outputIndex = arguments.firstIndex(of: "-o"), let outputPath = arguments[safe: outputIndex + 1] else { + print("Output path is missing. Specify -o.") + exit(EXIT_FAILURE) +} + +let fileManager = FileManager.default + +let projectURL = URL(fileURLWithPath: projectPath.expandingTildeInPath) +if !fileManager.fileExists(atPath: projectURL.path) { + print("xcodeproj not found at \(projectURL)") + exit(EXIT_FAILURE) +} + +let packageURL = projectURL.appendingPathComponent("project.xcworkspace/xcshareddata/swiftpm/Package.resolved") +if !fileManager.fileExists(atPath: packageURL.path) { + print("Package.resolved not found at \(packageURL)") + exit(EXIT_FAILURE) +} + +let packageData = try Data(contentsOf: packageURL) +let packageInfo = try JSONSerialization.jsonObject(with: packageData, options: .allowFragments) + +guard let package = packageInfo as? [String: Any] else { + print("Invalid package format") + exit(EXIT_FAILURE) +} + +guard let object = package["object"] as? [String: Any] else { + print("Invalid obejct format") + exit(EXIT_FAILURE) +} + +guard let pins = object["pins"] as? [[String: Any]] else { + print("Invalid pins format") + exit(EXIT_FAILURE) +} + +let projectsURL = Xcode.derivedDataURL +func projectsInfo(at url: URL) throws -> [Xcode.Project] { + try fileManager + .contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) + .map { $0.appendingPathComponent("info.plist") } + .compactMap { + guard let info = NSDictionary(contentsOf: $0) as? [String: Any] else { return nil } + return Xcode.Project(url: $0, info: info) + } +} +let projects = try projectsInfo(at: projectsURL) + +// Despite the naming, if the project only has an xcodeproj and not an xcworkspace, the WorkspacePath value will be the path to the xcodeproj +guard let currentProject = projects.first(where: ({ $0.workspacePath == projectPath.expandingTildeInPath })) else { + print("Derived data missing for project") + exit(EXIT_FAILURE) +} + +let checkouts = currentProject.url.deletingLastPathComponent().appendingPathComponent("SourcePackages/checkouts") +let checkedDependencies = try fileManager.contentsOfDirectory(at: checkouts, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) + +let licences: [Xcode.Project.License] = checkedDependencies.compactMap { + let supportedFilenames = ["LICENSE", "LICENSE.txt", "LICENSE.md"] + for filename in supportedFilenames { + let licenseURL = $0.appendingPathComponent(filename) + if fileManager.fileExists(atPath: licenseURL.path) { + return Xcode.Project.License(url: licenseURL, name: $0.lastPathComponent) + } + } + return nil +} + +let acknowledgementsAttributedString = NSMutableAttributedString() +for licence in licences { + acknowledgementsAttributedString.append(NSAttributedString(string: licence.name + "\n\n", attributes: [.font: NSFont.preferredFont(forTextStyle: .title2)])) + let licenseContents = try String(contentsOf: licence.url) + acknowledgementsAttributedString.append(NSAttributedString(string: licenseContents + "\n\n", attributes: [.font: NSFont.preferredFont(forTextStyle: .body)])) +} + +guard let data = acknowledgementsAttributedString.rtf(from: NSRange(location: 0, length: acknowledgementsAttributedString.length), documentAttributes: [:]) else { + print("Failed to create RTF data") + exit(EXIT_FAILURE) +} +try data.write(to: URL(fileURLWithPath: outputPath.expandingTildeInPath)) + +print("Licenses have been saved to \(outputPath)") diff --git a/Xcodes/Backend/Bundle+InfoPlistValues.swift b/Xcodes/Backend/Bundle+InfoPlistValues.swift new file mode 100644 index 0000000..0838bc5 --- /dev/null +++ b/Xcodes/Backend/Bundle+InfoPlistValues.swift @@ -0,0 +1,15 @@ +import Foundation + +extension Bundle { + var bundleName: String? { + infoDictionary?["CFBundleName"] as? String + } + + var shortVersion: String? { + infoDictionary?["CFBundleShortVersionString"] as? String + } + + var version: String? { + infoDictionary?["CFBundleVersion"] as? String + } +} diff --git a/Xcodes/Frontend/About/AboutView.swift b/Xcodes/Frontend/About/AboutView.swift new file mode 100644 index 0000000..e4a1136 --- /dev/null +++ b/Xcodes/Frontend/About/AboutView.swift @@ -0,0 +1,45 @@ +import SwiftUI + +struct AboutView: View { + let showAcknowledgementsWindow: () -> Void + + var body: some View { + HStack { + Image(nsImage: NSApp.applicationIconImage) + + VStack(alignment: .leading) { + Text(Bundle.main.bundleName!) + .font(.largeTitle) + + Text("Version \(Bundle.main.shortVersion!) (\(Bundle.main.version!))") + + Color.clear + .frame(width: 300, height: 16) + + HStack(spacing: 32) { + Button(action: { + NSWorkspace.shared.open(URL(string: "https://github.com/RobotsAndPencils/XcodesApp/")!) + }) { + Label("GitHub Repo", systemImage: "link") + } + .buttonStyle(LinkButtonStyle()) + + Button(action: showAcknowledgementsWindow) { + Label("Acknowledgements", systemImage: "doc") + } + .buttonStyle(LinkButtonStyle()) + } + + Text("Copyright © 2020 Robots and Pencils") + .font(.footnote) + } + } + .padding() + } +} + +struct AboutView_Previews: PreviewProvider { + static var previews: some View { + AboutView(showAcknowledgementsWindow: {}) + } +} diff --git a/Xcodes/Frontend/About/AcknowledgementsView.swift b/Xcodes/Frontend/About/AcknowledgementsView.swift new file mode 100644 index 0000000..889deb8 --- /dev/null +++ b/Xcodes/Frontend/About/AcknowledgementsView.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct AcknowledgmentsView: View { + var body: some View { + ScrollingTextView( + attributedString: NSAttributedString( + rtf: try! Data(contentsOf: Bundle.main.url(forResource: "Licenses", withExtension: "rtf")!), + documentAttributes: nil + )! + ) + .frame(minWidth: 500, minHeight: 500) + } +} + + +struct AcknowledgementsView_Previews: PreviewProvider { + static var previews: some View { + AcknowledgmentsView() + } +} diff --git a/Xcodes/Frontend/About/ScrollingTextView.swift b/Xcodes/Frontend/About/ScrollingTextView.swift new file mode 100644 index 0000000..97d053c --- /dev/null +++ b/Xcodes/Frontend/About/ScrollingTextView.swift @@ -0,0 +1,24 @@ +import SwiftUI + +struct ScrollingTextView: NSViewRepresentable { + typealias NSViewType = NSScrollView + + let attributedString: NSAttributedString + + func makeNSView(context: Context) -> NSViewType { + let view = NSTextView.scrollableTextView() + let textView = view.documentView as? NSTextView + textView?.isEditable = false + return view + } + + func updateNSView(_ nsView: NSViewType, context: Context) { + (nsView.documentView as? NSTextView)?.textStorage?.setAttributedString(attributedString) + } +} + +struct ScrollingTextView_Previews: PreviewProvider { + static var previews: some View { + ScrollingTextView(attributedString: NSAttributedString(string: "Some sample text")) + } +} diff --git a/Xcodes/Resources/Base.lproj/Main.storyboard b/Xcodes/Resources/Base.lproj/Main.storyboard deleted file mode 100644 index 962e04f..0000000 --- a/Xcodes/Resources/Base.lproj/Main.storyboard +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Xcodes/Resources/Licenses.rtf b/Xcodes/Resources/Licenses.rtf new file mode 100644 index 0000000..f9dca46 --- /dev/null +++ b/Xcodes/Resources/Licenses.rtf @@ -0,0 +1,395 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2577 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 + +\f0\fs34 \cf0 PromiseKit\ +\ + +\fs26 Copyright 2016-present, Max Howell; mxcl@me.com\ +\ +Permission is hereby granted, free of charge, to any person obtaining a\ +copy of this software and associated documentation files (the\ +"Software"), to deal in the Software without restriction, including\ +without limitation the rights to use, copy, modify, merge, publish,\ +distribute, sublicense, and/or sell copies of the Software, and to\ +permit persons to whom the Software is furnished to do so, subject to\ +the following conditions:\ +\ +The above copyright notice and this permission notice shall be included\ +in all copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS\ +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\ +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\ +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\ +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\ +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\ +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ +\ +\ + +\fs34 SwiftSoup\ +\ + +\fs26 MIT License\ +\ +Copyright (c) 2016 Nabil Chatbi\ +\ +Permission is hereby granted, free of charge, to any person obtaining a copy\ +of this software and associated documentation files (the "Software"), to deal\ +in the Software without restriction, including without limitation the rights\ +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ +copies of the Software, and to permit persons to whom the Software is\ +furnished to do so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included in all\ +copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ +SOFTWARE.\ +\ +\ + +\fs34 Foundation\ +\ + +\fs26 Copyright 2018-present, Max Howell; mxcl@me.com\ +\ +Permission is hereby granted, free of charge, to any person obtaining a\ +copy of this software and associated documentation files (the\ +"Software"), to deal in the Software without restriction, including\ +without limitation the rights to use, copy, modify, merge, publish,\ +distribute, sublicense, and/or sell copies of the Software, and to\ +permit persons to whom the Software is furnished to do so, subject to\ +the following conditions:\ +\ +The above copyright notice and this permission notice shall be included\ +in all copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS\ +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\ +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\ +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\ +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\ +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\ +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ +\ +\ + +\fs34 Path.swift\ +\ + +\fs26 Unlicense (Public Domain)\ +============================\ +\ +This is free and unencumbered software released into the public domain.\ +\ +Anyone is free to copy, modify, publish, use, compile, sell, or\ +distribute this software, either in source code form or as a compiled\ +binary, for any purpose, commercial or non-commercial, and by any\ +means.\ +\ +In jurisdictions that recognize copyright laws, the author or authors\ +of this software dedicate any and all copyright interest in the\ +software to the public domain. We make this dedication for the benefit\ +of the public at large and to the detriment of our heirs and\ +successors. We intend this dedication to be an overt act of\ +relinquishment in perpetuity of all present and future rights to this\ +software under copyright law.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\ +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\ +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\ +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\ +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ +OTHER DEALINGS IN THE SOFTWARE.\ +\ +For more information, please refer to <>\ +\ +\ + +\fs34 Version\ +\ + +\fs26 Apache License\ + Version 2.0, January 2004\ + http://www.apache.org/licenses/\ +\ + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\ +\ + 1. Definitions.\ +\ + "License" shall mean the terms and conditions for use, reproduction,\ + and distribution as defined by Sections 1 through 9 of this document.\ +\ + "Licensor" shall mean the copyright owner or entity authorized by\ + the copyright owner that is granting the License.\ +\ + "Legal Entity" shall mean the union of the acting entity and all\ + other entities that control, are controlled by, or are under common\ + control with that entity. For the purposes of this definition,\ + "control" means (i) the power, direct or indirect, to cause the\ + direction or management of such entity, whether by contract or\ + otherwise, or (ii) ownership of fifty percent (50%) or more of the\ + outstanding shares, or (iii) beneficial ownership of such entity.\ +\ + "You" (or "Your") shall mean an individual or Legal Entity\ + exercising permissions granted by this License.\ +\ + "Source" form shall mean the preferred form for making modifications,\ + including but not limited to software source code, documentation\ + source, and configuration files.\ +\ + "Object" form shall mean any form resulting from mechanical\ + transformation or translation of a Source form, including but\ + not limited to compiled object code, generated documentation,\ + and conversions to other media types.\ +\ + "Work" shall mean the work of authorship, whether in Source or\ + Object form, made available under the License, as indicated by a\ + copyright notice that is included in or attached to the work\ + (an example is provided in the Appendix below).\ +\ + "Derivative Works" shall mean any work, whether in Source or Object\ + form, that is based on (or derived from) the Work and for which the\ + editorial revisions, annotations, elaborations, or other modifications\ + represent, as a whole, an original work of authorship. For the purposes\ + of this License, Derivative Works shall not include works that remain\ + separable from, or merely link (or bind by name) to the interfaces of,\ + the Work and Derivative Works thereof.\ +\ + "Contribution" shall mean any work of authorship, including\ + the original version of the Work and any modifications or additions\ + to that Work or Derivative Works thereof, that is intentionally\ + submitted to Licensor for inclusion in the Work by the copyright owner\ + or by an individual or Legal Entity authorized to submit on behalf of\ + the copyright owner. For the purposes of this definition, "submitted"\ + means any form of electronic, verbal, or written communication sent\ + to the Licensor or its representatives, including but not limited to\ + communication on electronic mailing lists, source code control systems,\ + and issue tracking systems that are managed by, or on behalf of, the\ + Licensor for the purpose of discussing and improving the Work, but\ + excluding communication that is conspicuously marked or otherwise\ + designated in writing by the copyright owner as "Not a Contribution."\ +\ + "Contributor" shall mean Licensor and any individual or Legal Entity\ + on behalf of whom a Contribution has been received by Licensor and\ + subsequently incorporated within the Work.\ +\ + 2. Grant of Copyright License. Subject to the terms and conditions of\ + this License, each Contributor hereby grants to You a perpetual,\ + worldwide, non-exclusive, no-charge, royalty-free, irrevocable\ + copyright license to reproduce, prepare Derivative Works of,\ + publicly display, publicly perform, sublicense, and distribute the\ + Work and such Derivative Works in Source or Object form.\ +\ + 3. Grant of Patent License. Subject to the terms and conditions of\ + this License, each Contributor hereby grants to You a perpetual,\ + worldwide, non-exclusive, no-charge, royalty-free, irrevocable\ + (except as stated in this section) patent license to make, have made,\ + use, offer to sell, sell, import, and otherwise transfer the Work,\ + where such license applies only to those patent claims licensable\ + by such Contributor that are necessarily infringed by their\ + Contribution(s) alone or by combination of their Contribution(s)\ + with the Work to which such Contribution(s) was submitted. If You\ + institute patent litigation against any entity (including a\ + cross-claim or counterclaim in a lawsuit) alleging that the Work\ + or a Contribution incorporated within the Work constitutes direct\ + or contributory patent infringement, then any patent licenses\ + granted to You under this License for that Work shall terminate\ + as of the date such litigation is filed.\ +\ + 4. Redistribution. You may reproduce and distribute copies of the\ + Work or Derivative Works thereof in any medium, with or without\ + modifications, and in Source or Object form, provided that You\ + meet the following conditions:\ +\ + (a) You must give any other recipients of the Work or\ + Derivative Works a copy of this License; and\ +\ + (b) You must cause any modified files to carry prominent notices\ + stating that You changed the files; and\ +\ + (c) You must retain, in the Source form of any Derivative Works\ + that You distribute, all copyright, patent, trademark, and\ + attribution notices from the Source form of the Work,\ + excluding those notices that do not pertain to any part of\ + the Derivative Works; and\ +\ + (d) If the Work includes a "NOTICE" text file as part of its\ + distribution, then any Derivative Works that You distribute must\ + include a readable copy of the attribution notices contained\ + within such NOTICE file, excluding those notices that do not\ + pertain to any part of the Derivative Works, in at least one\ + of the following places: within a NOTICE text file distributed\ + as part of the Derivative Works; within the Source form or\ + documentation, if provided along with the Derivative Works; or,\ + within a display generated by the Derivative Works, if and\ + wherever such third-party notices normally appear. The contents\ + of the NOTICE file are for informational purposes only and\ + do not modify the License. You may add Your own attribution\ + notices within Derivative Works that You distribute, alongside\ + or as an addendum to the NOTICE text from the Work, provided\ + that such additional attribution notices cannot be construed\ + as modifying the License.\ +\ + You may add Your own copyright statement to Your modifications and\ + may provide additional or different license terms and conditions\ + for use, reproduction, or distribution of Your modifications, or\ + for any such Derivative Works as a whole, provided Your use,\ + reproduction, and distribution of the Work otherwise complies with\ + the conditions stated in this License.\ +\ + 5. Submission of Contributions. Unless You explicitly state otherwise,\ + any Contribution intentionally submitted for inclusion in the Work\ + by You to the Licensor shall be under the terms and conditions of\ + this License, without any additional terms or conditions.\ + Notwithstanding the above, nothing herein shall supersede or modify\ + the terms of any separate license agreement you may have executed\ + with Licensor regarding such Contributions.\ +\ + 6. Trademarks. This License does not grant permission to use the trade\ + names, trademarks, service marks, or product names of the Licensor,\ + except as required for reasonable and customary use in describing the\ + origin of the Work and reproducing the content of the NOTICE file.\ +\ + 7. Disclaimer of Warranty. Unless required by applicable law or\ + agreed to in writing, Licensor provides the Work (and each\ + Contributor provides its Contributions) on an "AS IS" BASIS,\ + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\ + implied, including, without limitation, any warranties or conditions\ + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\ + PARTICULAR PURPOSE. You are solely responsible for determining the\ + appropriateness of using or redistributing the Work and assume any\ + risks associated with Your exercise of permissions under this License.\ +\ + 8. Limitation of Liability. In no event and under no legal theory,\ + whether in tort (including negligence), contract, or otherwise,\ + unless required by applicable law (such as deliberate and grossly\ + negligent acts) or agreed to in writing, shall any Contributor be\ + liable to You for damages, including any direct, indirect, special,\ + incidental, or consequential damages of any character arising as a\ + result of this License or out of the use or inability to use the\ + Work (including but not limited to damages for loss of goodwill,\ + work stoppage, computer failure or malfunction, or any and all\ + other commercial damages or losses), even if such Contributor\ + has been advised of the possibility of such damages.\ +\ + 9. Accepting Warranty or Additional Liability. While redistributing\ + the Work or Derivative Works thereof, You may choose to offer,\ + and charge a fee for, acceptance of support, warranty, indemnity,\ + or other liability obligations and/or rights consistent with this\ + License. However, in accepting such obligations, You may act only\ + on Your own behalf and on Your sole responsibility, not on behalf\ + of any other Contributor, and only if You agree to indemnify,\ + defend, and hold each Contributor harmless for any liability\ + incurred by, or claims asserted against, such Contributor by reason\ + of your accepting any such warranty or additional liability.\ +\ + END OF TERMS AND CONDITIONS\ +\ + APPENDIX: How to apply the Apache License to your work.\ +\ + To apply the Apache License to your work, attach the following\ + boilerplate notice, with the fields enclosed by brackets "[]"\ + replaced with your own identifying information. (Don't include\ + the brackets!) The text should be enclosed in the appropriate\ + comment syntax for the file format. We also recommend that a\ + file or class name and description of purpose be included on the\ + same "printed page" as the copyright notice for easier\ + identification within third-party archives.\ +\ + Copyright [yyyy] [name of copyright owner]\ +\ + Licensed 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.\ +\ +\ +\ +## Runtime Library Exception to the Apache 2.0 License: ##\ +\ +\ + As an exception, if you use this Software to compile your source code and\ + portions of this Software are embedded into the binary product as a result,\ + you may redistribute such product without providing attribution as would\ + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.\ +\ + +\fs34 KeychainAccess\ +\ + +\fs26 The MIT License (MIT)\ +\ +Copyright (c) 2014 kishikawa katsumi\ +\ +Permission is hereby granted, free of charge, to any person obtaining a copy\ +of this software and associated documentation files (the "Software"), to deal\ +in the Software without restriction, including without limitation the rights\ +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ +copies of the Software, and to permit persons to whom the Software is\ +furnished to do so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included in all\ +copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ +SOFTWARE.\ +\ +\ +\ + +\fs34 LegibleError\ +\ + +\fs26 Unlicense (Public Domain)\ +============================\ +\ +This is free and unencumbered software released into the public domain.\ +\ +Anyone is free to copy, modify, publish, use, compile, sell, or\ +distribute this software, either in source code form or as a compiled\ +binary, for any purpose, commercial or non-commercial, and by any\ +means.\ +\ +In jurisdictions that recognize copyright laws, the author or authors\ +of this software dedicate any and all copyright interest in the\ +software to the public domain. We make this dedication for the benefit\ +of the public at large and to the detriment of our heirs and\ +successors. We intend this dedication to be an overt act of\ +relinquishment in perpetuity of all present and future rights to this\ +software under copyright law.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\ +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\ +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\ +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\ +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ +OTHER DEALINGS IN THE SOFTWARE.\ +\ +For more information, please refer to <>\ +\ +\ +} \ No newline at end of file diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index 5f27dbc..88645fa 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -1,7 +1,9 @@ import SwiftUI +import AppKit @main struct XcodesApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate @StateObject private var appState = AppState() var body: some Scene { @@ -9,6 +11,13 @@ struct XcodesApp: App { XcodeListView() .environmentObject(appState) } + .commands { + CommandGroup(replacing: .appInfo) { + Button("About Xcodes") { + appDelegate.showAboutWindow() + } + } + } Settings { SettingsView() @@ -16,3 +25,41 @@ struct XcodesApp: App { } } } + +class AppDelegate: NSObject, NSApplicationDelegate { + private lazy var aboutWindow = configure(NSWindow( + contentRect: .zero, + styleMask: [.closable, .resizable, .miniaturizable, .titled], + backing: .buffered, + defer: false + )) { + $0.title = "About Xcodes" + $0.contentView = NSHostingView(rootView: AboutView(showAcknowledgementsWindow: showAcknowledgementsWindow)) + $0.isReleasedWhenClosed = false + } + + private let acknowledgementsWindow = configure(NSWindow( + contentRect: .zero, + styleMask: [.closable, .resizable, .miniaturizable, .titled], + backing: .buffered, + defer: false + )) { + $0.title = "Xcodes Acknowledgements" + $0.contentView = NSHostingView(rootView: AcknowledgmentsView()) + $0.isReleasedWhenClosed = false + } + + /// If we wanted to use only SwiftUI API to do this we could make a new WindowGroup and use openURL and handlesExternalEvents. + /// WindowGroup lets the user open more than one window right now, which is a little strange for an About window. + /// (It's also weird that the main Xcode list window can be opened more than once, there should only be one.) + /// To work around this, an AppDelegate holds onto a single instance of an NSWindow that is shown here. + func showAboutWindow() { + aboutWindow.center() + aboutWindow.makeKeyAndOrderFront(nil) + } + + func showAcknowledgementsWindow() { + acknowledgementsWindow.center() + acknowledgementsWindow.makeKeyAndOrderFront(nil) + } +}