Rename to Kotej and add the app icon
"Cotejo" means nothing outside Spanish and is hard to pronounce elsewhere, so the project is now Kotej: same root, but it reads and sounds the same in English and Spanish. Renamed throughout — engine, targets, bundle ids and the URL scheme, which is now kotej://compare. Icon generated with Codex: two facing panels split by a seam, content lines on each side and one amber line marking a difference — the tool's job at a glance, still legible at 16pt. 34 tests still green; kotej:// verified end to end against the demo JARs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "idiom" : "mac", "scale" : "1x", "size" : "16x16", "filename" : "icon_16.png" },
|
||||
{ "idiom" : "mac", "scale" : "2x", "size" : "16x16", "filename" : "icon_32.png" },
|
||||
{ "idiom" : "mac", "scale" : "1x", "size" : "32x32", "filename" : "icon_32.png" },
|
||||
{ "idiom" : "mac", "scale" : "2x", "size" : "32x32", "filename" : "icon_64.png" },
|
||||
{ "idiom" : "mac", "scale" : "1x", "size" : "128x128", "filename" : "icon_128.png" },
|
||||
{ "idiom" : "mac", "scale" : "2x", "size" : "128x128", "filename" : "icon_256.png" },
|
||||
{ "idiom" : "mac", "scale" : "1x", "size" : "256x256", "filename" : "icon_256.png" },
|
||||
{ "idiom" : "mac", "scale" : "2x", "size" : "256x256", "filename" : "icon_512.png" },
|
||||
{ "idiom" : "mac", "scale" : "1x", "size" : "512x512", "filename" : "icon_512.png" },
|
||||
{ "idiom" : "mac", "scale" : "2x", "size" : "512x512", "filename" : "icon_1024.png" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 859 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1 @@
|
||||
{ "info" : { "author" : "xcode", "version" : 1 } }
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import CotejoEngine
|
||||
import KotejEngine
|
||||
|
||||
/// Drives one comparison: what's on each side, the resulting tree, and the
|
||||
/// options. Scanning runs off the main actor so big trees don't freeze the UI.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
import CotejoEngine
|
||||
import KotejEngine
|
||||
|
||||
struct ContentView: View {
|
||||
@Bindable var model: ComparisonModel
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SwiftUI
|
||||
import CotejoEngine
|
||||
import KotejEngine
|
||||
|
||||
/// A row ready to render: the filter is applied up front so the outline can use a
|
||||
/// plain key path for its children.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SwiftUI
|
||||
import CotejoEngine
|
||||
import KotejEngine
|
||||
|
||||
/// Side-by-side contents of the selected row. Text (and JSON/XML) is aligned line
|
||||
/// by line; binaries just report their verdict, since a hex view would be noise.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Cotejo</string>
|
||||
<string>Kotej</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -27,16 +27,16 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.alexandrev.cotejo</string>
|
||||
<string>com.alexandrev.kotej</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>cotejo</string>
|
||||
<string>kotej</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Cotejo</string>
|
||||
<string>Kotej</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import SwiftUI
|
||||
import CotejoEngine
|
||||
import KotejEngine
|
||||
|
||||
@main
|
||||
struct CotejoApp: App {
|
||||
struct KotejApp: App {
|
||||
@State private var tabs = TabsModel()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootView(tabs: tabs)
|
||||
.frame(minWidth: 900, minHeight: 560)
|
||||
// cotejo://compare?left=…&right=… — how the Finder extension asks
|
||||
// kotej://compare?left=…&right=… — how the Finder extension asks
|
||||
// for a comparison; it always lands in a tab.
|
||||
.onOpenURL { url in handle(url) }
|
||||
}
|
||||
@@ -34,7 +34,7 @@ struct CotejoApp: App {
|
||||
}
|
||||
|
||||
private func handle(_ url: URL) {
|
||||
guard url.scheme == "cotejo", url.host == "compare",
|
||||
guard url.scheme == "kotej", url.host == "compare",
|
||||
let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else { return }
|
||||
func path(_ name: String) -> URL? {
|
||||
items.first { $0.name == name }?.value.map { URL(fileURLWithPath: $0) }
|
||||
@@ -1,18 +1,18 @@
|
||||
import Cocoa
|
||||
import FinderSync
|
||||
|
||||
/// Adds Cotejo to Finder's contextual menu.
|
||||
/// Adds Kotej to Finder's contextual menu.
|
||||
///
|
||||
/// Two flows, matching how comparison tools are normally used:
|
||||
/// - select two items → "Comparar los 2 elementos"
|
||||
/// - select one item → "Seleccionar para comparar", then on another item
|
||||
/// "Comparar con <lo seleccionado>"
|
||||
///
|
||||
/// The app is asked to do the work through its `cotejo://` URL scheme, so the
|
||||
/// The app is asked to do the work through its `kotej://` URL scheme, so the
|
||||
/// extension stays tiny and holds no comparison logic.
|
||||
final class FinderMenu: FIFinderSync {
|
||||
/// Where the first pick is remembered between two right-clicks.
|
||||
private static let pendingKey = "cotejo.pendingLeft"
|
||||
private static let pendingKey = "kotej.pendingLeft"
|
||||
|
||||
private var pendingLeft: URL? {
|
||||
get {
|
||||
@@ -44,10 +44,10 @@ final class FinderMenu: FIFinderSync {
|
||||
let selected = FIFinderSyncController.default().selectedItemURLs() ?? []
|
||||
guard !selected.isEmpty else { return nil }
|
||||
|
||||
let menu = NSMenu(title: "Cotejo")
|
||||
let menu = NSMenu(title: "Kotej")
|
||||
|
||||
if selected.count >= 2 {
|
||||
let item = NSMenuItem(title: String(localized: "Compare the 2 items with Cotejo"),
|
||||
let item = NSMenuItem(title: String(localized: "Compare the 2 items with Kotej"),
|
||||
action: #selector(compareSelection(_:)), keyEquivalent: "")
|
||||
item.target = self
|
||||
menu.addItem(item)
|
||||
@@ -98,7 +98,7 @@ final class FinderMenu: FIFinderSync {
|
||||
|
||||
private func open(left: URL, right: URL) {
|
||||
var components = URLComponents()
|
||||
components.scheme = "cotejo"
|
||||
components.scheme = "kotej"
|
||||
components.host = "compare"
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "left", value: left.path),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Cotejo</string>
|
||||
<string>Kotej</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -25,7 +25,7 @@
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.FinderSync</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>CotejoFinder.FinderMenu</string>
|
||||
<string>KotejFinder.FinderMenu</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"Compare the 2 items with Cotejo" = "Compare the 2 items with Cotejo";
|
||||
"Compare the 2 items with Kotej" = "Compare the 2 items with Kotej";
|
||||
"Select for comparison" = "Select for comparison";
|
||||
"Compare with “%@”" = "Compare with “%@”";
|
||||
"Forget “%@”" = "Forget “%@”";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"Compare the 2 items with Cotejo" = "Comparar los 2 elementos con Cotejo";
|
||||
"Compare the 2 items with Kotej" = "Comparar los 2 elementos con Kotej";
|
||||
"Select for comparison" = "Seleccionar para comparar";
|
||||
"Compare with “%@”" = "Comparar con «%@»";
|
||||
"Forget “%@”" = "Olvidar «%@»";
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Cotejo",
|
||||
name: "Kotej",
|
||||
platforms: [.macOS(.v14)],
|
||||
products: [
|
||||
// The engine has no UI dependencies so it can also back a CLI later.
|
||||
.library(name: "CotejoEngine", targets: ["CotejoEngine"]),
|
||||
.library(name: "KotejEngine", targets: ["KotejEngine"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "CotejoEngine"),
|
||||
.testTarget(name: "CotejoEngineTests", dependencies: ["CotejoEngine"]),
|
||||
.target(name: "KotejEngine"),
|
||||
.testTarget(name: "KotejEngineTests", dependencies: ["KotejEngine"]),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Cotejo
|
||||
# Kotej
|
||||
|
||||
Comparador de ficheros, carpetas y **artefactos** (ZIP/JAR/EAR/WAR) para macOS.
|
||||
Ligero, sin dependencias externas y con el motor aislado del interfaz.
|
||||
@@ -28,13 +28,13 @@ Por defecto cada fichero usa el modo que le corresponde según su tipo
|
||||
## Integración con Finder
|
||||
|
||||
Con la app instalada, activa la extensión en **Ajustes del Sistema → General →
|
||||
Elementos de inicio y extensiones → Extensiones de Finder → Cotejo**. Después,
|
||||
Elementos de inicio y extensiones → Extensiones de Finder → Kotej**. Después,
|
||||
en el menú contextual del Finder:
|
||||
|
||||
- Selecciona **dos** elementos → *Comparar los 2 elementos con Cotejo*
|
||||
- Selecciona **dos** elementos → *Comparar los 2 elementos con Kotej*
|
||||
- O selecciona uno → *Seleccionar para comparar*, y en otro → *Comparar con “…”*
|
||||
|
||||
También responde a `cotejo://compare?left=/ruta/a&right=/ruta/b`, que abre la
|
||||
También responde a `kotej://compare?left=/ruta/a&right=/ruta/b`, que abre la
|
||||
comparación en una pestaña.
|
||||
|
||||
## Pestañas
|
||||
@@ -46,7 +46,7 @@ comparando.
|
||||
|
||||
## Estado
|
||||
|
||||
- ✅ **Motor** (`CotejoEngine`): lector ZIP propio, escaneo de árboles, archivos
|
||||
- ✅ **Motor** (`KotejEngine`): lector ZIP propio, escaneo de árboles, archivos
|
||||
anidados, comparación por niveles, comparadores binario/texto/JSON/XML y diff
|
||||
de líneas.
|
||||
- ✅ **App** SwiftUI: **pestañas** (varias comparaciones en una ventana),
|
||||
|
||||
@@ -172,7 +172,7 @@ public enum TreeScanner {
|
||||
|
||||
private static func extractNested(reader: ZipReader, entry: ZipReader.Entry, name: String) throws -> URL {
|
||||
let directory = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.appendingPathComponent("cotejo-nested-\(UUID().uuidString)")
|
||||
.appendingPathComponent("kotej-nested-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
let url = directory.appendingPathComponent(name)
|
||||
try reader.data(for: entry).write(to: url)
|
||||
@@ -1,5 +1,5 @@
|
||||
import XCTest
|
||||
@testable import CotejoEngine
|
||||
@testable import KotejEngine
|
||||
|
||||
final class ContentComparerTests: XCTestCase {
|
||||
private func data(_ text: String) -> Data { Data(text.utf8) }
|
||||
@@ -1,12 +1,12 @@
|
||||
import XCTest
|
||||
@testable import CotejoEngine
|
||||
@testable import KotejEngine
|
||||
|
||||
final class DirectoryComparerTests: XCTestCase {
|
||||
private var scratch: URL!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
scratch = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.appendingPathComponent("cotejo-\(UUID().uuidString)")
|
||||
.appendingPathComponent("kotej-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: scratch, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import XCTest
|
||||
@testable import CotejoEngine
|
||||
@testable import KotejEngine
|
||||
|
||||
final class TextDiffTests: XCTestCase {
|
||||
func testIdenticalTextIsAllEqual() {
|
||||
@@ -1,12 +1,12 @@
|
||||
import XCTest
|
||||
@testable import CotejoEngine
|
||||
@testable import KotejEngine
|
||||
|
||||
final class ZipReaderTests: XCTestCase {
|
||||
/// Builds a real archive with the system `zip`, so the reader is tested
|
||||
/// against the format as produced in the wild rather than a hand-made blob.
|
||||
private func makeArchive(_ files: [String: String], name: String = "test.zip") throws -> URL {
|
||||
let root = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.appendingPathComponent("cotejo-\(UUID().uuidString)")
|
||||
.appendingPathComponent("kotej-\(UUID().uuidString)")
|
||||
let content = root.appendingPathComponent("content")
|
||||
try FileManager.default.createDirectory(at: content, withIntermediateDirectories: true)
|
||||
|
||||
@@ -84,7 +84,7 @@ final class ZipReaderTests: XCTestCase {
|
||||
|
||||
func testRejectsNonZip() throws {
|
||||
let url = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.appendingPathComponent("cotejo-\(UUID().uuidString).zip")
|
||||
.appendingPathComponent("kotej-\(UUID().uuidString).zip")
|
||||
try Data("definitely not a zip".utf8).write(to: url)
|
||||
XCTAssertThrowsError(try ZipReader(url: url))
|
||||
}
|
||||
|
After Width: | Height: | Size: 859 KiB |
@@ -1,4 +1,4 @@
|
||||
name: Cotejo
|
||||
name: Kotej
|
||||
options:
|
||||
bundleIdPrefix: com.alexandrev
|
||||
createIntermediateGroups: true
|
||||
@@ -13,41 +13,43 @@ settings:
|
||||
DEVELOPMENT_TEAM: "2825Q76T7H"
|
||||
CODE_SIGN_STYLE: Automatic
|
||||
packages:
|
||||
CotejoEngine:
|
||||
KotejEngine:
|
||||
path: .
|
||||
targets:
|
||||
Cotejo:
|
||||
Kotej:
|
||||
type: application
|
||||
platform: macOS
|
||||
deploymentTarget: "14.0"
|
||||
sources:
|
||||
- App
|
||||
- path: App/Assets.xcassets
|
||||
buildPhase: resources
|
||||
- path: Resources
|
||||
buildPhase: resources
|
||||
dependencies:
|
||||
- package: CotejoEngine
|
||||
product: CotejoEngine
|
||||
- target: CotejoFinder
|
||||
- package: KotejEngine
|
||||
product: KotejEngine
|
||||
- target: KotejFinder
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.alexandrev.cotejo
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.alexandrev.kotej
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||
ENABLE_APP_SANDBOX: NO
|
||||
ENABLE_HARDENED_RUNTIME: YES
|
||||
info:
|
||||
path: App/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: Cotejo
|
||||
CFBundleDisplayName: Kotej
|
||||
CFBundleShortVersionString: "$(MARKETING_VERSION)"
|
||||
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
|
||||
CFBundleDevelopmentRegion: en
|
||||
CFBundleLocalizations: [en, es]
|
||||
NSHumanReadableCopyright: "Cotejo"
|
||||
NSHumanReadableCopyright: "Kotej"
|
||||
CFBundleURLTypes:
|
||||
- CFBundleURLName: com.alexandrev.cotejo
|
||||
CFBundleURLSchemes: [cotejo]
|
||||
- CFBundleURLName: com.alexandrev.kotej
|
||||
CFBundleURLSchemes: [kotej]
|
||||
|
||||
CotejoFinder:
|
||||
KotejFinder:
|
||||
type: app-extension
|
||||
platform: macOS
|
||||
deploymentTarget: "14.0"
|
||||
@@ -58,15 +60,15 @@ targets:
|
||||
buildPhase: resources
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.alexandrev.cotejo.finder
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.alexandrev.kotej.finder
|
||||
ENABLE_APP_SANDBOX: NO
|
||||
ENABLE_HARDENED_RUNTIME: YES
|
||||
info:
|
||||
path: FinderExtension/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: Cotejo
|
||||
CFBundleDisplayName: Kotej
|
||||
CFBundleShortVersionString: "$(MARKETING_VERSION)"
|
||||
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.FinderSync
|
||||
NSExtensionPrincipalClass: CotejoFinder.FinderMenu
|
||||
NSExtensionPrincipalClass: KotejFinder.FinderMenu
|
||||
|
||||