[
  {
    "path": ".gitignore",
    "content": "!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n!default.xcworkspace\n*.dSYM\n*.dSYM.zip\n*.hmap\n*.ipa\n*.lcov\n*.lock\n*.log\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n*.pid\n*.pid.lock\n*.seed\n*.swp\n*.tgz\n*.tsbuildinfo\n*.xccheckout\n*.xcscmblueprint\n*.xcuserstate\n*~.nib\n.AppleDB\n.AppleDesktop\n.AppleDouble\n.DS_Store\n.DocumentRevisions-V100\n.LSOverride\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n._*\n.apdisk\n.build\n.bundle\n.cache\n.cache/\n.com.apple.timemachine.donotpresent\n.dynamodb/\n.env\n.env.test\n.eslintcache\n.fseventsd\n.fusebox/\n.grunt\n.idea\n.lock-wscript\n.next\n.node_repl_history\n.npm\n.nuxt\n.nyc_output\n.parcel-cache\n.pnp.*\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n.serverless/\n.swiftpm\n.tern-port\n.vscode-test\n.vuepress/dist\n.yarn-integrity\n.yarn/build-state.yml\n.yarn/cache\n.yarn/unplugged\n/*.gcno\nArtifacts/\nCI\nCI-Pods.tar\nCarthage/Build\nCarthage/Build/\nDerivedData\nDerivedData/\nIcon\nNetwork Trash Folder\nPipeline/Dockers/Buildtime/\nPodfile.lock\nPods/\nTemporary Items\nartifacts/\nbower_components\nbuild/\nbuild/Release\ncoverage\ndefault.profraw\ndist\ndockerbuild\ndockermnt\nfastlane/Preview.html\nfastlane/report.xml\nfastlane/screenshots/**/*.png\nfastlane/test_output\niOSInjectionProject/\njspm_packages/\nlerna-debug.log*\nlib-cov\nlogs\nnode_modules/\nnpm-debug.log*\npids\nprofile\nproject.xcworkspace\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\ntemp/\ntemps/\nweb_modules/\nxcuserdata\nxcuserdata/\nyarn-debug.log*\nyarn-error.log*\n\n/Git/GitBuild\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"Git/Core\"]\n\tpath = Git/Core\n\turl = https://github.com/git/git\n"
  },
  {
    "path": ".root",
    "content": ""
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Lakr Aream\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "MyYearWithGit/App/App.swift",
    "content": "//\n//  App.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport AppKit\nimport SwiftUI\n\nlet preferredApplicationSize = CGSize(width: 750, height: 450)\nlet preferredTitleSize: CGFloat = 32\n\n// just in case if we are going to use it later\nlet currentBootWorkingDir: String = FileManager\n    .default\n    .currentDirectoryPath\n\nlet processCount: Int = {\n    var count = ProcessInfo\n        .processInfo\n        .processorCount\n    if count < 0 { count = 1 }\n    return count\n}()\n\nstruct MyYearWithGitApp: App {\n    init() {\n        unprotectWindowFromClose()\n        do {\n            let command = AuxiliaryExecuteWrapper.spawn(\n                command: AuxiliaryExecuteWrapper.git,\n                args: [\n                    \"version\",\n                ],\n                timeout: 0\n            ) { _ in }\n            if !command.1.contains(\"git version\") {\n                DispatchQueue.main.async {\n                    let alert = NSAlert()\n                    alert.alertStyle = .critical\n                    alert.messageText = NSLocalizedString(\"git 似乎没有安装，程序可能不工作。\", comment: \"\")\n                    alert.addButton(withTitle: NSLocalizedString(\"确定\", comment: \"\"))\n                    alert.beginSheetModal(for: NSApp.keyWindow ?? NSWindow()) { _ in\n                    }\n                }\n            }\n        }\n    }\n\n    var body: some Scene {\n        WindowGroup { content }\n            .windowStyle(.hiddenTitleBar)\n            .commands { CommandGroup(replacing: .newItem) {} }\n            .restrictWindowResizing()\n    }\n\n    // fixed size for better control over layout effect\n    var content: some View {\n        NavigatorView()\n            .ignoresSafeArea()\n            .frame(\n                width: preferredApplicationSize.width,\n                height: preferredApplicationSize.height,\n                alignment: .center\n            )\n            .onReceive(NotificationCenter.default.publisher(for: NSApplication.willUpdateNotification), perform: { _ in\n                hideWindowRelatedButtons()\n            })\n    }\n\n    func hideWindowRelatedButtons() {\n        for window in NSApplication.shared.windows {\n            window.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true\n            window.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true\n        }\n    }\n}\n\nprivate extension Scene {\n    func restrictWindowResizing() -> some Scene {\n        if #available(macOS 13.0, *) {\n            return self.windowResizability(.contentSize)\n        }\n        return self\n    }\n}\n\nextension View {\n    func makeHoverPointer() -> some View {\n        onHover { hover in\n            if hover {\n                NSCursor.pointingHand.push()\n            } else {\n                NSCursor.pop()\n            }\n        }\n    }\n}\n\nfunc unprotectWindowFromClose() {\n    UserDefaults.standard.set(false, forKey: \"wiki.qaq.window.confirm.close\")\n}\n\nfunc protectWindowFromClose() {\n    UserDefaults.standard.set(true, forKey: \"wiki.qaq.window.confirm.close\")\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n    \"images\": [\n        {\n            \"size\": \"16x16\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-16.png\",\n            \"scale\": \"1x\"\n        },\n        {\n            \"size\": \"16x16\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-16@2x.png\",\n            \"scale\": \"2x\"\n        },\n        {\n            \"size\": \"32x32\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-32.png\",\n            \"scale\": \"1x\"\n        },\n        {\n            \"size\": \"32x32\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-32@2x.png\",\n            \"scale\": \"2x\"\n        },\n        {\n            \"size\": \"128x128\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-128.png\",\n            \"scale\": \"1x\"\n        },\n        {\n            \"size\": \"128x128\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-128@2x.png\",\n            \"scale\": \"2x\"\n        },\n        {\n            \"size\": \"256x256\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-256.png\",\n            \"scale\": \"1x\"\n        },\n        {\n            \"size\": \"256x256\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-256@2x.png\",\n            \"scale\": \"2x\"\n        },\n        {\n            \"size\": \"512x512\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-512.png\",\n            \"scale\": \"1x\"\n        },\n        {\n            \"size\": \"512x512\",\n            \"idiom\": \"mac\",\n            \"filename\": \"icon-512@2x.png\",\n            \"scale\": \"2x\"\n        }\n    ],\n    \"info\": {\n        \"version\": 1,\n        \"author\": \"icon.wuruihong.com\"\n    }\n}"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/badge.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"wall.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/bitbucket.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"bitbucket_icon.svg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/git.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"git.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/github.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"GitHub-Mark-120px-plus.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/gitlab.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"gitlab.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/qrcode.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"39__700a529aa6d6df65e62f2f7def773e06_e86fdd9ba31ec0fd57ed3c79bb536bdb.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.aqi.high.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.aqi.high.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.aqi.low.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.aqi.low.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.aqi.medium.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.aqi.medium.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.circle.fill.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.circle.fill.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.bolt.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.bolt.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.bolt.rain.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.bolt.rain.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.drizzle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.drizzle.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.fog.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.fog.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.hail.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.hail.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.heavyrain.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.heavyrain.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.moon.bolt.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.moon.bolt.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.moon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.moon.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.moon.rain.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.moon.rain.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.rain.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.rain.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.sleet.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.sleet.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.snow.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.snow.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.sun.bolt.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.sun.bolt.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.sun.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.sun.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.cloud.sun.rain.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.cloud.sun.rain.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.humidity.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.humidity.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.hurricane.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.hurricane.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.moon.circle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.moon.circle.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.moon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.moon.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.moon.stars.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.moon.stars.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.smoke.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.smoke.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.snowflake.circle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.snowflake.circle.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.snowflake.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.snowflake.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sparkles.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sparkles.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sun.and.horizon.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sun.and.horizon.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sun.dust.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sun.dust.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sun.haze.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sun.haze.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sun.max.circle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sun.max.circle.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sun.max.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sun.max.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sun.min.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sun.min.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sunrise.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sunrise.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.sunset.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.sunset.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.thermometer.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.thermometer.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.thermometer.snowflake.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.thermometer.snowflake.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.thermometer.sun.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.thermometer.sun.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.tornado.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.tornado.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.tropicalstorm.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.tropicalstorm.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.wind.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.wind.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Assets.xcassets/sfexp/custom.wind.snow.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"custom.wind.snow.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"original\"\n  }\n}\n"
  },
  {
    "path": "MyYearWithGit/App/Hooker.m",
    "content": "//\n//  AskBeforeCloseMyWindow.swift\n//\n//\n//  Created by Lakr Aream on 2021/11/30.\n//\n\n#import <AppKit/AppKit.h>\n#import <objc/runtime.h>\n\nstatic void HookMessage(Class cls, SEL selName, IMP replaced, IMP *orig) {\n    Method origMethod = class_getInstanceMethod(cls, selName);\n    if (!origMethod) {\n        printf(\"nullprt origMethod, check class and selector name\\n\");\n        return;\n    }\n    *orig = method_setImplementation(origMethod, replaced);\n#ifdef DEBUG\n    printf(\"HookMessage %p -> %p <%s>\\n\", orig, replaced, [NSStringFromClass(cls) UTF8String]);\n#endif\n}\n\nstatic void (*original_NSWindow_close)(NSWindow *self, SEL _cmd);\nstatic void replaced_NSWindow_close(NSWindow *self, SEL _cmd)\n{\n    NSString *className = NSStringFromClass([self class]);\n    if (![className isEqualToString:@\"SwiftUI.SwiftUIWindow\"]) {\n        original_NSWindow_close(self, _cmd);\n        return;\n    }\n    \n    // check if we need to perform this action\n    NSString *userDefaultKey = @\"wiki.qaq.window.confirm.close\";\n    if ([[[NSUserDefaults standardUserDefaults] valueForKey:userDefaultKey] boolValue] != YES) {\n        original_NSWindow_close(self, _cmd);\n        exit(0);\n        return;\n    }\n    \n    NSAlert *alert = [[NSAlert alloc] init];\n    [alert setAlertStyle:NSAlertStyleCritical];\n    [alert setMessageText:NSLocalizedString(@\"关闭窗口将不会保存你的分析记录。\", nil)];\n    [alert addButtonWithTitle:NSLocalizedString(@\"关闭\", nil)];\n    [alert addButtonWithTitle:NSLocalizedString(@\"取消\", nil)];\n    NSWindow *window = [NSApp keyWindow];\n    if (window) {\n        [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) {\n            if (returnCode == NSAlertFirstButtonReturn) {\n                exit(0);\n            }\n        }];\n    } else {\n        if ([alert runModal] == NSAlertFirstButtonReturn) {\n            exit(0);\n        }\n    }\n    return;\n}\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wundeclared-selector\"\n\n__attribute__((constructor))\nstatic void makeMyMagicWork(void) {\n    // do our magic\n    HookMessage(\n        objc_getClass(\"NSWindow\"),\n        NSSelectorFromString(@\"close\"),\n        (IMP)&replaced_NSWindow_close,\n        (IMP *)&original_NSWindow_close\n    );\n}\n\n#pragma clang diagnostic pop\n"
  },
  {
    "path": "MyYearWithGit/App/InfoPlist.xcstrings",
    "content": "{\n  \"sourceLanguage\" : \"zh-Hans\",\n  \"strings\" : {\n    \"CFBundleDisplayName\" : {\n      \"comment\" : \"Bundle display name\",\n      \"extractionState\" : \"extracted_with_value\",\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Git Year\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"年度代码\"\n          }\n        }\n      }\n    },\n    \"CFBundleName\" : {\n      \"comment\" : \"Bundle name\",\n      \"extractionState\" : \"extracted_with_value\",\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"My Year w/ Git\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"MyYearWithGit\"\n          }\n        }\n      }\n    },\n    \"NSHumanReadableCopyright\" : {\n      \"comment\" : \"Copyright (human-readable)\",\n      \"extractionState\" : \"extracted_with_value\",\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Copyright © 2024 @Lakr233's Team. All Rights Reserved.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"标准件厂长@砍砍\"\n          }\n        }\n      }\n    },\n    \"年度代码报告\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Annually Code Report\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"年度代码报告\"\n          }\n        }\n      }\n    }\n  },\n  \"version\" : \"1.0\"\n}"
  },
  {
    "path": "MyYearWithGit/App/Localizable.xcstrings",
    "content": "{\n  \"sourceLanguage\" : \"zh-Hans\",\n  \"strings\" : {\n    \"\" : {\n      \"shouldTranslate\" : false\n    },\n    \" %@ \" : {\n      \"shouldTranslate\" : false\n    },\n    \" %lld \" : {\n      \"shouldTranslate\" : false\n    },\n    \"**制作名单**\\n\\n此项目由 [标准件厂长@砍砍](https://twitter.com/Lakr233) 发起，设计，并完成撰写。\\n\\n与此同时，感谢 [Cyandev](https://twitter.com/unixzii) [拾一](https://twitter.com/__oquery) [82Flex](https://twitter.com/82flex) 与我一同前行。排名不分先后。\\n\\n**软件许可证**\\n\\n此项目使用了 [Octokit](https://github.com/nerdishbynature/octokit.swift) 与 [RequestKit](https://github.com/nerdishbynature/RequestKit.git) 来处理 GitHub 相关 Api，请参考他们的使用许可。\\n\\n此项目使用了来自 [Git](https://git-scm.com/downloads/logos) [GitHub](https://github.com/logos) [GitLab](https://about.gitlab.com/press/press-kit/) [Bitbucket](https://Bitbucket.org/) 的相关图标，请参考他们的使用许可。\\n\\n2021 冬，最后更新于 2024 年。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"**Made by List**\\n\\nThis project was initiated, designed, and written by [@Lakr233](https://twitter.com/Lakr233).\\n\\nAt the same time, thanks to [Cyandev](https://twitter.com/unixzii), [拾一](https://twitter.com/__oquery), and [82Flex](https://twitter.com/82flex) for working with me. The ranking is in no particular order.\\n\\n**Software Licenses**\\n\\nThis project uses [Octokit](https://github.com/nerdishbynature/octokit.swift) and [RequestKit](https://github.com/nerdishbynature/RequestKit.git) to handle GitHub-related APIs. Please refer to their respective licenses for use.\\n\\nThis project uses related icons from [Git](https://git-scm.com/downloads/logos), [GitHub](https://github.com/logos), [GitLab](https://about.gitlab.com/press/press-kit/), and [Bitbucket](https://Bitbucket.org/). Please refer to their respective licenses for use.\\n\\nWinter 2021, last updated in 2024.\\n\"\n          }\n        }\n      }\n    },\n    \"%@\" : {\n      \"shouldTranslate\" : false\n    },\n    \"%@  行\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%@ Line(s)\"\n          }\n        }\n      }\n    },\n    \"%@ - %@\" : {\n      \"localizations\" : {\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"%1$@ - %2$@\"\n          }\n        }\n      },\n      \"shouldTranslate\" : false\n    },\n    \"%@ 年\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%@ Year\"\n          }\n        }\n      }\n    },\n    \"%@ 年 x %@\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%1$@ x %2$@\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"%1$@ 年 x %2$@\"\n          }\n        }\n      }\n    },\n    \"%@ 是我今年的代言词。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%@ tagged my year.\"\n          }\n        }\n      }\n    },\n    \"%@的年度代码报告\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%@’s Annually Code Report\"\n          }\n        }\n      }\n    },\n    \"%lld\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%lld\"\n          }\n        }\n      },\n      \"shouldTranslate\" : false\n    },\n    \"%lld 月 %lld 日\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%1$lld @ %2$lld th\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"%1$lld 月 %2$lld 日\"\n          }\n        }\n      }\n    },\n    \"© %lld 标准件厂长@砍砍 & 他的朋友们\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Copyright © %lld @Lakr233's Team. All Rights Reserved.\"\n          }\n        }\n      }\n    },\n    \"↓ 向下滑动开启报告 ↓\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"↓ Open My Git Letter ↓\"\n          }\n        }\n      }\n    },\n    \">>\" : {\n      \"shouldTranslate\" : false\n    },\n    \"Bitbucket\" : {\n      \"shouldTranslate\" : false\n    },\n    \"Bufeature 制造机\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Bufeature Maker\"\n          }\n        }\n      }\n    },\n    \"Bufeature: <noun> bug feature, feature with bug.\" : {\n      \"shouldTranslate\" : false\n    },\n    \"ghp_xxxxxx\" : {\n      \"shouldTranslate\" : false\n    },\n    \"Git\" : {\n      \"shouldTranslate\" : false\n    },\n    \"git 似乎没有安装，程序可能不工作。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"It looks like git was not installed, this program may not work.\"\n          }\n        }\n      }\n    },\n    \"GitHub\" : {\n      \"shouldTranslate\" : false\n    },\n    \"GitLab\" : {\n      \"shouldTranslate\" : false\n    },\n    \"https://your.git.lab.example.com\" : {\n      \"shouldTranslate\" : false\n    },\n    \"namespace::\" : {\n      \"shouldTranslate\" : false\n    },\n    \"xxxxxx\" : {\n      \"shouldTranslate\" : false\n    },\n    \"上串下跳的提交，是开心，是愉悦，还是害怕，或者担心呢？\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The erratic commits — are they driven by joy, pleasure, fear, or perhaps worry?\"\n          }\n        }\n      }\n    },\n    \"下午\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Afternoon\"\n          }\n        }\n      }\n    },\n    \"个人访问令牌\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Personal Access Token\"\n          }\n        }\n      }\n    },\n    \"中午\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Noon\"\n          }\n        }\n      }\n    },\n    \"人们说色即是空，空即是色。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Form is emptiness, and emptiness is form.\"\n          }\n        }\n      }\n    },\n    \"今年，我获得了不少成就。下面是我愿意和你分享的一些。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This year, I achieved quite a few things. Here are some I’d like to share with you.\"\n          }\n        }\n      }\n    },\n    \"今年有一天的提交次数超过五十次\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Having a day committed over 50 times.\"\n          }\n        }\n      }\n    },\n    \"今年有一天的提交次数超过百次\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Having a day committed over 100 times.\"\n          }\n        }\n      }\n    },\n    \"今年有且只有一次提交\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Being Single\"\n          }\n        }\n      }\n    },\n    \"今年没有写代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Never Code This Year\"\n          }\n        }\n      }\n    },\n    \"今年的提交中熟练使用了超过六种语言\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"More then 6 language was used this year.\"\n          }\n        }\n      }\n    },\n    \"仓库因你增添了 %@ 行代码，也减去了 %@ 行的重量。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Repository added %1$@ line(s) of code made by your hand, while removed %2$@ line(s), also made by your hand.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"仓库因你增添了 %1$@ 行代码，也减去了 %2$@ 行的重量。\"\n          }\n        }\n      }\n    },\n    \"他们也陪我走过一段旅程。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"They have also accompanied me on a journey.\"\n          }\n        }\n      }\n    },\n    \"他们说多少不重要，因为我的提交，每一次都心意满满。😮\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"They say the number doesn’t matter, because each of my commits is filled with intention. 😮\"\n          }\n        }\n      }\n    },\n    \"他是我最好的伙伴。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"He is my best partner.\"\n          }\n        }\n      }\n    },\n    \"代码里，这些词经常出现\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In your code, by your mind. These words are frequent.\"\n          }\n        }\n      }\n    },\n    \"以获取 App 专用密码。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"to obtain the App-specific password.\"\n          }\n        }\n      }\n    },\n    \"以获取个人访问令牌。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"to obtain a personal access token.\"\n          }\n        }\n      }\n    },\n    \"休养生息\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Taking Break\"\n          }\n        }\n      }\n    },\n    \"你可添加本地或远端的仓库，不用担心仓库重复，相同的提交哈希仅计算一次。\\n远端仓库在分析时会被载入本地临时文件夹内，分析完成以后会从本地删除。\\n你可能需要额外的步骤来获取远端仓库的访问令牌，登录时会自动添加邮件地址。\\n克隆仅支持使用 HTTPS 协议。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"You can add local or remote repositories without worrying about duplication. The same commit hash will only be counted once.\\nRemote repositories will be loaded into a local temporary folder during analysis and will be deleted locally after the analysis is complete.\\nYou may need additional steps to obtain an access token for remote repositories. Your email address will be automatically added during login.\\nCloning only supports the HTTPS protocol. ￼\"\n          }\n        }\n      }\n    },\n    \"你看到了吗，这一页，有 1010 行空行呢。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Did you see this? This page has 1010 blank lines.\"\n          }\n        }\n      }\n    },\n    \"修得的福报，是我一生最大的欢喜。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The good karma I’ve earned is the greatest joy of my life.\"\n          }\n        }\n      }\n    },\n    \"偷得浮生半日闲，可不能再修福报啦！\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Having a half of the day off keyboard, no more coding!\"\n          }\n        }\n      }\n    },\n    \"全勤战士\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Perfect Attendance Warrior\"\n          }\n        }\n      }\n    },\n    \"全年提交热力图\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Annual Commit Heatmap\"\n          }\n        }\n      }\n    },\n    \"全年每天都有提交记录\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Committed every single day of the year\"\n          }\n        }\n      }\n    },\n    \"共 %lld 个仓库\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%lld repository in total\"\n          }\n        }\n      }\n    },\n    \"关闭\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Close\"\n          }\n        }\n      }\n    },\n    \"关闭窗口将不会保存你的分析记录。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Close this window will delete your analysis record.\"\n          }\n        }\n      }\n    },\n    \"凌晨\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Early Morning\"\n          }\n        }\n      }\n    },\n    \"分析过程中不可载入数据，请稍后再试。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Data can not be loaded while in analysis.\"\n          }\n        }\n      }\n    },\n    \"删除\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Delete\"\n          }\n        }\n      }\n    },\n    \"删除关键词\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Delete the keyword.\"\n          }\n        }\n      }\n    },\n    \"删除包含 <关键词> 的仓库，区分大小写。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Delete repositories containing the <Keyword>, case-sensitive.\"\n          }\n        }\n      }\n    },\n    \"勤劳努力\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Some Hardworking\"\n          }\n        }\n      }\n    },\n    \"卷卷卷卷卷卷\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Haaarrrddd Wooorrrkkk\"\n          }\n        }\n      }\n    },\n    \"发奋图强\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hard Work\"\n          }\n        }\n      }\n    },\n    \"取消\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Cancel\"\n          }\n        }\n      }\n    },\n    \"可能敲代码，正是我的乐趣吧。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Writing code might just be my source of joy.\"\n          }\n        }\n      }\n    },\n    \"喜欢在下午茶时间提交代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Likes to submit code during afternoon tea time\"\n          }\n        }\n      }\n    },\n    \"喜欢在中午提交代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Likes to submit code during the noon\"\n          }\n        }\n      }\n    },\n    \"喜欢在午夜时分提交代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Likes to submit code during the early morning\"\n          }\n        }\n      }\n    },\n    \"喜欢在早晨提交代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Likes to submit code during the morning\"\n          }\n        }\n      }\n    },\n    \"喜欢在晚上提交代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Likes to submit code during the evening\"\n          }\n        }\n      }\n    },\n    \"喜欢在晚饭时间提交代码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Likes to submit code during dinner time\"\n          }\n        }\n      }\n    },\n    \"回过头来看看这一年，似乎付出了不少。咱一共活跃了 %@ 天。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Looking back on this year, it seems quite a lot of effort was put in. We were active for %@ days.\\n\"\n          }\n        }\n      }\n    },\n    \"回过头来看看这一年，咱一共活跃了 %@ 天。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Looking back on this year, we were active for %@ days.\\n\"\n          }\n        }\n      }\n    },\n    \"在 %@ 年\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In the year %@\"\n          }\n        }\n      }\n    },\n    \"在代码或者提交备注中使用了不少的脏话\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"A fair amount of profanity was used in the code or commit messages.\"\n          }\n        }\n      }\n    },\n    \"在代码或者提交备注中使用的最多的词语是脏话\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The most frequently used word in code or commit messages is profanity.\"\n          }\n        }\n      }\n    },\n    \"在剩余的时光里，\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In the remaining time,\"\n          }\n        }\n      }\n    },\n    \"在这一年里，我使用这门语言提交了 %@ 行代码。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In this year, I have submitted %@ lines of code using this language.\"\n          }\n        }\n      }\n    },\n    \"在这短短的一天里，你一共提交了 %@ 次代码。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In just this short day, you have made %@ commits.\"\n          }\n        }\n      }\n    },\n    \"在这里输入表达式\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enter expression here.\"\n          }\n        }\n      }\n    },\n    \"地址\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Address\"\n          }\n        }\n      }\n    },\n    \"夜猫子\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Night Owl\"\n          }\n        }\n      }\n    },\n    \"大概是很特别的一天。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Looks like a special day.\"\n          }\n        }\n      }\n    },\n    \"天啦噜！我的摸鱼流量超过了 100TB 呢！\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Oh my goodness! My “slacking off” bandwidth has exceeded 100TB!\"\n          }\n        }\n      }\n    },\n    \"天才第一步\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"First Step\"\n          }\n        }\n      }\n    },\n    \"如果不计算周末的日子，则是 %@ 次。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"If weekends are not counted, it will be %@ times.\"\n          }\n        }\n      }\n    },\n    \"如果说代码是有温度的字符，那仓库便是咱的小太阳～ 🤫\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"If code is made up of characters with warmth, then the repository is our little sun~ 🤫\"\n          }\n        }\n      }\n    },\n    \"完成\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Done\"\n          }\n        }\n      }\n    },\n    \"导出分析数据\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Export Analysis Data\"\n          }\n        }\n      }\n    },\n    \"小试牛刀\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Try My Best\"\n          }\n        }\n      }\n    },\n    \"干饭人！干饭魂！\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Lets Have Launch First\"\n          }\n        }\n      }\n    },\n    \"平均而言，我一天提交代码 %@ 次。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"On average, %@ commit(s) was made over day to day.\"\n          }\n        }\n      }\n    },\n    \"应用密码\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"App Passwrod\"\n          }\n        }\n      }\n    },\n    \"应该是太冷门了吧，数据库里找不到对应的语言。🥲\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"It’s probably too obscure, that’s why the language can’t be found in the database. 🥲\"\n          }\n        }\n      }\n    },\n    \"开启我的年度报告\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open My Annual Report\"\n          }\n        }\n      }\n    },\n    \"感谢我的仓库们，他们记录着我生活的点点滴滴。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Many thanks to our repository, the kept our records.\"\n          }\n        }\n      }\n    },\n    \"成就墙\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Achievements\"\n          }\n        }\n      }\n    },\n    \"我不知道你写了什么\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I’m not sure what you wrote either.\"\n          }\n        }\n      }\n    },\n    \"我也不知道你来这里干什么\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I don’t know why you’ve here.\"\n          }\n        }\n      }\n    },\n    \"我从来不摸鱼，因为没有鱼给我摸。🐟\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I never slack off, because there’s no fish for me to touch. 🐟\"\n          }\n        }\n      }\n    },\n    \"我们将会列出你的全部仓库地址供你挑选，稍后克隆到本地为分析作准备。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"We will list all your repository addresses for you to choose from, and later clone them to your local machine to prepare for analysis.\"\n          }\n        }\n      }\n    },\n    \"我会写很多很多的单词，很多很多的句子。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I can write many, many words, many, many sentences.\"\n          }\n        }\n      }\n    },\n    \"我最喜欢在 %@ 的时候提交代码，总共提交了 %lld 次。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I love submitting code when %1$@, and I have made a total of %2$lld commits.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"new\",\n            \"value\" : \"我最喜欢在 %1$@ 的时候提交代码，总共提交了 %2$lld 次。\"\n          }\n        }\n      }\n    },\n    \"我和我的代码，还有这一年。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"My code, my journey, and this year.\"\n          }\n        }\n      }\n    },\n    \"我很专一，没有使用过其他的语言。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I am very dedicated and have never used any other language.\"\n          }\n        }\n      }\n    },\n    \"我想你也会很喜欢的，我如此说道，我如此和你说道。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I think you will like it too, as I say this, I say it to you.\"\n          }\n        }\n      }\n    },\n    \"我是卷王本王 🤪\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I am the King of Competition! 🤪\"\n          }\n        }\n      }\n    },\n    \"我是卷王王中王本王\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"King of Compete\"\n          }\n        }\n      }\n    },\n    \"我是奥特曼\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I’m Altman\"\n          }\n        }\n      }\n    },\n    \"我的 Bufeature 做好了吗？\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Is my Bufeature ready?\"\n          }\n        }\n      }\n    },\n    \"我的年度代码总结\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"My Annual Code Summary\"\n          }\n        }\n      }\n    },\n    \"我着实不能理解其中的含义。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I truly cannot understand the meaning behind it.\"\n          }\n        }\n      }\n    },\n    \"打印\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Print\"\n          }\n        }\n      }\n    },\n    \"扫码开启你的专属年度代码提交报告\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Scan the QR code to unlock your personalized annual code commit report.\"\n          }\n        }\n      }\n    },\n    \"排除列表\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Exclude List\"\n          }\n        }\n      }\n    },\n    \"提交的次数为负数\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Negative Commits\"\n          }\n        }\n      }\n    },\n    \"提交记录告诉咱：\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The commit history tells us:\"\n          }\n        }\n      }\n    },\n    \"提交记录里，这些词经常出现\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"These words often appear in the commit history:\"\n          }\n        }\n      }\n    },\n    \"数据源\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Data Source\"\n          }\n        }\n      }\n    },\n    \"文件名关键词\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Filename Keyword\"\n          }\n        }\n      }\n    },\n    \"文件名关键词 匹配大小写\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Filename Keyword & Case Sensitive\"\n          }\n        }\n      }\n    },\n    \"文件名正则表达式完整匹配\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Filename Regex Full Match\"\n          }\n        }\n      }\n    },\n    \"文明语言大师\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Civilized Language Master\"\n          }\n        }\n      }\n    },\n    \"文明语言学者\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Civilized Language Scholar\"\n          }\n        }\n      }\n    },\n    \"无名氏\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Anonymous\"\n          }\n        }\n      }\n    },\n    \"早晨\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Morning\"\n          }\n        }\n      }\n    },\n    \"早睡早起身体好\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Early to bed, early to rise, healthy body.\"\n          }\n        }\n      }\n    },\n    \"时光印记\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Time Traces\"\n          }\n        }\n      }\n    },\n    \"星星有月亮，代码回家有仓库，而你有我相伴。😛\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The stars have the moon, code has repositories to return to, and you have me by your side. 😛\"\n          }\n        }\n      }\n    },\n    \"星爸爸和气氛组的关怀\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The coffee shop is necessary\"\n          }\n        }\n      }\n    },\n    \"是的，我又在摸鱼 🥺\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Haha, I see! Sometimes a little break is needed! 😅\"\n          }\n        }\n      }\n    },\n    \"晚上\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Night\"\n          }\n        }\n      }\n    },\n    \"晚饭的吃好\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Dinner the Best\"\n          }\n        }\n      }\n    },\n    \"晚餐时间\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Dinner Time\"\n          }\n        }\n      }\n    },\n    \"有 %@ 个周末的日子，我在仓库留下了身影。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"There are %@ weekends, and I’ve left my mark in the repository.\"\n          }\n        }\n      }\n    },\n    \"有一些些提交\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Committed Some Sometimes\"\n          }\n        }\n      }\n    },\n    \"有一些提交\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Committed Sometimes\"\n          }\n        }\n      }\n    },\n    \"有很多很多很多很多的提交\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Many Many Many Commits\"\n          }\n        }\n      }\n    },\n    \"有很多提交\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Many Commits\"\n          }\n        }\n      }\n    },\n    \"未找到有效的仓库。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No valid repository found.\"\n          }\n        }\n      }\n    },\n    \"本地仓库\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Local Repository\"\n          }\n        }\n      }\n    },\n    \"校验码: 0x%@\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Checksum: 0x%@\"\n          }\n        }\n      }\n    },\n    \"正在从 Bitbucket 下载仓库 %@...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Downloading repository %@ from Bitbucket…\"\n          }\n        }\n      }\n    },\n    \"正在从 Github 下载仓库 %@...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Downloading repository %@ from GitHub…\"\n          }\n        }\n      }\n    },\n    \"正在从 GitLab 下载仓库 %@...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Downloading repository %@ from GitLab…\"\n          }\n        }\n      }\n    },\n    \"正在分析 %@...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Analysis %@…\"\n          }\n        }\n      }\n    },\n    \"正在创建分析副本 %@...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Creating analysis copy %@…\"\n          }\n        }\n      }\n    },\n    \"正在处理...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Processing…\"\n          }\n        }\n      }\n    },\n    \"正在查找代码仓库...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Searching for code repositories…\"\n          }\n        }\n      }\n    },\n    \"正在生成汇总...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Generating Summary…\"\n          }\n        }\n      }\n    },\n    \"正在解析数据...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Decoding data…\"\n          }\n        }\n      }\n    },\n    \"此年度报告支持以下数据源\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This report supports following data source\"\n          }\n        }\n      }\n    },\n    \"每月代码量统计\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Monthly Code Statistics\"\n          }\n        }\n      }\n    },\n    \"没有可用的数据源，分析被取消。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No data source available.\"\n          }\n        }\n      }\n    },\n    \"没有可用的数据源，请点击上面的按钮来添加。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No available data sources. Please click the button above to add.\"\n          }\n        }\n      }\n    },\n    \"添加\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add\"\n          }\n        }\n      }\n    },\n    \"添加来自 Bitbucket 的仓库\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add Repository @Bitbucket\"\n          }\n        }\n      }\n    },\n    \"添加来自 GitHub 的仓库\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add Repository @GitHub\"\n          }\n        }\n      }\n    },\n    \"添加来自 GitLab 的仓库\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add Repository @GitLab\"\n          }\n        }\n      }\n    },\n    \"添加来自本地的仓库\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add Repository @localhost\"\n          }\n        }\n      }\n    },\n    \"狗急了会跳墙，我急了会骂娘。一天这么多次的提交，肯定是有人被我骂得很惨吧。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"A cornered dog will jump over a wall, and when I’m pushed, I start swearing. With so many commits in a day, someone must’ve caught the brunt of my wrath, huh?\"\n          }\n        }\n      }\n    },\n    \"生成截图\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Generate Screenshot\"\n          }\n        }\n      }\n    },\n    \"用户名\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Username\"\n          }\n        }\n      }\n    },\n    \"由 标准件厂长@砍砍 制作\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Made by @Lakr233\"\n          }\n        }\n      }\n    },\n    \"睡前故事\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Nightmare Story\"\n          }\n        }\n      }\n    },\n    \"确定\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"OK\"\n          }\n        }\n      }\n    },\n    \"空行能让我的代码变得好看，我很喜欢。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Blank lines make my code look better, and I like it.\"\n          }\n        }\n      }\n    },\n    \"签到不是胡闹\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Sign Up & Wake Up.\"\n          }\n        }\n      }\n    },\n    \"编程语言大师\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Master of Programing Language\"\n          }\n        }\n      }\n    },\n    \"编辑\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Edit\"\n          }\n        }\n      }\n    },\n    \"致谢\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Acknowledgement\"\n          }\n        }\n      }\n    },\n    \"若要忽略指定的文件，请在此处填写。\\n我们将会传入提交文件的相对路径与你的排除项逐一进行匹配。\\n若有一项满足，则该文件不会被记入。此次提交的其他文件仍会进行统计。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"If you wish to ignore specific files, please enter them here.\\nWe will match the relative path of the submitted files with your exclusions one by one.\\nIf any of the exclusions match, the file will not be counted. The other files in this submission will still be included in the statistics.\"\n          }\n        }\n      }\n    },\n    \"若要添加提交邮件地址，请在下方的文本框中输入。一次仅能输入一个，且只支持小写字符。\\n若提交的电子邮件地址不在该列表内，则不会计算此次提交。\\n我们会为你保存电子邮件地址记录，仅需配置一次即可。\\n登录到 GitHub 或 GitLab 账号会自动添加账号所属的电子邮件地址。\\n若有不愿使用的电子邮件地址，请在配置完成仓库以后，再来此处删除。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"To add a commit email address, please enter it in the text box below. Only one email address can be entered at a time, and it must be in lowercase characters.\\nIf the submitted email address is not in the list, the commit will not be counted.\\nWe will save the email address record for you, and you only need to configure it once.\\nLogging into your GitHub or GitLab account will automatically add the email address associated with the account.\\nIf there is an email address you wish not to use, please come back to delete it after the repository configuration is complete.\"\n          }\n        }\n      }\n    },\n    \"若要连接到 Bitbucket，请参考\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"To connect to Bitbucket, please refer to:\"\n          }\n        }\n      }\n    },\n    \"若要连接到 GitHub，请参考\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"To connect to GitHub, please refer to:\"\n          }\n        }\n      }\n    },\n    \"若要连接到 GitLab，请参考\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"To connect to GitLab, please refer to:\"\n          }\n        }\n      }\n    },\n    \"获取数据\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Fetch Data\"\n          }\n        }\n      }\n    },\n    \"语言大师的称号，非你莫属！\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"The title of Language Master is yours and yours alone! 🙌\"\n          }\n        }\n      }\n    },\n    \"请为 App 专用密码添加 Account 和 Repositories 的 Read 权限。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Please grant the App-specific password Read access to Account and Repositories.\"\n          }\n        }\n      }\n    },\n    \"请为令牌添加 read_user, read_api, read_repository 权限，需要服务端支持 v4 api。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Please grant the token read_user, read_api, and read_repository permissions, and ensure the server supports the v4 API.\"\n          }\n        }\n      }\n    },\n    \"请为令牌添加 repo 和 user 的全部权限。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Please grant the token full repo and user permissions.\"\n          }\n        }\n      }\n    },\n    \"请选择一个需要分析的仓库的来源，我们将在稍后填写具体信息。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Please select the source of the repository to be analyzed. Specific details will be filled in later.\"\n          }\n        }\n      }\n    },\n    \"请选择一些文件夹，我们将从中搜索仓库。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Please select some folders from which we will search for repositories.\"\n          }\n        }\n      }\n    },\n    \"路径中存在文件名\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Path Keyword\"\n          }\n        }\n      }\n    },\n    \"路径中存在文件名 匹配大小写\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Path Keyword & Case Sensitive\"\n          }\n        }\n      }\n    },\n    \"路径关键词\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Path\"\n          }\n        }\n      }\n    },\n    \"路径关键词 匹配大小写\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Path & Case Sensitive\"\n          }\n        }\n      }\n    },\n    \"输入昵称 可选\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enter Nickname (Optional）\"\n          }\n        }\n      }\n    },\n    \"输入电子邮件地址\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enter Email Address\"\n          }\n        }\n      }\n    },\n    \"辛苦啦 🥲\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Thank you for your hard work! 🥲\"\n          }\n        }\n      }\n    },\n    \"过滤器\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Filter\"\n          }\n        }\n      }\n    },\n    \"还有 %lld 种语言...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"And %lld more languages...\"\n          }\n        }\n      }\n    },\n    \"这一天，是忙碌的自己，没吃好，没睡好。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This day is the busy version of myself — didn’t eat well, didn’t sleep well.\"\n          }\n        }\n      }\n    },\n    \"这一年，咱一共卷了 %@ 天。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This year, I’ve been competitive for a total of %@ days.\"\n          }\n        }\n      }\n    },\n    \"这一年的周末，我都没有提交代码。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"I didn’t commit any code on the weekends this year.\"\n          }\n        }\n      }\n    },\n    \"这一年里，我总共进行了 %@ 次代码提交。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In this year, I made a total of %@ code commits.\"\n          }\n        }\n      }\n    },\n    \"这份文档\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This document\"\n          }\n        }\n      }\n    },\n    \"这会删除分析记录。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This will delete analysis record.\"\n          }\n        }\n      }\n    },\n    \"这是我今年写的空行的数量。空行，没错，就是只有空格或者什么都没有的那一行。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This is the number of empty lines I wrote this year. Empty lines, yes, the ones that only have spaces or nothing at all.\"\n          }\n        }\n      }\n    },\n    \"这是我最常用的语言。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This is the language I use most often.\"\n          }\n        }\n      }\n    },\n    \"这是我在代码中最常写到的单词，他出现了 %lld 次。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This is the word I most frequently wrote in my code, and it appeared %lld times.\"\n          }\n        }\n      }\n    },\n    \"这是我在代码提交记录中最常写到的单词，他出现了 %lld 次。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"This is the word I most frequently wrote in my commit history, and it appeared %lld times.\"\n          }\n        }\n      }\n    },\n    \"这相当于好几百只 🐳🐳🐳🐳🐳🐳 从我身边游过\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"That’s like several hundred 🐳🐳🐳🐳🐳🐳 swimming past me!\"\n          }\n        }\n      }\n    },\n    \"选择...\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Select…\"\n          }\n        }\n      }\n    },\n    \"邮件地址\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Email Address\"\n          }\n        }\n      }\n    },\n    \"配置排除项\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Configure Excludes\"\n          }\n        }\n      }\n    },\n    \"配置邮箱地址\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Configure Commit Email\"\n          }\n        }\n      }\n    },\n    \"重新开始\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Start Over\"\n          }\n        }\n      }\n    },\n    \"风雨兼程，目的地是我向往的星辰大海。🥺\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"While the world sleep, we dream about the ocean with star. 🥺\"\n          }\n        }\n      }\n    },\n    \"风雨兼程的 Coding 旅途，一天中我最忙碌的时段。\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Was the busy day. Was the busy hour.\"\n          }\n        }\n      }\n    },\n    \"黑客是我的外号，我总能找到属于我的 🚩! 是 🏳️‍⚧️ 还是 🏳️‍🌈 呢？\" : {\n      \"localizations\" : {\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hacker is my nickname—I always manage to claim my own 🚩! But is it 🏳️‍⚧️ or 🏳️‍🌈? 😉\"\n          }\n        }\n      }\n    }\n  },\n  \"version\" : \"1.0\"\n}"
  },
  {
    "path": "MyYearWithGit/App/main.swift",
    "content": "//\n//  main.swift\n//  MyYearWithGit\n//\n//  Created by 秋星桥 on 2024/12/17.\n//\n\nimport Foundation\n\nlet requiredYear = 2025\n\nsetenv(\"GIT_TERMINAL_PROMPT\", \"0\", 1)\nsetenv(\"GIT_LFS_SKIP_SMUDGE\", \"1\", 1)\n\nAuxiliaryExecuteWrapper.setupExecutables()\n\nMyYearWithGitApp.main()\n"
  },
  {
    "path": "MyYearWithGit/Data/Analyser/CommitFilter.swift",
    "content": "//\n//  CommitFilter.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/1.\n//\n\nimport AppKit\nimport Foundation\n\nclass CommitFileFilter {\n    static let shared = CommitFileFilter()\n\n    private init() {\n        if let str = UserDefaults.standard.value(forKey: \"wiki.qaq.block.list\") as? String,\n           let data = str.data(using: .utf8),\n           let object = try? JSONDecoder().decode([BlockItem].self, from: data)\n        {\n            commitBlockList = object\n        }\n    }\n\n    var commitBlockList: [BlockItem] = [] {\n        didSet {\n            if let jsonData = try? JSONEncoder().encode(commitBlockList),\n               let json = String(data: jsonData, encoding: .utf8)\n            {\n                UserDefaults.standard.set(json, forKey: \"wiki.qaq.block.list\")\n            }\n        }\n    }\n\n    enum BlockType: String, Codable, CaseIterable, HumanReadable {\n        case nameKeyWord\n        case nameKeyWordCaseSensitive\n        case pathKeyWord\n        case pathKeyWordCaseSensitive\n        case pathComponentFullMatch\n        case pathComponentFullMatchCaseSensitive\n        case nameRegExFullMatch\n\n        func readableDescription() -> String {\n            switch self {\n            case .nameKeyWord:\n                NSLocalizedString(\"文件名关键词\", comment: \"\")\n            case .nameKeyWordCaseSensitive:\n                NSLocalizedString(\"文件名关键词 匹配大小写\", comment: \"\")\n            case .pathKeyWord:\n                NSLocalizedString(\"路径关键词\", comment: \"\")\n            case .pathKeyWordCaseSensitive:\n                NSLocalizedString(\"路径关键词 匹配大小写\", comment: \"\")\n            case .pathComponentFullMatch:\n                NSLocalizedString(\"路径中存在文件名\", comment: \"\")\n            case .pathComponentFullMatchCaseSensitive:\n                NSLocalizedString(\"路径中存在文件名 匹配大小写\", comment: \"\")\n            case .nameRegExFullMatch:\n                NSLocalizedString(\"文件名正则表达式完整匹配\", comment: \"\")\n            }\n        }\n    }\n\n    typealias Passed = Bool\n    struct BlockItem: Codable, Equatable {\n        let type: BlockType\n        let value: String\n\n        typealias Matched = Bool\n        func match(_ location: URL) -> Matched {\n            switch type {\n            case .nameKeyWord:\n                return location\n                    .lastPathComponent\n                    .lowercased()\n                    .contains(value.lowercased())\n\n            case .nameKeyWordCaseSensitive:\n                return location\n                    .lastPathComponent\n                    .contains(value)\n\n            case .pathKeyWord:\n                return location\n                    .deletingLastPathComponent()\n                    .path\n                    .lowercased()\n                    .contains(value.lowercased())\n\n            case .pathKeyWordCaseSensitive:\n                return location\n                    .deletingLastPathComponent()\n                    .path\n                    .contains(value)\n\n            case .pathComponentFullMatch:\n                let items = location\n                    .deletingLastPathComponent()\n                    .pathComponents\n                    .map { $0.lowercased() }\n                for item in items where item == value.lowercased() {\n                    return true\n                }\n                return false\n\n            case .pathComponentFullMatchCaseSensitive:\n                let items = location\n                    .deletingLastPathComponent()\n                    .pathComponents\n                for item in items where item == value {\n                    return true\n                }\n                return false\n\n            case .nameRegExFullMatch:\n                let val = location\n                    .path\n                    .lowercased()\n                return regExMatch(for: val, in: location.lastPathComponent)\n            }\n        }\n\n        func regExMatch(for regex: String, in text: String) -> Bool {\n            do {\n                let regex = try NSRegularExpression(pattern: regex)\n                let nsString = text as NSString\n                let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))\n                return !results.isEmpty\n            } catch {\n                print(\"invalid regex: \\(error.localizedDescription)\")\n                return false\n            }\n        }\n    }\n\n    func filter(_ location: URL) -> Passed {\n        for blocker in commitBlockList where blocker.match(location) {\n            print(\n                \"\"\"\n                ignoring file \\(location.path) matching rule \\(blocker.type.readableDescription()) \\(blocker.value)\n                \"\"\"\n            )\n            return false\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Analyser/DictionaryBuilder.swift",
    "content": "//\n//  DictionaryBuilder.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\n\nclass DictionaryBuilder {\n    static let sharedIncrease = DictionaryBuilder()\n    static let sharedDecrease = DictionaryBuilder()\n    static let sharedCommit = DictionaryBuilder()\n    private init() {}\n\n    var currentSession = UUID()\n    var dictionary: [String: Int] = [:]\n\n    var trimCounter = 1024\n\n    func beginSession() -> UUID {\n        let session = UUID()\n        currentSession = session\n        dictionary = [:]\n        return session\n    }\n\n    private let lock = NSLock()\n\n    func feed(buffer: String, session: UUID) {\n        lock.lock()\n        defer {\n            lock.unlock()\n        }\n        guard session == currentSession else {\n            return\n        }\n        buffer.enumerateSubstringsByWordsWithCamelCase { substring, _, _, _ in\n            guard let substring,\n                  substring.count > 3,\n                  substring.elegantForDictonary,\n                  Double(substring) == nil\n            else {\n                return\n            }\n            if self.dictionary[substring.lowercased(), default: 0] > 2_147_483_647 {\n                return\n            }\n            self.dictionary[substring.lowercased(), default: 0] += 1\n            self.trimCounter -= 1\n            if self.trimCounter < 0 {\n                self.trimCounter = 1024\n            }\n            if self.trimCounter == 1024 {\n                self.trimMemory()\n            }\n        }\n    }\n\n    func trimMemory() {\n        var currentTrimCount = 0 // why would it be zero? for robust\n        while dictionary.keys.count > 65535 {\n            var currentTrimPassCount = 0\n            for key in dictionary.keys {\n                let value = dictionary[key, default: 0]\n                if value == currentTrimCount {\n                    dictionary.removeValue(forKey: key)\n                    currentTrimPassCount += 1\n                }\n            }\n            currentTrimCount += 1\n            print(\"trimming dictionary passed \\(currentTrimPassCount)\")\n        }\n    }\n\n    func commitSession(session: UUID) -> [String: Int] {\n        guard session == currentSession else {\n            return [:]\n        }\n        let copy = dictionary\n        _ = beginSession()\n        return copy\n    }\n}\n\nprivate let invalidCharacters = [\n    CharacterSet.controlCharacters,\n    CharacterSet.illegalCharacters,\n    CharacterSet.controlCharacters,\n    CharacterSet.whitespacesAndNewlines,\n    CharacterSet.punctuationCharacters,\n    CharacterSet.decimalDigits,\n]\n\nprivate extension String {\n    var elegantForDictonary: Bool {\n        if isEmpty {\n            return false\n        }\n        for charSet in invalidCharacters {\n            if rangeOfCharacter(from: charSet) != nil {\n                return false\n            }\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Analyser/GitDiff.swift",
    "content": "//\n//  GitDiff.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport Foundation\n\nextension RepoAnalyser {\n    struct GitCommitResult: Codable {\n        // these are emails\n        let email: String\n//        let auther: String\n//        let coAuthers: [String]\n        // ...\n        let date: Date\n        // diff analysis\n        let diffFiles: [GitFileDiff]\n        struct GitFileDiff: Codable {\n            // using the latest\n            // eg .c changed to .swift, then we say it is a .swift\n            let language: SourceLanguage?\n            // file mode\n            let mode: DiffMode\n            // the emptyLine counted\n            let emptyLineAdded: Int\n            // add/remove\n            let increasedLine: Int\n            let decreasedLine: Int\n\n            enum DiffMode: String, Codable {\n                case modify\n                case add\n                case delete\n            }\n        }\n    }\n\n    func grabGitCommitDetail(withHash commitHash: String) -> [GitCommitResult.GitFileDiff] {\n        let command = AuxiliaryExecuteWrapper.spawn(\n            command: AuxiliaryExecuteWrapper.git,\n            args: [\n                \"diff\",\n                \"\\(commitHash)^!\",\n            ], timeout: 30\n        ) { _ in\n        }\n        \n        // Check if git diff command failed\n        guard command.0 == 0 else {\n            print(\"git diff failed for commit \\(commitHash), exit code: \\(command.0)\", to: &standardError)\n            if !command.2.isEmpty {\n                print(\"stderr: \\(command.2)\", to: &standardError)\n            }\n            return []\n        }\n        \n        let diff = command\n            .1\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        \n        // Handle empty diff (e.g., merge commit with no changes)\n        guard !diff.isEmpty else {\n            return []\n        }\n        \n        var result = [GitCommitResult.GitFileDiff]()\n\n        var currentFileDiff: GitCommitResult.GitFileDiff?\n        var currentStatus: CurrentStatus = .none\n        var currentBuffer: [String] = []\n        enum CurrentStatus: String {\n            case none\n            case header\n            case body\n        }\n\n        func commitBuffer(str: String) {\n            currentBuffer.append(str)\n        }\n\n        // when commit header, we construct a GitCommitResult.GitFileDiff\n        func commitHeaderForAnalysis() {\n            // get the first line we submit to the buffer\n            // it will either be [\"add\", \"delete\", \"rename\"]\n            // otherwise will be \"index\"\n            guard let decisionLine = currentBuffer.first,\n                  let decisionWord = decisionLine.components(separatedBy: \" \").first\n            else {\n                print(\"no decision can make\")\n                return\n            }\n\n            var language: SourceLanguage?\n            var mode: GitCommitResult.GitFileDiff.DiffMode?\n            var fileName: String?\n            switch decisionWord {\n            case \"index\", \"old\":\n                /*\n                 - 0 : \"old mode 100755\"\n                 - 1 : \"new mode 100644\"\n                 - 2 : \"index a4a32c1c..24295f0c\"\n                 */\n                mode = .modify\n                for line in currentBuffer where line.hasPrefix(\"+++ \") {\n                    var path = String(line.dropFirst(\"+++ \".count))\n                    while path.hasPrefix(\" \") {\n                        path.removeFirst()\n                    }\n                    // Safety check: prevent infinite loop\n                    let originalPathLength = path.count\n                    var removedChars = 0\n                    while !path.hasPrefix(\"/\"), path.count > 0, removedChars < originalPathLength {\n                        path.removeFirst()\n                        removedChars += 1\n                    }\n                    guard path.count > 0, path.hasPrefix(\"/\") else {\n                        print(\"unknown file <\\(line)>\", to: &standardError)\n                        return\n                    }\n//                    print(path)\n                    let url = URL(fileURLWithPath: path)\n                    if !CommitFileFilter.shared.filter(url) { break }\n                    fileName = url.lastPathComponent\n                    language = SourceLanguage\n                        .languageDecision(withFileExtension: url.pathExtension)\n                    break\n                }\n            case \"new\":\n                mode = .add\n                for line in currentBuffer where line.hasPrefix(\"+++ \") {\n                    var path = String(line.dropFirst(\"+++ \".count))\n                    while path.hasPrefix(\" \") {\n                        path.removeFirst()\n                    }\n                    // Safety check: prevent infinite loop\n                    let originalPathLength = path.count\n                    var removedChars = 0\n                    while !path.hasPrefix(\"/\"), path.count > 0, removedChars < originalPathLength {\n                        path.removeFirst()\n                        removedChars += 1\n                    }\n                    guard path.count > 0, path.hasPrefix(\"/\") else {\n                        print(\"unknown file <\\(line)>\", to: &standardError)\n                        return\n                    }\n//                    print(path)\n                    let url = URL(fileURLWithPath: path)\n                    if !CommitFileFilter.shared.filter(url) { break }\n                    fileName = url.lastPathComponent\n                    language = SourceLanguage\n                        .languageDecision(withFileExtension: url.pathExtension)\n                    break\n                }\n            case \"similarity\":\n                /*\n                 eg:\n                 diff --git a/osfmk/corecrypto/ccn/src/ccn_set.c b/iokit/Kernel/IOPMGR.cpp\n                 similarity index 80%\n                 rename from osfmk/corecrypto/ccn/src/ccn_set.c\n                 rename to iokit/Kernel/IOPMGR.cpp\n                 index e288733..4fd29c3 100644\n                 */\n                mode = .modify\n                for line in currentBuffer where line.hasPrefix(\"rename to\") {\n                    var path = String(line.dropFirst(\"rename to\".count))\n                    while path.hasPrefix(\" \") {\n                        path.removeFirst()\n                    }\n                    // Safety check: prevent infinite loop\n                    let originalPathLength = path.count\n                    var removedChars = 0\n                    while !path.hasPrefix(\"/\"), path.count > 0, removedChars < originalPathLength {\n                        path.removeFirst()\n                        removedChars += 1\n                    }\n                    guard path.count > 0, path.hasPrefix(\"/\") else {\n                        print(\"unknown file <\\(line)>\", to: &standardError)\n                        return\n                    }\n//                    print(path)\n                    let url = URL(fileURLWithPath: path)\n                    if !CommitFileFilter.shared.filter(url) { break }\n                    fileName = url.lastPathComponent\n                    language = SourceLanguage\n                        .languageDecision(withFileExtension: url.pathExtension)\n                    break\n                }\n            case \"deleted\":\n                mode = .delete\n                for line in currentBuffer where line.hasPrefix(\"--- \") {\n                    var path = String(line.dropFirst(\"--- \".count))\n                    while path.hasPrefix(\" \") {\n                        path.removeFirst()\n                    }\n                    // Safety check: prevent infinite loop\n                    let originalPathLength = path.count\n                    var removedChars = 0\n                    while !path.hasPrefix(\"/\"), path.count > 0, removedChars < originalPathLength {\n                        path.removeFirst()\n                        removedChars += 1\n                    }\n                    guard path.count > 0, path.hasPrefix(\"/\") else {\n                        print(\"unknown file <\\(line)>\", to: &standardError)\n                        return\n                    }\n//                    print(path)\n                    let url = URL(fileURLWithPath: path)\n                    if !CommitFileFilter.shared.filter(url) { break }\n                    fileName = url.lastPathComponent\n                    language = SourceLanguage\n                        .languageDecision(withFileExtension: url.pathExtension)\n                    break\n                }\n            default:\n                print(\"unknown header [\\(decisionWord)]\", to: &standardError)\n                print(currentBuffer.joined(separator: \" \"), to: &standardError)\n                return\n            }\n            guard fileName != nil, let mode else {\n                return\n            }\n            currentFileDiff = .init(\n                language: language,\n                mode: mode,\n                emptyLineAdded: 0,\n                increasedLine: 0,\n                decreasedLine: 0\n            )\n        }\n\n        // when commit the body, we add up the value in currentFileDiff\n        func commitBodyForAnalysis() {\n            guard currentBuffer.count > 0 else {\n                return\n            }\n            guard let currentDiff = currentFileDiff else {\n                // no header, drop data\n                print(\"missing header, dropping data body\", to: &standardError)\n                return\n            }\n            var emptyLineAdded = currentDiff.emptyLineAdded\n            var increasedLine = currentDiff.increasedLine\n            var decreasedLine = currentDiff.decreasedLine\n            for line in currentBuffer {\n                var line = line\n                if line.hasPrefix(\"+\") {\n                    // added\n                    line.removeFirst()\n                    increasedLine += 1\n                    if line.trimmingCharacters(in: .whitespacesAndNewlines).count < 1 {\n                        emptyLineAdded += 1\n                    }\n                    DictionaryBuilder\n                        .sharedIncrease\n                        .feed(buffer: line, session: dictonaryIncreaseSession)\n                } else if line.hasPrefix(\"-\") {\n                    // removed\n                    line.removeFirst()\n                    decreasedLine += 1\n                    DictionaryBuilder\n                        .sharedIncrease\n                        .feed(buffer: line, session: dictonaryDecreaseSession)\n                } else {\n                    // ignore\n                    continue\n                }\n            }\n\n            currentFileDiff = .init(\n                language: currentDiff.language,\n                mode: currentDiff.mode,\n                emptyLineAdded: emptyLineAdded,\n                increasedLine: increasedLine,\n                decreasedLine: decreasedLine\n            )\n            // clean the body before we double this analysis\n            currentBuffer = []\n        }\n\n        // when commit the barrier, reset the header, push the result if needed\n        func commitBodyBarrier() {\n            commitBodyForAnalysis()\n            // submit to result if needed\n            if let currentFileDiff {\n                result.append(currentFileDiff)\n            }\n            currentFileDiff = nil\n        }\n\n        func commitSwitch(status: CurrentStatus) {\n            // previous status is used for selector\n            let privStatus = currentStatus\n            currentStatus = status\n            switch privStatus {\n            case .none:\n                break\n            case .header:\n                commitHeaderForAnalysis()\n            case .body:\n                commitBodyForAnalysis()\n            }\n            // now we switch the status\n            switch status {\n            case .none:\n                if privStatus == .body {\n                    commitBodyBarrier()\n                    // reset the buffers\n                    currentStatus = .none\n                    currentBuffer = []\n                }\n            case .header:\n                // avoid crashing if two header nested together\n                if privStatus == .body {\n                    commitBodyBarrier()\n                    // now we are in the header\n                    // nothing to do tho\n                    currentStatus = .header\n                    currentBuffer = []\n                }\n            case .body:\n                // commit the header and do the switch\n                currentStatus = .body\n                currentBuffer = []\n            }\n        }\n\n        currentBuffer = []\n        currentStatus = .none\n        for line in diff.components(separatedBy: \"\\n\") {\n            autoreleasepool {\n                if line.hasPrefix(\"diff --git \") || line.hasPrefix(\"diff --cc \") {\n                    commitSwitch(status: .header)\n                    return\n                }\n                if line.hasPrefix(\"@@\") {\n                    commitSwitch(status: .body)\n                    return\n                }\n                commitBuffer(str: line)\n            }\n        }\n        // for the last block\n        commitSwitch(status: .none)\n\n        // final result generator\n\n        return result\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Analyser/GitLog.swift",
    "content": "//\n//  GitLog.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport Foundation\n\nprivate let jsonDecoder = JSONDecoder()\n\nextension RepoAnalyser {\n    struct GitLogElement {\n        let hash: String\n        let autherEmail: String\n        let date: String\n        let note: String\n    }\n\n    func grabGitCommitLog() -> [GitLogElement]? {\n        let command = AuxiliaryExecuteWrapper.spawn(\n            command: AuxiliaryExecuteWrapper.git,\n            args: [\n                \"log\",\n                \"--all\",\n            ], timeout: 0\n        ) { _ in\n        }\n        let output = command\n            .1\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n\n        var results = [GitLogElement]()\n\n        var currentHash: String?\n        var date: String?\n        var autherEmail: String?\n        var lineBuffer = [String]()\n\n        func submitBarrier() {\n            defer {\n                currentHash = nil\n                date = nil\n                autherEmail = nil\n                lineBuffer = []\n            }\n            guard let currentHash,\n                  let date,\n                  let autherEmail\n            else {\n                return\n            }\n            let commitLog = lineBuffer\n                .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                .filter { $0.count > 0 }\n                .joined(separator: \"\\n\")\n            let build = GitLogElement(\n                hash: currentHash,\n                autherEmail: autherEmail,\n                date: date,\n                note: commitLog\n            )\n            results.append(build)\n//            print(build)\n        }\n\n        for line in output.components(separatedBy: \"\\n\") {\n            if line.hasPrefix(\"commit \") {\n                submitBarrier()\n                currentHash = line\n                    .components(separatedBy: \" \")\n                    .last?\n                    .lowercased() // <- LOWER CASE HERE, IT'S HASH\n                continue\n            }\n            if line.hasPrefix(\"Author: \") {\n                let trim = String(line.dropFirst(\"Author:\".count))\n                autherEmail = trim\n                    .components(separatedBy: \"<\")\n                    .last?\n                    .components(separatedBy: \">\")\n                    .first?\n                    .lowercased()\n                continue\n            }\n            if line.hasPrefix(\"Date:\") {\n                var line = String(line.dropFirst(\"Date:\".count))\n                while line.hasPrefix(\" \") {\n                    line.removeFirst()\n                }\n                date = line\n                continue\n            }\n            lineBuffer.append(line)\n        }\n        submitBarrier()\n\n        if results.count == 0 {\n            return nil\n        }\n        return results\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Analyser/GitRepoResult.swift",
    "content": "//\n//  GitRepoResult.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport Foundation\n\nextension RepoAnalyser {\n    struct GitRepoResult: Codable {\n        let commits: [GitCommitResult]\n    }\n\n    func generateResultPackage(with result: ResultPackage.DataSource) -> ResultPackage {\n        let resultPackage = ResultPackage(data: result)\n        resultPackage.update(with: result)\n        return resultPackage\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Analyser/RepoAnalyser.swift",
    "content": "//\n//  RepoAnalyser.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport Foundation\n\n// Sun Apr 19 01:20:44 2020 +0800\n\nprivate let dateFormatters: [DateFormatter] = [\n    \"E MMM d HH:mm:ss yyyy Z\",\n    \"E MMM d HH:mm:ss yyyy\",\n    \"E, d MMM yyyy HH:mm:ss Z\",\n    \"MM-dd-yyyy HH:mm\",\n    \"EEEE, MMM d, yyyy\",\n]\n.map { createFormatter($0) }\n\nprivate func createFormatter(_ str: String) -> DateFormatter {\n    let dateFormatter = DateFormatter()\n    dateFormatter.locale = Locale(identifier: \"en_US_POSIX\")\n    dateFormatter.dateFormat = str\n    return dateFormatter\n}\n\nprivate func decodeDate(_ str: String) -> Date? {\n    for formatter in dateFormatters {\n        if let date = formatter.date(from: str) {\n            return date\n        }\n    }\n    return nil\n}\n\nprivate let calender = Calendar.current\n\nclass RepoAnalyser {\n    static let shared = RepoAnalyser()\n    private init() {}\n\n    var currentSession = UUID()\n    var currentResults = FinalReportCodeable(repos: [])\n    var dictonaryIncreaseSession = UUID()\n    var dictonaryDecreaseSession = UUID()\n    var dictonaryCommitMessage = UUID()\n    var requiredEmails = [String]()\n    var commitHash: Set<String> = []\n\n    struct FinalReportCodeable: Codable {\n        var repos: [GitRepoResult]\n    }\n\n    func beginSession() -> UUID {\n        let session = UUID()\n        currentSession = session\n        currentResults = FinalReportCodeable(repos: [])\n        requiredEmails = []\n        commitHash = []\n        dictonaryIncreaseSession = DictionaryBuilder\n            .sharedIncrease\n            .beginSession()\n        dictonaryDecreaseSession = DictionaryBuilder\n            .sharedDecrease\n            .beginSession()\n        dictonaryCommitMessage = DictionaryBuilder\n            .sharedCommit\n            .beginSession()\n        return session\n    }\n\n    func submitEmails(with: [String]) {\n        requiredEmails = with\n    }\n\n    func analysis(at: URL, session: UUID) {\n        guard session == currentSession else {\n            return\n        }\n        defer {\n            FileManager.default.changeCurrentDirectoryPath(\"/\")\n            // delete the repo after analysis\n            try? FileManager.default.removeItem(at: at)\n        }\n        FileManager.default.changeCurrentDirectoryPath(at.path)\n        // call git log\n\n        guard let currentCommitLogs = grabGitCommitLog() else {\n            print(\"failed to code git log, giving up!\")\n            return\n        }\n        var commitResults = [GitCommitResult]()\n\n        // MARK: MULTI_THREAD\n\n        let lock = NSLock()\n        let queue = DispatchQueue(label: \"wiki.qaq.git\", attributes: .concurrent)\n        let group = DispatchGroup()\n        let boom = DispatchSemaphore(value: processCount)\n\n        for currentCommitLog in currentCommitLogs {\n            let currentCommitLog = currentCommitLog\n            guard requiredEmails.contains(currentCommitLog.autherEmail),\n                  let date = decodeDate(currentCommitLog.date),\n                  let year = calender.dateComponents([.year], from: date).year,\n                  year == requiredYear\n            else {\n                continue\n            }\n            guard !commitHash.contains(currentCommitLog.hash) else {\n                continue\n            }\n            commitHash.insert(currentCommitLog.hash)\n            autoreleasepool {\n                // MARK: MULTI_THREAD\n\n                group.enter()\n                boom.wait()\n\n                // MARK: EXEC\n\n                queue.async {\n                    print(\"calling analysis on commit \\(currentCommitLog.hash) with \\(currentCommitLog.autherEmail)\")\n                    let commitDiffs = self.grabGitCommitDetail(withHash: currentCommitLog.hash)\n                    let commitResult = GitCommitResult(\n                        email: currentCommitLog.autherEmail,\n                        date: date,\n                        diffFiles: commitDiffs\n                    )\n\n                    // DictionaryBuilder has it's own lock when feeding data\n                    DictionaryBuilder\n                        .sharedCommit\n                        .feed(buffer: currentCommitLog.note, session: self.dictonaryCommitMessage)\n\n                    lock.lock()\n                    commitResults.append(commitResult)\n                    lock.unlock()\n\n                    // MARK: MULTI_THREAD\n\n                    boom.signal()\n                    group.leave()\n                }\n            }\n        }\n        guard commitResults.count > 0 else {\n            print(\"no data was generated from repo \\(at.path), skipping submit\")\n            return\n        }\n        print(\"analysis completed, submitting \\(commitResults.count) result to repo\")\n        let repoResult = GitRepoResult(commits: commitResults)\n        currentResults.repos.append(repoResult)\n    }\n\n    func commitResult() -> ResultPackage {\n        print(\"compiling result for \\(currentResults.repos.count) repos\")\n        let dataSource = ResultPackage.DataSource(\n            repoResult: currentResults,\n            dictionaryIncrease: DictionaryBuilder\n                .sharedIncrease\n                .commitSession(session: dictonaryIncreaseSession),\n            dictionaryDecrease: DictionaryBuilder\n                .sharedDecrease\n                .commitSession(session: dictonaryDecreaseSession),\n            dictionaryCommit: DictionaryBuilder\n                .sharedCommit\n                .commitSession(session: dictonaryCommitMessage)\n        )\n        let resultPackage = generateResultPackage(with: dataSource)\n        _ = beginSession() // clear everything\n        return resultPackage\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Api/ApiProtocol.swift",
    "content": "//\n//  ApiProtocol.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/5.\n//\n\nimport Foundation\n\nprotocol GitApi {\n    func validate() throws\n    func repositories() throws -> [URL]\n}\n\nenum ApiError: Error, LocalizedError {\n    case emptyData\n    case invalidUrl\n    case invalidUser\n    case network(reason: String)\n    case statusCode(code: Int)\n    case response(reason: String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .emptyData:\n            \"未找到有效的数据，请再试一次。\"\n        case .invalidUrl:\n            \"错误的 URL 数据，请再试一次。\"\n        case .invalidUser:\n            \"无效的用户信息，请再试一次。\"\n        case let .network(reason):\n            \"无法连接到服务器: \\(reason)\"\n        case let .statusCode(code):\n            \"服务器返回未知状态: \\(code) \\(HTTPURLResponse.localizedString(forStatusCode: code))\"\n        case let .response(reason):\n            \"服务器返回错误信息：\\(reason)\"\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Api/BitbucketApi.swift",
    "content": "//\n//  BitbucketApi.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/3.\n//\n\nimport Foundation\n\nprivate let bbEndpoint = URL(string: \"https://api.bitbucket.org/\")!\nprivate let bbCloneBase = URL(string: \"https://bitbucket.org/\")!\n\nclass BitbucketApi: GitApi {\n    let config: Config\n    struct Config {\n        let token: String\n    }\n\n    // user info from validation\n    var userId: String?\n    var userName: String?\n\n    init(config: Config) {\n        self.config = config\n    }\n\n    // nil = invalid, otherwise \"\" but not nil\n    func validate() throws {\n        let userRequest = createRequest(withPaths: [\n            \"user\",\n        ])\n        var result = false\n        var error: Error?\n        let sem = DispatchSemaphore(value: 0)\n        URLSession\n            .shared\n            .dataTask(with: userRequest) { data, resp, err in\n                defer { sem.signal() }\n\n                // connection error\n                if let err {\n                    error = ApiError.network(reason: err.localizedDescription)\n                    return\n                }\n\n                // unknown status code\n                if let resp = resp as? HTTPURLResponse, resp.statusCode != 200 {\n                    error = ApiError.statusCode(code: resp.statusCode)\n                }\n\n                // json decodable as dict\n                guard let data,\n                      let dic = try? JSONSerialization\n                      .jsonObject(\n                          with: data, options: .allowFragments\n                      ) as? [String: Any]\n                else {\n                    return\n                }\n\n                // not an error\n                if error == nil {\n                    if let name = dic[\"username\"] as? String {\n                        self.userName = name\n                    }\n                    if let id = dic[\"account_id\"] as? String {\n                        self.userId = id\n                        result = true\n                    }\n                }\n\n                // server has detailed error description or messages\n                if let errorDict = dic[\"error\"] as? [String: Any] {\n                    if let errorDetail = errorDict[\"detail\"] as? String,\n                       let errorMessage = errorDict[\"message\"] as? String\n                    {\n                        error = ApiError.response(reason: \"\\(errorDetail) (\\(errorMessage))\")\n                    }\n                }\n            }\n            .resume()\n        let emailRequest = createRequest(withPaths: [\n            \"user\",\n            \"emails\",\n        ])\n        URLSession\n            .shared\n            .dataTask(with: emailRequest) { data, resp, err in\n                defer { sem.signal() }\n\n                // connection error\n                if let err {\n                    error = ApiError.network(reason: err.localizedDescription)\n                    return\n                }\n\n                // unknown status code\n                if let resp = resp as? HTTPURLResponse, resp.statusCode != 200 {\n                    error = ApiError.statusCode(code: resp.statusCode)\n                }\n\n                // json decodable as dict\n                guard let data,\n                      let dic = try? JSONSerialization\n                      .jsonObject(\n                          with: data, options: .allowFragments\n                      ) as? [String: Any]\n                else {\n                    return\n                }\n\n                // not an error\n                if error == nil {\n                    if let emailValues = dic[\"values\"] as? [[String: Any]] {\n                        emailValues\n                            .compactMap { $0[\"email\"] as? String }\n                            .forEach { User.current.email.insert($0) }\n                    }\n                }\n\n                // server has detailed error description or messages\n                if let errorDict = dic[\"error\"] as? [String: Any] {\n                    if let errorDetail = errorDict[\"detail\"] as? String,\n                       let errorMessage = errorDict[\"message\"] as? String\n                    {\n                        error = ApiError.response(reason: \"\\(errorDetail) (\\(errorMessage))\")\n                    }\n                }\n            }.resume()\n\n        sem.wait() // URLSession default timeout is 60 * 2 = 120 secs\n\n        if let error {\n            throw error\n        }\n\n        sem.wait()\n\n        if let error {\n            throw error\n        }\n\n        guard result else {\n            throw ApiError.invalidUser\n        }\n    }\n\n    func repositories() throws -> [URL] {\n        var result = Set<URL>()\n        for workspace in try workspaces() {\n            var endpoints = Set<URL>(arrayLiteral: workspace)\n            while let endpoint = endpoints.popFirst() {\n                var error: Error?\n                let sem = DispatchSemaphore(value: 0)\n                URLSession\n                    .shared\n                    .dataTask(with: createRequest(withURL: endpoint)) { data, resp, err in\n                        defer { sem.signal() }\n\n                        // connection error\n                        if let err {\n                            error = ApiError.network(reason: err.localizedDescription)\n                            return\n                        }\n\n                        // unknown status code\n                        if let resp = resp as? HTTPURLResponse, resp.statusCode != 200 {\n                            error = ApiError.statusCode(code: resp.statusCode)\n                        }\n\n                        // json decodable as dict\n                        guard let data,\n                              let dic = try? JSONSerialization\n                              .jsonObject(\n                                  with: data, options: .allowFragments\n                              ) as? [String: Any]\n                        else {\n                            return\n                        }\n\n                        // not an error\n                        if error == nil {\n                            if let retValues = dic[\"values\"] as? [[String: Any]] {\n                                let repoHrefs = retValues.compactMap { retVal in\n                                    ((retVal[\"links\"] as? [String: Any])?[\"clone\"] as? [[String: String]])?.first(where: { $0[\"name\"] == \"https\" })?[\"href\"]\n                                }\n                                let repoURLs = repoHrefs.compactMap { URL(string: $0) }\n                                result.formUnion(repoURLs)\n                            }\n                            if let nextHref = dic[\"next\"] as? String, let nextURL = URL(string: nextHref) {\n                                endpoints.insert(nextURL)\n                            }\n                        }\n\n                        // server has detailed error description or messages\n                        if let errorDict = dic[\"error\"] as? [String: Any] {\n                            if let errorDetail = errorDict[\"detail\"] as? String,\n                               let errorMessage = errorDict[\"message\"] as? String\n                            {\n                                error = ApiError.response(reason: \"\\(errorDetail) (\\(errorMessage))\")\n                            }\n                        }\n                    }.resume()\n                sem.wait()\n                if let error {\n                    throw error\n                }\n            }\n        }\n        guard result.count > 0 else {\n            throw ApiError.emptyData\n        }\n        return [URL](result).map { bbCloneBase.appendingPathComponent(String($0.path.dropFirst())) }\n    }\n\n    func workspaces() throws -> [URL] {\n        var endpoints = Set<URL>(arrayLiteral: bbEndpoint.appendingPathComponent(\"2.0\").appendingPathComponent(\"workspaces\"))\n        var result = Set<URL>()\n        while let endpoint = endpoints.popFirst() {\n            var error: Error?\n            let sem = DispatchSemaphore(value: 0)\n            URLSession\n                .shared\n                .dataTask(with: createRequest(withURL: endpoint)) { data, resp, err in\n                    defer { sem.signal() }\n\n                    // connection error\n                    if let err {\n                        error = ApiError.network(reason: err.localizedDescription)\n                        return\n                    }\n\n                    // unknown status code\n                    if let resp = resp as? HTTPURLResponse, resp.statusCode != 200 {\n                        error = ApiError.statusCode(code: resp.statusCode)\n                    }\n\n                    // json decodable as dict\n                    guard let data,\n                          let dic = try? JSONSerialization\n                          .jsonObject(\n                              with: data, options: .allowFragments\n                          ) as? [String: Any]\n                    else {\n                        return\n                    }\n\n                    // not an error\n                    if error == nil {\n                        if let retValues = dic[\"values\"] as? [[String: Any]] {\n                            let repoHrefs = retValues.compactMap { (($0[\"links\"] as? [String: Any])?[\"repositories\"] as? [String: String])?[\"href\"] }\n                            let repoURLs = repoHrefs.compactMap { URL(string: $0) }\n                            result.formUnion(repoURLs)\n                        }\n                        if let nextHref = dic[\"next\"] as? String, let nextURL = URL(string: nextHref) {\n                            endpoints.insert(nextURL)\n                        }\n                    }\n\n                    // server has detailed error description or messages\n                    if let errorDict = dic[\"error\"] as? [String: Any] {\n                        if let errorDetail = errorDict[\"detail\"] as? String,\n                           let errorMessage = errorDict[\"message\"] as? String\n                        {\n                            error = ApiError.response(reason: \"\\(errorDetail) (\\(errorMessage))\")\n                        }\n                    }\n                }.resume()\n            sem.wait()\n            if let error {\n                throw error\n            }\n        }\n        guard result.count > 0 else {\n            throw ApiError.emptyData\n        }\n        return [URL](result)\n    }\n\n    func createURL(withPaths paths: [String], withParameters parameters: [String: String]? = nil) -> URL {\n        var endpoint = bbEndpoint\n            .appendingPathComponent(\"2.0\")\n        for path in paths {\n            endpoint.appendPathComponent(path)\n        }\n        if let parameters {\n            endpoint = endpoint.appendingQueryParameters(parameters)\n        }\n        return endpoint\n    }\n\n    func createRequest(withPaths paths: [String], withParameters parameters: [String: String]? = nil) -> URLRequest {\n        createRequest(withURL: createURL(withPaths: paths, withParameters: parameters))\n    }\n\n    func createRequest(withURL url: URL) -> URLRequest {\n        let endpoint = url\n        let basicToken = config.token.data(using: .utf8)!.base64EncodedString()\n        var request = URLRequest(url: endpoint)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Basic \\(basicToken)\", forHTTPHeaderField: \"Authorization\")\n        return request\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Api/GitHubApi.swift",
    "content": "//\n//  GitHubApi.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/5.\n//\n\nimport Foundation\nimport OctoKit\nimport RequestKit\n\nclass GitHubApi: GitApi {\n    let config: Config\n    struct Config {\n        let token: String\n    }\n\n    init(config: Config) {\n        self.config = config\n    }\n\n    var api: Octokit?\n\n    func checkError(_ err: Error?) -> Error? {\n        var error: Error?\n        let errObj = (err as NSError?)?.userInfo[RequestKit.RequestKitErrorKey]\n        if let errObj = errObj as? [String: Any],\n           let errMsg = errObj[\"message\"] as? String\n        {\n            if let errDoc = errObj[\"documentation_url\"] as? String {\n                error = ApiError.response(reason: \"\\(errMsg) (See: \\(errDoc))\")\n            } else {\n                error = ApiError.response(reason: errMsg)\n            }\n        } else if let errObj = errObj as? String {\n            error = ApiError.response(reason: errObj)\n        } else if let err = err as? URLError {\n            error = ApiError.network(reason: err.localizedDescription)\n        } else {\n            error = err\n        }\n        return error\n    }\n\n    func validate() throws {\n        let config = TokenConfiguration(config.token)\n        var validated = false\n        let sem = DispatchSemaphore(value: 0)\n        let core = Octokit(config)\n        var error: Error?\n        core.me { [weak self] response in\n            defer { sem.signal() }\n            switch response {\n            case let .success(userObject):\n                validated = true\n                if let email = userObject.email {\n                    User.current.email.insert(email)\n                }\n            case let .failure(err):\n                error = self?.checkError(err)\n            }\n        }\n        sem.wait()\n        if let error {\n            throw error\n        }\n        if validated {\n            api = core\n        }\n        guard validated else {\n            throw ApiError.invalidUser\n        }\n    }\n\n    func repositories() throws -> [URL] {\n        guard let api else {\n            return []\n        }\n        var result: Set<URL> = []\n        var page = 1\n        repeat {\n            do {\n                let repos = try repositoryFor(page: page, api: api)\n                repos.forEach { result.insert($0) }\n                if repos.count > 0, page <= 65535 {\n                    page += 1\n                } else {\n                    page = -1\n                }\n            } catch {\n                throw error\n            }\n        } while page != -1\n        return [URL](result)\n    }\n\n    func repositoryFor(page: Int, api: Octokit) throws -> [URL] {\n        var result: Set<URL> = []\n        let sem = DispatchSemaphore(value: 0)\n        var error: Error?\n        api.repositories(page: String(page), perPage: \"100\") { [weak self] response in\n            defer { sem.signal() }\n            switch response {\n            case let .success(repository):\n                for repo in repository {\n                    guard let location = repo.cloneURL,\n                          let url = URL(string: location)\n                    else {\n                        continue\n                    }\n                    result.insert(url)\n                }\n            case let .failure(err):\n                error = self?.checkError(err)\n            }\n        }\n        sem.wait()\n        if let error {\n            throw error\n        }\n        print(\"GitHub repository for page \\(page) returned \\(result.count) results\")\n        return [URL](result)\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Api/GitLabApi.swift",
    "content": "//\n//  GitLabApi.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport Foundation\n\nclass GitLabApi: GitApi {\n    let config: Config\n    struct Config {\n        let endpoint: URL\n        let token: String\n    }\n\n    // user info from validation\n    var userId: Int?\n    var userName: String?\n\n    init(config: Config) {\n        self.config = config\n    }\n\n    // nil = invalid, otherwise \"\" but not nil\n    func validate() throws {\n        let request = createRequest(withPaths: [\n            \"user\",\n        ])\n        var result = false\n        var error: Error?\n        let sem = DispatchSemaphore(value: 0)\n        URLSession\n            .shared\n            .dataTask(with: request) { data, resp, err in\n                defer { sem.signal() }\n\n                // connection error\n                if let err {\n                    error = ApiError.network(reason: err.localizedDescription)\n                    return\n                }\n\n                // unknown status code\n                if let resp = resp as? HTTPURLResponse, resp.statusCode != 200 {\n                    error = ApiError.statusCode(code: resp.statusCode)\n                }\n\n                // json decodable as dict\n                guard let data,\n                      let dic = try? JSONSerialization\n                      .jsonObject(\n                          with: data, options: .allowFragments\n                      ) as? [String: Any]\n                else {\n                    return\n                }\n\n                // not an error\n                if error == nil {\n                    if let name = dic[\"name\"] as? String {\n                        self.userName = name\n                    }\n                    if let id = dic[\"id\"] as? Int {\n                        self.userId = id\n                        result = true\n                    }\n                    if let email = dic[\"email\"] as? String {\n                        User.current.email.insert(email)\n                    }\n                    if let email = dic[\"commit_email\"] as? String {\n                        User.current.email.insert(email)\n                    }\n                }\n\n                // server has detailed error description or messages\n                if let errorVal = dic[\"error\"] as? String,\n                   let errorReason = dic[\"error_description\"] as? String\n                {\n                    error = ApiError.response(reason: \"\\(errorReason) (\\(errorVal))\")\n                } else if let errorMessage = dic[\"message\"] as? String {\n                    error = ApiError.response(reason: \"\\(errorMessage)\")\n                }\n            }\n            .resume()\n        sem.wait()\n\n        if let error {\n            throw error\n        }\n\n        guard result else {\n            throw ApiError.invalidUser\n        }\n    }\n\n    func repositories() throws -> [URL] {\n        var endpoints = Set<URL>(arrayLiteral: createURL(\n            withPaths: [\n                \"projects\",\n            ],\n            withParameters: [\n                \"min_access_level\": \"30\", // developer access level\n                \"pagination\": \"keyset\",\n                \"per_page\": \"100\",\n                \"order_by\": \"id\",\n                \"sort\": \"asc\",\n            ]\n        ))\n\n        var result = [URL]()\n        var error: Error?\n        while let endpoint = endpoints.popFirst() {\n            let sem = DispatchSemaphore(value: 0)\n            URLSession\n                .shared\n                .dataTask(with: createRequest(withURL: endpoint)) { [weak self] data, resp, err in\n                    defer { sem.signal() }\n\n                    // connection error\n                    if let err {\n                        error = ApiError.network(reason: err.localizedDescription)\n                        return\n                    }\n\n                    // unknown status code\n                    if let resp = resp as? HTTPURLResponse, resp.statusCode != 200 {\n                        error = ApiError.statusCode(code: resp.statusCode)\n                    }\n\n                    // json decodable as arr\n                    guard let data,\n                          let dic = try? JSONSerialization\n                          .jsonObject(\n                              with: data, options: .allowFragments\n                          ) as? [[String: Any]]\n                    else {\n                        return\n                    }\n\n                    // not an error\n                    if error == nil {\n                        for value in dic {\n                            if let gitUrlStr = value[\"http_url_to_repo\"] as? String,\n                               let gitUrl = URL(string: gitUrlStr)\n                            {\n                                result.append(gitUrl)\n                            }\n                        }\n                        if let resp = resp as? HTTPURLResponse,\n                           let linkVal = resp.value(forHTTPHeaderField: \"Link\")?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines),\n                           linkVal.hasSuffix(\"rel=\\\"next\\\"\")\n                        {\n                            var linkHref = String(linkVal.dropLast(\"rel=\\\"next\\\"\".count))\n                                .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)\n                            if linkHref.hasPrefix(\"<\") {\n                                linkHref = String(linkHref.dropFirst(\"<\".count))\n                            }\n                            if linkHref.hasSuffix(\">;\") {\n                                linkHref = String(linkHref.dropLast(\">;\".count))\n                            }\n                            if let linkURL = URL(string: linkHref),\n                               let linkQuery = linkURL.queryParameters,\n                               let replURL = self?.config.endpoint.appendingPathComponent(String(linkURL.path.dropFirst())).appendingQueryParameters(linkQuery)\n                            {\n                                endpoints.insert(replURL)\n                            }\n                        }\n                    }\n                }\n                .resume()\n            sem.wait()\n        }\n\n        if let error {\n            throw error\n        }\n\n        guard result.count > 0 else {\n            throw ApiError.emptyData\n        }\n        return result\n    }\n\n    func createURL(withPaths paths: [String], withParameters parameters: [String: String]? = nil) -> URL {\n        var endpoint = config\n            .endpoint\n            .appendingPathComponent(\"api\")\n            .appendingPathComponent(\"v4\")\n        for path in paths {\n            endpoint.appendPathComponent(path)\n        }\n        if let parameters {\n            endpoint = endpoint.appendingQueryParameters(parameters)\n        }\n        return endpoint\n    }\n\n    func createRequest(withPaths paths: [String], withParameters parameters: [String: String]? = nil) -> URLRequest {\n        createRequest(withURL: createURL(withPaths: paths, withParameters: parameters))\n    }\n\n    func createRequest(withURL url: URL) -> URLRequest {\n        let endpoint = url\n        var request = URLRequest(url: endpoint)\n        request.httpMethod = \"GET\"\n        request.setValue(config.token, forHTTPHeaderField: \"PRIVATE-TOKEN\")\n        return request\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/AuxiliaryExecute.swift",
    "content": "//\n//  AuxiliaryExecute.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport AuxiliaryExecute\nimport Foundation\n\nenum AuxiliaryExecuteWrapper {\n    private(set) static var git: String = \"/usr/bin/git\"\n\n    static func setupExecutables() {\n        var binaryLookupTable = [String: URL]()\n        let binarySearchPath = [\n            \"/usr/local/bin\",\n            \"/usr/bin\",\n            \"/bin\",\n        ]\n        var searchPaths = binarySearchPath.map { URL(fileURLWithPath: $0) }\n        while !searchPaths.isEmpty {\n            let path = searchPaths.removeFirst()\n            \n            var isDir = ObjCBool(false)\n            guard FileManager.default.fileExists(atPath: path.path, isDirectory: &isDir) else {\n                continue\n            }\n            if isDir.boolValue {\n                let items = (try? FileManager.default.contentsOfDirectory(atPath: path.path)) ?? []\n                for item in items {\n                    let url = path.appendingPathComponent(item)\n                    searchPaths.append(url)\n                }\n            } else {\n                binaryLookupTable[path.lastPathComponent] = path\n            }\n        }\n\n        if let git = binaryLookupTable[\"git\"] {\n            self.git = git.path\n            print(\"setting up binary git at \\(git.path)\")\n        } else {\n            fatalError()\n        }\n    }\n\n    @discardableResult\n    static func spawn(command: String,\n                      args: [String],\n                      timeout: Int,\n                      output: @escaping (String) -> Void)\n        -> (Int, String, String)\n    {\n        print(\"exec: \\(command) \" + args.joined(separator: \" \") + \" timeout: \\(timeout)\")\n        let recipe = AuxiliaryExecute.spawn(\n            command: command,\n            args: args,\n            timeout: Double(timeout),\n            output: output\n        )\n        return (recipe.exitCode, recipe.stdout, recipe.stderr)\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/Exts.swift",
    "content": "//\n//  Exts.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport Foundation\n\nprotocol HumanReadable {\n    func readableDescription() -> String\n}\n\nvar standardError = FileHandle.standardError\n\nextension FileHandle: @retroactive TextOutputStream {\n    public func write(_ string: String) {\n        let data = Data(string.utf8)\n        write(data)\n    }\n}\n\nextension URL {\n    var queryParameters: [String: String]? {\n        guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false),\n              let queryItems = components.queryItems else { return nil }\n\n        var items: [String: String] = [:]\n\n        for queryItem in queryItems {\n            items[queryItem.name] = queryItem.value\n        }\n\n        return items\n    }\n\n    func appendingQueryParameters(_ parameters: [String: String]) -> URL {\n        var urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: true)!\n        urlComponents.queryItems = (urlComponents.queryItems ?? []) + parameters\n            .map { URLQueryItem(name: $0, value: $1) }\n        return urlComponents.url!\n    }\n}\n\nextension String {\n    private static let camelCaseRegex = try! NSRegularExpression(\n        pattern: \"([a-z](?=[A-Z]))\",\n        options: []\n    )\n\n    func enumerateSubstringsByWordsWithCamelCase(\n        _ body: @escaping (\n            _ substring: String?,\n            _ substringRange: Range<Self.Index>,\n            _ enclosingRange: Range<Self.Index>,\n            inout Bool\n        ) -> Void\n    ) -> Void {\n        var processingBuffer = self\n        processingBuffer = processingBuffer.replacingOccurrences(of: \".\", with: \" \")\n        processingBuffer = processingBuffer.replacingOccurrences(of: \"-\", with: \" \")\n        processingBuffer = processingBuffer.replacingOccurrences(of: \"_\", with: \" \")\n        processingBuffer = String.camelCaseRegex.stringByReplacingMatches(\n            in: processingBuffer,\n            options: [],\n            range: NSRange(location: 0, length: processingBuffer.utf16.count),\n            withTemplate: \"$1 \"\n        )\n        processingBuffer = processingBuffer.lowercased()\n        let newBody: (\n            _ substring: String?,\n            _ substringRange: Range<Self.Index>,\n            _ enclosingRange: Range<Self.Index>,\n            inout Bool\n        ) -> Void = { (substring, substringRange, enclosingRange, stop) in\n            let newSubstring = substring?.lowercased()\n            body(newSubstring, substringRange, enclosingRange, &stop)\n        }\n        processingBuffer.enumerateSubstrings(\n            in: processingBuffer.startIndex...,\n            options: .byWords,\n            newBody\n        )\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/SourceLanguage.swift",
    "content": "//\n//  SourceLanguage.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport Foundation\n\nenum SourceLanguage: String, Codable, HumanReadable {\n    static func languageDecision(withFileExtension pathExtension: String) -> Self? {\n        switch pathExtension.lowercased() {\n        case \"c\":\n            .c\n        case \"cs\", \"csharp\":\n            .csharp\n        case \"cpp\", \"cc\", \"cxx\", \"c++\":\n            .cpp\n        case \"css\":\n            .css\n        case \"dart\":\n            .dart\n        case \"ex\":\n            .elixir\n        case \"go\":\n            .go\n        case \"groovy\", \"gvy\", \"gy\", \"gsh\":\n            .groovy\n        case \"html\", \"htm\":\n            .html\n        case \"java\", \"jav\", \"j\":\n            .java\n        case \"js\", \"jsx\":\n            .javascript\n        case \"kt\", \"ktm\":\n            .kotlin\n        case \"m\", \"mm\":\n            .objc\n        case \"pl\":\n            .perl\n        case \"php\":\n            .php\n        case \"ps1\":\n            .powershell\n        case \"py\":\n            .python\n        case \"rb\":\n            .rbuy\n        case \"rs\":\n            .rust\n        case \"scala\", \"sc\":\n            .scala\n        case \"sh\", \"command\":\n            .shell\n        case \"swift\":\n            .swift\n        case \"ts\", \"tsx\":\n            .typescript\n        case \"vue\":\n            .vue\n        default:\n            nil\n        }\n    }\n\n    case c\n    case csharp\n    case cpp\n    case css\n    case dart\n    case elixir\n    case go\n    case groovy\n    case html\n    case java\n    case javascript\n    case kotlin\n    case objc\n    case perl\n    case php\n    case powershell\n    case python\n    case rbuy\n    case rust\n    case scala\n    case shell\n    case swift\n    case typescript\n    case vue\n\n    func readableDescription() -> String {\n        switch self {\n        case .c:\n            \"C\"\n        case .csharp:\n            \"C#\"\n        case .cpp:\n            \"C++\"\n        case .css:\n            \"CSS\"\n        case .dart:\n            \"Dart\"\n        case .elixir:\n            \"Elixir\"\n        case .go:\n            \"Go\"\n        case .groovy:\n            \"Groovy\"\n        case .html:\n            \"HTML\"\n        case .java:\n            \"Java\"\n        case .javascript:\n            \"JavaScript\"\n        case .kotlin:\n            \"Kotlin\"\n        case .objc:\n            \"Objective-C\"\n        case .perl:\n            \"Perl\"\n        case .php:\n            \"PHP\"\n        case .powershell:\n            \"PowerShell\"\n        case .python:\n            \"Python\"\n        case .rbuy:\n            \"Ruby\"\n        case .rust:\n            \"Rust\"\n        case .scala:\n            \"Scala\"\n        case .shell:\n            \"Shell\"\n        case .swift:\n            \"Swift\"\n        case .typescript:\n            \"TypeScript\"\n        case .vue:\n            \"Vue\"\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/SourcePackage.swift.swift",
    "content": "//\n//  SourcePackage.swift.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport Foundation\n\nextension Notification.Name {\n    static let postAnalysis = Notification.Name(\"wiki.qaq.source.done\")\n}\n\nstruct SourcePackage {\n    let tempDir: URL\n    let representedObjects: [SourceRegistrationData]\n\n    init(sources: [SourceRegistrationData]) {\n        tempDir = {\n            let tempDir = NSTemporaryDirectory()\n            let uuid = UUID().uuidString\n            let location = URL(fileURLWithPath: tempDir)\n                .appendingPathComponent(\"wiki.qaq.myyearwithgit\")\n                .appendingPathComponent(uuid)\n            try? FileManager.default.removeItem(at: location)\n            do {\n                try FileManager\n                    .default\n                    .createDirectory(\n                        at: location,\n                        withIntermediateDirectories: true,\n                        attributes: nil\n                    )\n            } catch {\n                fatalError(\"error when creating analysis temp bundle \\(error.localizedDescription)\")\n            }\n            return location\n        }()\n        representedObjects = sources\n    }\n\n    func postToAnalysis() {\n        NotificationCenter.default.post(name: .postAnalysis, object: self)\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/SourceRegister.swift",
    "content": "//\n//  SourceRegister.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport Foundation\n\nenum SourceRegisters: String, CaseIterable, Codable, HumanReadable {\n    // MARK: CASE\n\n    case local\n    case github\n    case gitlab\n    case bitbucket\n\n    // MARK: PROTOCOL\n\n    func readableDescription() -> String {\n        switch self {\n        case .local:\n            NSLocalizedString(\"本地仓库\", comment: \"\")\n        case .github:\n            \"GitHub\"\n        case .gitlab:\n            \"GitLab\"\n        case .bitbucket:\n            \"Bitbucket\"\n        }\n    }\n}\n\nstruct SourceRegistrationData: Codable, Identifiable, Hashable {\n    var id = UUID()\n\n    static func == (lhs: SourceRegistrationData, rhs: SourceRegistrationData) -> Bool {\n        lhs.id == rhs.id\n    }\n\n    let register: SourceRegisters\n    let mainUrl: URL\n    let repos: [RepoElement]\n\n    struct RepoElement: Codable, Hashable {\n        var representedData: [RepresentedKeys: String]\n\n        init(localUrl: URL) {\n            representedData = [\n                .localUrl: localUrl.path,\n                .identifier: UUID().uuidString,\n            ]\n        }\n\n        init(remoteUrl: URL, username: String, token: String) {\n            representedData = [\n                .remoteUrl: remoteUrl.absoluteString,\n                .username: username,\n                .token: token,\n                .identifier: UUID().uuidString,\n            ]\n        }\n\n        enum RepresentedKeys: String, Codable {\n            case remoteUrl\n            case localUrl\n\n            case username\n            case token\n\n            case identifier\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/User.swift",
    "content": "//\n//  User.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/4.\n//\n\nimport Foundation\n\nprivate let userDefaultKey = \"wiki.qaq.mywg.emails\"\n\nclass User {\n    static let current = User()\n\n    public var email: Set<String> = [] {\n        didSet {\n            if let data = try? JSONEncoder().encode(email) {\n                UserDefaults.standard.set(data, forKey: userDefaultKey)\n            }\n        }\n    }\n\n    @UserDefaultsWrapper(key: \"wiki.qaq.mywg.name\", defaultValue: \"anonymous\")\n    public var namespace: String\n\n    private init() {\n        if let data = UserDefaults.standard.value(forKey: userDefaultKey) as? Data,\n           let build = try? JSONDecoder().decode([String].self, from: data)\n        {\n            email = Set<String>(build)\n        }\n        let command = AuxiliaryExecuteWrapper.spawn(\n            command: AuxiliaryExecuteWrapper.git,\n            args: [\"config\", \"user.email\"],\n            timeout: 1\n        ) { str in\n            print(str)\n        }\n        let gitEmail = command\n            .1\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        if gitEmail.count > 0 {\n            print(\"found email: \\(gitEmail)\")\n            email.insert(gitEmail)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/Generic/UserDefault.swift.swift",
    "content": "//\n//  UserDefault.swift.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/4.\n//\n\nimport Foundation\n\n@propertyWrapper\npublic struct UserDefaultsWrapper<Value> {\n    let key: String\n    let defaultValue: Value\n    var storage: UserDefaults = .standard\n\n    public init(key: String, defaultValue: Value, storage: UserDefaults = .standard) {\n        self.key = key\n        self.defaultValue = defaultValue\n        self.storage = storage\n    }\n\n    public var wrappedValue: Value {\n        get {\n            let value = storage.value(forKey: key) as? Value\n            return value ?? defaultValue\n        }\n        set {\n            storage.setValue(newValue, forKey: key)\n        }\n    }\n}\n\npublic extension UserDefaultsWrapper where Value: ExpressibleByNilLiteral {\n    init(key: String, storage: UserDefaults = .standard) {\n        self.init(key: key, defaultValue: nil, storage: storage)\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/DicCounter.swift",
    "content": "//\n//  DicCounter.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\n\nenum DicCounter {\n    static func mostUsedKeyword<T>(from dic: [T: Int]) -> (T, Int)? {\n        var searchKey: T?\n        var searchKeyCount = -1\n        for key in dic.keys {\n            let count = dic[key, default: 0]\n            if count > searchKeyCount {\n                searchKey = key\n                searchKeyCount = count\n            }\n        }\n        guard let key = searchKey else {\n            return nil\n        }\n        return (key, searchKeyCount)\n    }\n\n    static func mostUsedKeywords(from dic: [String: Int], count: Int) -> [String] {\n        var resultBuilder = [(String, Int)](repeating: (\"\", -1), count: count)\n        for key in dic.keys {\n            let count = dic[key, default: 0]\n            inner: for index in 0 ..< resultBuilder.count where resultBuilder[index].1 < count {\n                resultBuilder[index] = (key, count)\n                break inner\n            }\n        }\n        return resultBuilder\n            .filter { $0.1 > 0 }\n            .map(\\.0)\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS0.swift",
    "content": "//\n//  RS0.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection0: ResultSection {\n    var reportHash: String = \"\"\n    var clipDataItems: [Int: Int] = [:]\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        clipDataItems = [:]\n        // update the data\n\n        reportHash = \"\"\n        // dictionaries are unsorted\n        // which means we do not considering it stable\n        // so, we use some numbers inside them to do the calculate\n        var hashSeedCounter: [UInt64] = []\n\n        func countSeed(val: Int) {\n            if let convertor = UInt64(exactly: val) {\n                // fuck, i'm not caring about overflow anymore\n                hashSeedCounter.append(convertor)\n            }\n        }\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                for diff in commit.diffFiles {\n                    countSeed(val: diff.increasedLine)\n                    countSeed(val: diff.decreasedLine)\n                    countSeed(val: diff.emptyLineAdded)\n                }\n            }\n        }\n\n        let cal = Calendar.current\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                let date = commit.date\n                if let day = cal.ordinality(of: .day, in: .year, for: date) {\n                    clipDataItems[day, default: 0] += 1\n                    countSeed(val: day * 114_514)\n                }\n            }\n        }\n        print(\"calendar result: \\(clipDataItems)\")\n\n        // no more overflow on big numbers!\n        reportHash = hashSeedCounter\n            .sorted()\n            .map { String($0) }\n            .joined(separator: \"\")\n            .sha256\n            .uppercase\n\n        while reportHash.count > 16 {\n            reportHash.removeFirst()\n        }\n        print(\"generated report hash 0x\\(reportHash)\")\n\n        return nil\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            clipData: clipDataItems,\n            reportHash: reportHash,\n            animated: true\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        AnyView(AssociatedView(\n            clipData: clipDataItems,\n            reportHash: reportHash,\n            animated: false\n        ))\n    }\n\n    struct AssociatedView: View {\n        let clipData: [Int: Int]\n        let reportHash: String\n        let animated: Bool\n\n        @State var isInPresentation: Bool = false\n\n        var body: some View {\n            GeometryReader { r in\n                ZStack {\n                    Rectangle()\n                        .foregroundColor(Color(NSColor.textBackgroundColor))\n                        .shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 0)\n                    container\n                        .padding(25)\n                        .frame(\n                            width: r.size.width,\n                            height: r.size.height,\n                            alignment: .center\n                        )\n                }\n                .frame(\n                    width: r.size.width,\n                    height: r.size.height,\n                    alignment: .center\n                )\n            }\n        }\n\n        var container: some View {\n            HStack {\n                CodeTiles(commits: clipData, animated: animated)\n                rightContainer\n            }\n        }\n\n        var rightContainer: some View {\n            VStack(alignment: .leading, spacing: 10) {\n                Spacer()\n                let name = User.current.namespace\n                if name.count > 0 {\n                    Text(\"\\(String(requiredYear)) 年 x \\(name)\")\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                } else {\n                    Text(\"\\(String(requiredYear)) 年\")\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                }\n                Text(\"我和我的代码，还有这一年。\")\n                    .font(.system(size: 25, weight: .semibold, design: .rounded))\n                Divider()\n                Text(\"校验码: 0x\\(reportHash.uppercase)\")\n                    .font(.system(size: 14, weight: .semibold, design: .monospaced))\n                    .opacity(0.5)\n                Spacer()\n            }\n            .overlay(\n                VStack {\n                    HStack {\n                        Spacer()\n                        Image(\"git\")\n                            .resizable()\n                            .aspectRatio(contentMode: .fit)\n                            .frame(width: 40, height: 40)\n                    }\n                    Spacer()\n                }\n            )\n            .padding()\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS1.swift",
    "content": "//\n//  RS1.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nprivate let calender = Calendar.current\n\nclass ResultSection1: ResultSection {\n    var totalCommit: Int = 0\n    var commitIncreaseLine: Int = 0\n    var commitDecreaseLine: Int = 0\n    var totalCommitDay: Int = 0\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        totalCommit = scannerResult\n            .repoResult\n            .repos\n            .map(\\.commits.count)\n            .reduce(0, +)\n        commitIncreaseLine = 0\n        commitDecreaseLine = 0\n        var commitDay = Set<Int>() // day of the year\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                commitIncreaseLine += commit\n                    .diffFiles\n                    .map(\\.increasedLine)\n                    .reduce(0, +)\n                commitDecreaseLine += commit\n                    .diffFiles\n                    .map(\\.decreasedLine)\n                    .reduce(0, +)\n                if let day = calender.ordinality(of: .day, in: .year, for: commit.date) {\n                    commitDay.insert(day)\n                }\n            }\n        }\n        totalCommitDay = commitDay.count\n\n        return totoalCommitToDesc(totalCommit: totalCommit, totoalAdd: commitIncreaseLine)\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            totalCommit: totalCommit,\n            commitIncreaseLine: commitIncreaseLine,\n            commitDecreaseLine: commitDecreaseLine,\n            totalCommitDay: totalCommitDay\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let totalCommit: Int\n        let commitIncreaseLine: Int\n        let commitDecreaseLine: Int\n        let totalCommitDay: Int\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                container\n                    .padding(50)\n            }\n        }\n\n        var container: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                Group {\n                    Text(\"在 \\(String(requiredYear)) 年\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"\\(makeYearDescription()) 是我今年的代言词。\")\n                        .frame(height: preferredContentHeight)\n                }\n\n                Group {\n                    Spacer()\n                        .frame(height: 20)\n                    Text(\"这一年里，我总共进行了 \\(makeBigNumber(totalCommit)) 次代码提交。\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"感谢我的仓库们，他们记录着我生活的点点滴滴。\")\n                        .frame(height: preferredContentHeight)\n                }\n\n                Group {\n                    Spacer()\n                        .frame(height: 20)\n                    Text(\"提交记录告诉咱：\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"仓库因你增添了 \\(makeAdd(commitIncreaseLine)) 行代码，也减去了 \\(makeDelete(commitDecreaseLine)) 行的重量。\")\n                        .fixedSize(horizontal: false, vertical: true)\n                        .frame(height: preferredContentHeight)\n                }\n\n                Group {\n                    Spacer()\n                        .frame(height: 20)\n                    if totalCommit < 0 {\n                        Text(\"回过头来看看这一年，咱一共活跃了 \\(makeBigNumber(totalCommitDay)) 天。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"黑客是我的外号，我总能找到属于我的 🚩! 是 🏳️‍⚧️ 还是 🏳️‍🌈 呢？\")\n                            .frame(height: preferredContentHeight)\n                    } else if totalCommit > 1000 {\n                        Text(\"这一年，咱一共卷了 \\(makeBigNumber(totalCommitDay)) 天。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"风雨兼程，目的地是我向往的星辰大海。🥺\")\n                            .frame(height: preferredContentHeight)\n                    } else if totalCommit > 365 {\n                        Text(\"回过头来看看这一年，似乎付出了不少。咱一共活跃了 \\(makeBigNumber(totalCommitDay)) 天。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"如果说代码是有温度的字符，那仓库便是咱的小太阳～ 🤫\")\n                            .frame(height: preferredContentHeight)\n                    } else if totalCommit > 50 {\n                        Text(\"回过头来看看这一年，咱一共活跃了 \\(makeBigNumber(totalCommitDay)) 天。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"星星有月亮，代码回家有仓库，而你有我相伴。😛\")\n                            .frame(height: preferredContentHeight)\n                    } else {\n                        Text(\"回过头来看看这一年，咱一共活跃了 \\(makeBigNumber(totalCommitDay)) 天。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"他们说多少不重要，因为我的提交，每一次都心意满满。😮\")\n                    }\n                }\n\n                Group {\n                    Divider()\n                        .hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n\n        func makeBigNumber(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.blue)\n        }\n\n        func makeAdd(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(.green)\n        }\n\n        func makeDelete(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(.red)\n        }\n\n        func makeYearDescription() -> Text {\n            Text(\n                totoalCommitToDesc(\n                    totalCommit: totalCommit,\n                    totoalAdd: commitIncreaseLine\n                )\n                .achievement\n                .name\n            )\n            .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n        }\n    }\n}\n\nprivate func totoalCommitToDesc(totalCommit: Int, totoalAdd: Int) -> ResultSectionUpdateRecipe {\n    if totalCommit < 0 {\n        return .init(achievement: .init(\n            name: \"flag{Hack_m3_1n_th3_b0x!}\",\n            describe: NSLocalizedString(\"提交的次数为负数\", comment: \"\")\n        ))\n    }\n    if totalCommit == 0 {\n        return .init(achievement: .init(\n            name: NSLocalizedString(\"我也不知道你来这里干什么\", comment: \"\"),\n            describe: NSLocalizedString(\"今年没有写代码\", comment: \"\")\n        ))\n    }\n    if totalCommit == 1 {\n        return .init(achievement: .init(\n            name: NSLocalizedString(\"签到不是胡闹\", comment: \"\"),\n            describe: NSLocalizedString(\"今年有且只有一次提交\", comment: \"\")\n        ))\n    }\n    let score = totalCommit * 10 + totoalAdd\n    if (0 ... 500).contains(score) {\n        return .init(achievement: .init(\n            name: NSLocalizedString(\"休养生息\", comment: \"\"),\n            describe: NSLocalizedString(\"有一些提交\", comment: \"\")\n        ))\n    }\n    if (500 ... 1000).contains(score) {\n        return .init(achievement: .init(\n            name: NSLocalizedString(\"小试牛刀\", comment: \"\"),\n            describe: NSLocalizedString(\"有一些些提交\", comment: \"\")\n        ))\n    }\n    if (1000 ... 10000).contains(score) {\n        return .init(achievement: .init(\n            name: NSLocalizedString(\"勤劳努力\", comment: \"\"),\n            describe: NSLocalizedString(\"有很多提交\", comment: \"\")\n        ))\n    }\n    if (10000 ... 100_000).contains(score) {\n        return .init(achievement: .init(\n            name: NSLocalizedString(\"发奋图强\", comment: \"\"),\n            describe: NSLocalizedString(\"有很多很多很多很多的提交\", comment: \"\")\n        ))\n    }\n    return .init(achievement: .init(\n        name: NSLocalizedString(\"卷卷卷卷卷卷\", comment: \"\"),\n        describe: NSLocalizedString(\"我是卷王王中王本王\", comment: \"\")\n    ))\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS2.swift",
    "content": "//\n//  RS2.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection2: ResultSection {\n    var mostUsedLanguage: SourceLanguage?\n    var howManyLine: Int = 0\n    var otherUsedLanguages: [SourceLanguage] = []\n    var languageStats: [(language: SourceLanguage, lines: Int)] = [] // 新增：所有语言统计\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        mostUsedLanguage = nil\n        howManyLine = 0\n        otherUsedLanguages = []\n        languageStats = [] // 重置统计数据\n\n        var languageBuilder: [SourceLanguage: Int] = [:]\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                for file in commit.diffFiles {\n                    if let language = file.language {\n                        // count add only\n                        languageBuilder[language, default: 0] += file.increasedLine\n                    }\n                }\n            }\n        }\n        \n        // 按行数从高到低排序所有语言\n        languageStats = languageBuilder.map { (language: $0.key, lines: $0.value) }\n            .sorted { $0.lines > $1.lines }\n        \n        var mostUsed: SourceLanguage?\n        var mostUsedCount: Int = -1\n        for key in languageBuilder.keys {\n            let count = languageBuilder[key, default: 0]\n            // if contain multiple result, first come first use\n            if count > mostUsedCount {\n                mostUsed = key\n                mostUsedCount = count\n            }\n        }\n\n        if let mostUsed {\n            mostUsedLanguage = mostUsed\n            howManyLine = mostUsedCount\n\n            // don't count those tiny things\n            // required at lease 0.05 percent of most used\n            // so if a guy write 1000 line of code, then 5 line of other is required\n            // or, if 128 line is written, then check!\n            for key in languageBuilder.keys where key != mostUsed {\n                let count = languageBuilder[key, default: 0]\n                if count > Int(Double(howManyLine) * 0.05) || count > 128 {\n                    otherUsedLanguages.append(key)\n                }\n            }\n        }\n\n        if otherUsedLanguages.count + 1 >= 6 {\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"编程语言大师\", comment: \"\"),\n                describe: NSLocalizedString(\"今年的提交中熟练使用了超过六种语言\", comment: \"\")\n            )\n            )\n        }\n        return nil\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            mostUsedLanguage: mostUsedLanguage,\n            howManyLine: howManyLine,\n            otherUsedLanguages: otherUsedLanguages,\n            languageStats: languageStats // 传递统计数据\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let mostUsedLanguage: SourceLanguage?\n        let howManyLine: Int\n        let otherUsedLanguages: [SourceLanguage]\n        let languageStats: [(language: SourceLanguage, lines: Int)] // 新增参数\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                container\n                    .padding(50)\n            }\n        }\n\n        var container: some View {\n            HStack(alignment: .top, spacing: 30) {\n                // 左侧：文字描述\n                textContent\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                \n                // 右侧：柱状图\n                if !languageStats.isEmpty {\n                    chartContent\n                        .frame(width: 200)\n                }\n            }\n        }\n        \n        var textContent: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                if let mostUsedLanguage {\n                    Group {\n                        Text(mostUsedLanguage.readableDescription())\n                            .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                            .frame(height: preferredContentHeight)\n                        Text(\"这是我最常用的语言。\")\n                            .frame(height: preferredContentHeight)\n                    }\n                    Group {\n                        Spacer()\n                            .frame(height: 20)\n                        Text(\"在这一年里，我使用这门语言提交了 \\(makeBigNumber(howManyLine)) 行代码。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"他是我最好的伙伴。\")\n                    }\n\n                    Group {\n                        if otherUsedLanguages.count > 0 {\n                            Spacer()\n                                .frame(height: 20)\n                            Text(\"在剩余的时光里，\")\n                                .frame(height: preferredContentHeight)\n                            Text(\n                                otherUsedLanguages\n                                    .map { $0.readableDescription() }\n                                    .shuffled()\n                                    .joined(separator: \",  \")\n                            )\n                            .font(.system(size: preferredContextSize * 1.2, weight: .semibold, design: .rounded))\n                            .foregroundColor(.purple)\n                            Text(\"他们也陪我走过一段旅程。\")\n                                .frame(height: preferredContentHeight)\n                        } else {\n                            Spacer()\n                                .frame(height: 20)\n                            Text(\"我很专一，没有使用过其他的语言。\")\n                                .frame(height: preferredContentHeight)\n                        }\n                    }\n\n                    Group {\n                        if otherUsedLanguages.count > 6 {\n                            Spacer()\n                                .frame(height: 20)\n                            Text(\"语言大师的称号，非你莫属！\")\n                        }\n                    }\n\n                } else {\n                    Text(\"我不知道你写了什么\")\n                        .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                        .frame(height: preferredContentHeight)\n                    Text(\"应该是太冷门了吧，数据库里找不到对应的语言。🥲\")\n                        .frame(height: preferredContentHeight)\n                }\n\n                Group {\n                    Divider()\n                        .hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n        \n        // 新增：柱状图视图\n        var chartContent: some View {\n            VStack(alignment: .leading, spacing: 8) {\n                // 获取最大值用于计算比例\n                let maxLines = languageStats.first?.lines ?? 1\n                \n                // 显示前8种语言（如果有的话）\n                ForEach(Array(languageStats.prefix(8).enumerated()), id: \\.element.language) { index, stat in\n                    VStack(alignment: .leading, spacing: 2) {\n                        HStack(spacing: 4) {\n                            Text(stat.language.readableDescription())\n                                .font(.system(size: 9, weight: .medium, design: .rounded))\n                                .frame(width: 60, alignment: .leading)\n                                .lineLimit(1)\n                            \n                            Text(\"\\(stat.lines)\")\n                                .font(.system(size: 8, weight: .regular, design: .monospaced))\n                                .foregroundColor(.secondary)\n                        }\n                        \n                        // 横向柱状条\n                        GeometryReader { geometry in\n                            ZStack(alignment: .leading) {\n                                // 背景条\n                                RoundedRectangle(cornerRadius: 3)\n                                    .fill(Color.gray.opacity(0.2))\n                                    .frame(width: geometry.size.width, height: 12)\n                                \n                                // 数据条\n                                RoundedRectangle(cornerRadius: 3)\n                                    .fill(\n                                        LinearGradient(\n                                            gradient: Gradient(colors: [\n                                                getColorForIndex(index),\n                                                getColorForIndex(index).opacity(0.7)\n                                            ]),\n                                            startPoint: .leading,\n                                            endPoint: .trailing\n                                        )\n                                    )\n                                    .frame(\n                                        width: geometry.size.width * CGFloat(stat.lines) / CGFloat(maxLines),\n                                        height: 12\n                                    )\n                            }\n                        }\n                        .frame(height: 12)\n                    }\n                }\n                \n                if languageStats.count > 8 {\n                    Text(\"还有 \\(languageStats.count - 8) 种语言...\")\n                        .font(.system(size: 8, weight: .regular, design: .rounded))\n                        .foregroundColor(.secondary)\n                        .padding(.top, 4)\n                }\n            }\n        }\n        \n        // 为不同的柱状条分配颜色\n        func getColorForIndex(_ index: Int) -> Color {\n            let colors: [Color] = [\n                .blue, .green, .orange, .purple, .pink, .red, .yellow, .cyan\n            ]\n            return colors[index % colors.count]\n        }\n\n        func makeBigNumber(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.orange)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS3.swift",
    "content": "//\n//  RS3.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nprivate let calender = Calendar.current\n\nclass ResultSection3: ResultSection {\n    var commitDateInDay: CommitDateInDay = .midnight\n    var commitDateInDayCount: Int = 0\n    var averageCommitPerDay: Double = 0\n    var averageCommitPerWeekday: Double = 0\n    var weekendCommitCount: Int = 0\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        var counter = [CommitDateInDay: Int]()\n        var totalCounter = 0\n        var weekendCounter: Set<Int> = []\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                totalCounter += 1\n                let date = commit.date\n                do {\n                    if isDateWeekend(date, with: calender) {\n                        if let dayOfYear = calender.ordinality(of: .day, in: .year, for: commit.date) {\n                            weekendCounter.insert(dayOfYear)\n                        }\n                    }\n                }\n                do {\n                    let components = calender.dateComponents([.hour], from: date)\n                    guard let hour = components.hour else {\n                        continue\n                    }\n                    let object = CommitDateInDay.convertFrom(hour: hour)\n                    counter[object, default: 0] += 1\n                }\n            }\n        }\n        averageCommitPerDay = Double(totalCounter) / 365 // no need to be that actuate\n        averageCommitPerWeekday = Double(totalCounter) / 261 // google telling me 261 working days lol\n        weekendCommitCount = weekendCounter.count\n        var mostUsed = CommitDateInDay.midnight\n        var mostUsedCount = -1\n        for key in counter.keys {\n            let count = counter[key, default: 0]\n            if count > mostUsedCount {\n                mostUsed = key\n                mostUsedCount = count\n            }\n        }\n        commitDateInDay = mostUsed\n        commitDateInDayCount = mostUsedCount\n\n        switch commitDateInDay {\n        case .midnight:\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"夜猫子\", comment: \"\"),\n                describe: NSLocalizedString(\"喜欢在午夜时分提交代码\", comment: \"\")\n            ))\n        case .morning:\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"早睡早起身体好\", comment: \"\"),\n                describe: NSLocalizedString(\"喜欢在早晨提交代码\", comment: \"\")\n            ))\n        case .noon:\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"干饭人！干饭魂！\", comment: \"\"),\n                describe: NSLocalizedString(\"喜欢在中午提交代码\", comment: \"\")\n            ))\n        case .afternoon:\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"星爸爸和气氛组的关怀\", comment: \"\"),\n                describe: NSLocalizedString(\"喜欢在下午茶时间提交代码\", comment: \"\")\n            ))\n        case .dinner:\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"晚饭的吃好\", comment: \"\"),\n                describe: NSLocalizedString(\"喜欢在晚饭时间提交代码\", comment: \"\")\n            ))\n        case .night:\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"睡前故事\", comment: \"\"),\n                describe: NSLocalizedString(\"喜欢在晚上提交代码\", comment: \"\")\n            ))\n        }\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            commitDateInDay: commitDateInDay,\n            commitDateInDayCount: commitDateInDayCount,\n            averageCommitPerDay: averageCommitPerDay,\n            averageCommitPerWeekday: averageCommitPerWeekday,\n            weekendCommitCount: weekendCommitCount\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let commitDateInDay: CommitDateInDay\n        let commitDateInDayCount: Int\n        let averageCommitPerDay: Double\n        let averageCommitPerWeekday: Double\n        let weekendCommitCount: Int\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                container\n                    .padding(50)\n            }\n        }\n\n        var container: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                Group {\n                    makeLarge(text: commitDateInDay.readableDescription())\n                        .frame(height: preferredContentHeight)\n                    Text(\"我最喜欢在 \\(commitDateInDay.readableDescription()) 的时候提交代码，总共提交了 \\(commitDateInDayCount) 次。\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"风雨兼程的 Coding 旅途，一天中我最忙碌的时段。\")\n                        .frame(height: preferredContentHeight)\n                    Spacer()\n                        .frame(height: 20)\n                }\n                Group {\n                    Text(\"平均而言，我一天提交代码 \\(makeBigNumber(averageCommitPerDay)) 次。\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"如果不计算周末的日子，则是 \\(makeBigNumber(averageCommitPerWeekday)) 次。\")\n                        .frame(height: preferredContentHeight)\n                    if averageCommitPerWeekday > 10 {\n                        Text(\"我是卷王本王 🤪\")\n                            .frame(height: preferredContentHeight)\n                    } else if averageCommitPerWeekday > 3 {\n                        Text(\"辛苦啦 🥲\")\n                            .frame(height: preferredContentHeight)\n                    } else {\n                        Text(\"是的，我又在摸鱼 🥺\")\n                            .frame(height: preferredContentHeight)\n                    }\n                    Spacer()\n                        .frame(height: 20)\n                }\n\n                Text(\"有 \\(makeBigNumber(weekendCommitCount)) 个周末的日子，我在仓库留下了身影。\")\n                    .frame(height: preferredContentHeight)\n\n                if weekendCommitCount > 0 {\n                    if weekendCommitCount > 30 {\n                        Text(\"修得的福报，是我一生最大的欢喜。\")\n                            .frame(height: preferredContentHeight)\n                    } else if weekendCommitCount > 10 {\n                        Text(\"可能敲代码，正是我的乐趣吧。\")\n                            .frame(height: preferredContentHeight)\n                    }\n                } else {\n                    Text(\"这一年的周末，我都没有提交代码。\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"偷得浮生半日闲，可不能再修福报啦！\")\n                        .frame(height: preferredContentHeight)\n                }\n\n                Group {\n                    Divider()\n                        .hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n\n        func makeBigNumber(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.blue)\n        }\n\n        func makeBigNumber(_ number: Double) -> Text {\n            Text(\" \\(String(format: \"%.4f\", number)) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.blue)\n        }\n\n        func makeLarge(text: String) -> Text {\n            Text(text)\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(.orange)\n        }\n    }\n\n    func isDateWeekend(_ date: Date, with calendar: Calendar) -> Bool {\n        var enCalendar = calendar\n        enCalendar.locale = Locale(identifier: \"en_US\")\n        let components = enCalendar.dateComponents([.weekday], from: date)\n        guard let weekday = components.weekday else {\n            assertionFailure(\"Failed to extract weekday from date\")\n            return false\n        }\n        return [\"Saturday\", \"Sunday\"].contains(enCalendar.weekdaySymbols[weekday - 1])\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS4.swift",
    "content": "//\n//  RS4.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection4: ResultSection {\n    var mostUsedWordInCode: String = \"\"\n    var mostUsedWordInCodeCount: Int = 0\n    var mostUsedWordInCommitLog: String = \"\"\n    var mostUsedWordInCommitLogCount: Int = 0\n    var otherUsedWordInCode: [String] = []\n    var otherUsedWordInCommitLog: [String] = []\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        mostUsedWordInCode = \"\"\n        mostUsedWordInCodeCount = 0\n        if let value = DicCounter.mostUsedKeyword(\n            from: scannerResult.dictionaryIncrease\n        ) {\n            mostUsedWordInCode = value.0\n            mostUsedWordInCodeCount = value.1\n        }\n\n        mostUsedWordInCommitLog = \"\"\n        mostUsedWordInCommitLogCount = 0\n        if let value = DicCounter.mostUsedKeyword(\n            from: scannerResult.dictionaryCommit\n        ) {\n            mostUsedWordInCommitLog = value.0\n            mostUsedWordInCommitLogCount = value.1\n        }\n\n        otherUsedWordInCode = DicCounter.mostUsedKeywords(from: scannerResult.dictionaryIncrease, count: 10)\n        otherUsedWordInCommitLog = DicCounter.mostUsedKeywords(from: scannerResult.dictionaryCommit, count: 10)\n\n        if dirtyWordList.contains(mostUsedWordInCode)\n            || dirtyWordList.contains(mostUsedWordInCommitLog)\n        {\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"文明语言大师\", comment: \"\"),\n                describe: NSLocalizedString(\"在代码或者提交备注中使用的最多的词语是脏话\", comment: \"\")\n            ))\n        }\n        for item in otherUsedWordInCode + otherUsedWordInCommitLog where dirtyWordList.contains(item) {\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"文明语言学者\", comment: \"\"),\n                describe: NSLocalizedString(\"在代码或者提交备注中使用了不少的脏话\", comment: \"\")\n            ))\n        }\n        return nil\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            mostUsedWordInCode: mostUsedWordInCode,\n            mostUsedWordInCodeCount: mostUsedWordInCodeCount,\n            mostUsedWordInCommitLog: mostUsedWordInCommitLog,\n            mostUsedWordInCommitLogCount: mostUsedWordInCommitLogCount,\n            otherUsedWordInCode: otherUsedWordInCode,\n            otherUsedWordInCommitLog: otherUsedWordInCommitLog\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let mostUsedWordInCode: String\n        let mostUsedWordInCodeCount: Int\n        let mostUsedWordInCommitLog: String\n        let mostUsedWordInCommitLogCount: Int\n\n        let otherUsedWordInCode: [String]\n        let otherUsedWordInCommitLog: [String]\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                HStack {\n                    container\n                }\n                .padding(50)\n            }\n        }\n\n        var container: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                Group {\n                    if mostUsedWordInCodeCount > 0 {\n                        makeLarge(text: mostUsedWordInCode)\n                            .frame(height: preferredContentHeight)\n                        Text(\"这是我在代码中最常写到的单词，他出现了 \\(mostUsedWordInCodeCount) 次。\")\n                            .frame(height: preferredContentHeight)\n                    }\n                    if mostUsedWordInCommitLogCount > 0 {\n                        makeLarge(text: mostUsedWordInCommitLog)\n                            .frame(height: preferredContentHeight)\n                        Text(\"这是我在代码提交记录中最常写到的单词，他出现了 \\(mostUsedWordInCommitLogCount) 次。\")\n                            .frame(height: preferredContentHeight)\n                    }\n                    Spacer()\n                        .frame(height: 20)\n                }\n                Group {\n                    if otherUsedWordInCode.count > 0 {\n                        Text(\"代码里，这些词经常出现\")\n                            .frame(height: preferredContentHeight)\n                        Text(otherUsedWordInCode.joined(separator: \" \"))\n                        Spacer()\n                            .frame(height: 20)\n                    }\n                }\n                Group {\n                    if otherUsedWordInCommitLog.count > 0 {\n                        Text(\"提交记录里，这些词经常出现\")\n                            .frame(height: preferredContentHeight)\n                        Text(otherUsedWordInCommitLog.joined(separator: \" \"))\n                        Spacer()\n                            .frame(height: 20)\n                    }\n                }\n                Group {\n                    Text(\"我会写很多很多的单词，很多很多的句子。\")\n                        .frame(height: preferredContentHeight)\n                }\n                Group {\n                    Divider()\n                        .hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n\n        func makeBigNumber(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.blue)\n        }\n\n        func makeLarge(text: String) -> Text {\n            Text(text)\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(.orange)\n        }\n    }\n}\n\nlet dirtyWordList = [\n    \"2g1c\",\n    \"2 girls 1 cup\",\n    \"acrotomophilia\",\n    \"alabama hot pocket\",\n    \"alaskan pipeline\",\n    \"anal\",\n    \"anilingus\",\n    \"anus\",\n    \"apeshit\",\n    \"arsehole\",\n    \"ass\",\n    \"asshole\",\n    \"assmunch\",\n    \"auto erotic\",\n    \"autoerotic\",\n    \"babeland\",\n    \"baby batter\",\n    \"baby juice\",\n    \"ball gag\",\n    \"ball gravy\",\n    \"ball kicking\",\n    \"ball licking\",\n    \"ball sack\",\n    \"ball sucking\",\n    \"bangbros\",\n    \"bangbus\",\n    \"bareback\",\n    \"barely legal\",\n    \"barenaked\",\n    \"bastard\",\n    \"bastardo\",\n    \"bastinado\",\n    \"bbw\",\n    \"bdsm\",\n    \"beaner\",\n    \"beaners\",\n    \"beaver cleaver\",\n    \"beaver lips\",\n    \"beastiality\",\n    \"bestiality\",\n    \"big black\",\n    \"big breasts\",\n    \"big knockers\",\n    \"big tits\",\n    \"bimbos\",\n    \"birdlock\",\n    \"bitch\",\n    \"bitches\",\n    \"black cock\",\n    \"blonde action\",\n    \"blonde on blonde action\",\n    \"blowjob\",\n    \"blow job\",\n    \"blow your load\",\n    \"blue waffle\",\n    \"blumpkin\",\n    \"bollocks\",\n    \"bondage\",\n    \"boner\",\n    \"boob\",\n    \"boobs\",\n    \"booty call\",\n    \"brown showers\",\n    \"brunette action\",\n    \"bukkake\",\n    \"bulldyke\",\n    \"bullet vibe\",\n    \"bullshit\",\n    \"bung hole\",\n    \"bunghole\",\n    \"busty\",\n    \"butt\",\n    \"buttcheeks\",\n    \"butthole\",\n    \"camel toe\",\n    \"camgirl\",\n    \"camslut\",\n    \"camwhore\",\n    \"carpet muncher\",\n    \"carpetmuncher\",\n    \"chocolate rosebuds\",\n    \"cialis\",\n    \"circlejerk\",\n    \"cleveland steamer\",\n    \"clit\",\n    \"clitoris\",\n    \"clover clamps\",\n    \"clusterfuck\",\n    \"cock\",\n    \"cocks\",\n    \"coprolagnia\",\n    \"coprophilia\",\n    \"cornhole\",\n    \"coon\",\n    \"coons\",\n    \"creampie\",\n    \"cum\",\n    \"cumming\",\n    \"cumshot\",\n    \"cumshots\",\n    \"cunnilingus\",\n    \"cunt\",\n    \"darkie\",\n    \"date rape\",\n    \"daterape\",\n    \"deep throat\",\n    \"deepthroat\",\n    \"dendrophilia\",\n    \"dick\",\n    \"dildo\",\n    \"dingleberry\",\n    \"dingleberries\",\n    \"dirty pillows\",\n    \"dirty sanchez\",\n    \"doggie style\",\n    \"doggiestyle\",\n    \"doggy style\",\n    \"doggystyle\",\n    \"dog style\",\n    \"dolcett\",\n    \"domination\",\n    \"dominatrix\",\n    \"dommes\",\n    \"donkey punch\",\n    \"double dong\",\n    \"double penetration\",\n    \"dp action\",\n    \"dry hump\",\n    \"dvda\",\n    \"eat my ass\",\n    \"ecchi\",\n    \"ejaculation\",\n    \"erotic\",\n    \"erotism\",\n    \"escort\",\n    \"eunuch\",\n    \"fag\",\n    \"faggot\",\n    \"fecal\",\n    \"felch\",\n    \"fellatio\",\n    \"feltch\",\n    \"female squirting\",\n    \"femdom\",\n    \"figging\",\n    \"fingerbang\",\n    \"fingering\",\n    \"fisting\",\n    \"foot fetish\",\n    \"footjob\",\n    \"frotting\",\n    \"fuck\",\n    \"fuck buttons\",\n    \"fuckin\",\n    \"fucking\",\n    \"fucktards\",\n    \"fudge packer\",\n    \"fudgepacker\",\n    \"futanari\",\n    \"gangbang\",\n    \"gang bang\",\n    \"gay sex\",\n    \"genitals\",\n    \"giant cock\",\n    \"girl on\",\n    \"girl on top\",\n    \"girls gone wild\",\n    \"goatcx\",\n    \"goatse\",\n    \"god damn\",\n    \"gokkun\",\n    \"golden shower\",\n    \"goodpoop\",\n    \"goo girl\",\n    \"goregasm\",\n    \"grope\",\n    \"group sex\",\n    \"g-spot\",\n    \"guro\",\n    \"hand job\",\n    \"handjob\",\n    \"hard core\",\n    \"hardcore\",\n    \"hentai\",\n    \"homoerotic\",\n    \"honkey\",\n    \"hooker\",\n    \"horny\",\n    \"hot carl\",\n    \"hot chick\",\n    \"how to kill\",\n    \"how to murder\",\n    \"huge fat\",\n    \"humping\",\n    \"incest\",\n    \"intercourse\",\n    \"jack off\",\n    \"jail bait\",\n    \"jailbait\",\n    \"jelly donut\",\n    \"jerk off\",\n    \"jigaboo\",\n    \"jiggaboo\",\n    \"jiggerboo\",\n    \"jizz\",\n    \"juggs\",\n    \"kike\",\n    \"kinbaku\",\n    \"kinkster\",\n    \"kinky\",\n    \"knobbing\",\n    \"leather restraint\",\n    \"leather straight jacket\",\n    \"lemon party\",\n    \"livesex\",\n    \"lolita\",\n    \"lovemaking\",\n    \"make me come\",\n    \"male squirting\",\n    \"masturbate\",\n    \"masturbating\",\n    \"masturbation\",\n    \"menage a trois\",\n    \"milf\",\n    \"missionary position\",\n    \"mong\",\n    \"motherfucker\",\n    \"mound of venus\",\n    \"mr hands\",\n    \"muff diver\",\n    \"muffdiving\",\n    \"nambla\",\n    \"nawashi\",\n    \"negro\",\n    \"neonazi\",\n    \"nigga\",\n    \"nigger\",\n    \"nig nog\",\n    \"nimphomania\",\n    \"nipple\",\n    \"nipples\",\n    \"nsfw\",\n    \"nsfw images\",\n    \"nude\",\n    \"nudity\",\n    \"nutten\",\n    \"nympho\",\n    \"nymphomania\",\n    \"octopussy\",\n    \"omorashi\",\n    \"one cup two girls\",\n    \"one guy one jar\",\n    \"orgasm\",\n    \"orgy\",\n    \"paedophile\",\n    \"paki\",\n    \"panties\",\n    \"panty\",\n    \"pedobear\",\n    \"pedophile\",\n    \"pegging\",\n    \"penis\",\n    \"phone sex\",\n    \"piece of shit\",\n    \"pikey\",\n    \"pissing\",\n    \"piss pig\",\n    \"pisspig\",\n    \"playboy\",\n    \"pleasure chest\",\n    \"pole smoker\",\n    \"ponyplay\",\n    \"poof\",\n    \"poon\",\n    \"poontang\",\n    \"punany\",\n    \"poop chute\",\n    \"poopchute\",\n    \"porn\",\n    \"porno\",\n    \"pornography\",\n    \"prince albert piercing\",\n    \"pthc\",\n    \"pubes\",\n    \"pussy\",\n    \"queaf\",\n    \"queef\",\n    \"quim\",\n    \"raghead\",\n    \"raging boner\",\n    \"rape\",\n    \"raping\",\n    \"rapist\",\n    \"rectum\",\n    \"reverse cowgirl\",\n    \"rimjob\",\n    \"rimming\",\n    \"rosy palm\",\n    \"rosy palm and her 5 sisters\",\n    \"rusty trombone\",\n    \"sadism\",\n    \"santorum\",\n    \"scat\",\n    \"schlong\",\n    \"scissoring\",\n    \"semen\",\n    \"sex\",\n    \"sexcam\",\n    \"sexo\",\n    \"sexy\",\n    \"sexual\",\n    \"sexually\",\n    \"sexuality\",\n    \"shaved beaver\",\n    \"shaved pussy\",\n    \"shemale\",\n    \"shibari\",\n    \"shit\",\n    \"shitblimp\",\n    \"shitty\",\n    \"shota\",\n    \"shrimping\",\n    \"skeet\",\n    \"slanteye\",\n    \"slut\",\n    \"s&m\",\n    \"smut\",\n    \"snatch\",\n    \"snowballing\",\n    \"sodomize\",\n    \"sodomy\",\n    \"spastic\",\n    \"spic\",\n    \"splooge\",\n    \"splooge moose\",\n    \"spooge\",\n    \"spread legs\",\n    \"spunk\",\n    \"strap on\",\n    \"strapon\",\n    \"strappado\",\n    \"strip club\",\n    \"style doggy\",\n    \"suck\",\n    \"sucks\",\n    \"suicide girls\",\n    \"sultry women\",\n    \"swastika\",\n    \"swinger\",\n    \"tainted love\",\n    \"taste my\",\n    \"tea bagging\",\n    \"threesome\",\n    \"throating\",\n    \"thumbzilla\",\n    \"tied up\",\n    \"tight white\",\n    \"tit\",\n    \"tits\",\n    \"titties\",\n    \"titty\",\n    \"tongue in a\",\n    \"topless\",\n    \"tosser\",\n    \"towelhead\",\n    \"tranny\",\n    \"tribadism\",\n    \"tub girl\",\n    \"tubgirl\",\n    \"tushy\",\n    \"twat\",\n    \"twink\",\n    \"twinkie\",\n    \"two girls one cup\",\n    \"undressing\",\n    \"upskirt\",\n    \"urethra play\",\n    \"urophilia\",\n    \"vagina\",\n    \"venus mound\",\n    \"viagra\",\n    \"vibrator\",\n    \"violet wand\",\n    \"vorarephilia\",\n    \"voyeur\",\n    \"voyeurweb\",\n    \"voyuer\",\n    \"vulva\",\n    \"wank\",\n    \"wetback\",\n    \"wet dream\",\n    \"white power\",\n    \"whore\",\n    \"worldsex\",\n    \"wrapping men\",\n    \"wrinkled starfish\",\n    \"xx\",\n    \"xxx\",\n    \"yaoi\",\n    \"yellow showers\",\n    \"yiffy\",\n    \"zoophilia\",\n    \"🖕\",\n    \"13.\",\n    \"13点\",\n    \"三级片\",\n    \"下三烂\",\n    \"下贱\",\n    \"个老子的\",\n    \"九游\",\n    \"乳\",\n    \"乳交\",\n    \"乳头\",\n    \"乳房\",\n    \"乳波臀浪\",\n    \"交配\",\n    \"仆街\",\n    \"他奶奶\",\n    \"他奶奶的\",\n    \"他奶娘的\",\n    \"他妈\",\n    \"他妈ㄉ王八蛋\",\n    \"他妈地\",\n    \"他妈的\",\n    \"他娘\",\n    \"他马的\",\n    \"你个傻比\",\n    \"你他马的\",\n    \"你全家\",\n    \"你奶奶的\",\n    \"你她马的\",\n    \"你妈\",\n    \"你妈的\",\n    \"你娘\",\n    \"你娘卡好\",\n    \"你娘咧\",\n    \"你它妈的\",\n    \"你它马的\",\n    \"你是鸡\",\n    \"你是鸭\",\n    \"你马的\",\n    \"做爱\",\n    \"傻比\",\n    \"傻逼\",\n    \"册那\",\n    \"军妓\",\n    \"几八\",\n    \"几叭\",\n    \"几巴\",\n    \"几芭\",\n    \"刚度\",\n    \"刚瘪三\",\n    \"包皮\",\n    \"十三点\",\n    \"卖B\",\n    \"卖比\",\n    \"卖淫\",\n    \"卵\",\n    \"卵子\",\n    \"双峰微颤\",\n    \"口交\",\n    \"口肯\",\n    \"叫床\",\n    \"吃屎\",\n    \"后庭\",\n    \"吹箫\",\n    \"塞你公\",\n    \"塞你娘\",\n    \"塞你母\",\n    \"塞你爸\",\n    \"塞你老师\",\n    \"塞你老母\",\n    \"处女\",\n    \"外阴\",\n    \"大卵子\",\n    \"大卵泡\",\n    \"大鸡巴\",\n    \"奶\",\n    \"奶奶的熊\",\n    \"奶子\",\n    \"奸\",\n    \"奸你\",\n    \"她妈地\",\n    \"她妈的\",\n    \"她马的\",\n    \"妈B\",\n    \"妈个B\",\n    \"妈个比\",\n    \"妈个老比\",\n    \"妈妈的\",\n    \"妈比\",\n    \"妈的\",\n    \"妈的B\",\n    \"妈逼\",\n    \"妓\",\n    \"妓女\",\n    \"妓院\",\n    \"妳她妈的\",\n    \"妳妈的\",\n    \"妳娘的\",\n    \"妳老母的\",\n    \"妳马的\",\n    \"姘头\",\n    \"姣西\",\n    \"姦\",\n    \"娘个比\",\n    \"娘的\",\n    \"婊子\",\n    \"婊子养的\",\n    \"嫖娼\",\n    \"嫖客\",\n    \"它妈地\",\n    \"它妈的\",\n    \"密洞\",\n    \"射你\",\n    \"射精\",\n    \"小乳头\",\n    \"小卵子\",\n    \"小卵泡\",\n    \"小瘪三\",\n    \"小肉粒\",\n    \"小骚比\",\n    \"小骚货\",\n    \"小鸡巴\",\n    \"小鸡鸡\",\n    \"屁眼\",\n    \"屁股\",\n    \"屄\",\n    \"屌\",\n    \"巨乳\",\n    \"干x娘\",\n    \"干七八\",\n    \"干你\",\n    \"干你妈\",\n    \"干你娘\",\n    \"干你老母\",\n    \"干你良\",\n    \"干妳妈\",\n    \"干妳娘\",\n    \"干妳老母\",\n    \"干妳马\",\n    \"干您娘\",\n    \"干机掰\",\n    \"干死CS\",\n    \"干死GM\",\n    \"干死你\",\n    \"干死客服\",\n    \"幹\",\n    \"强奸\",\n    \"强奸你\",\n    \"性\",\n    \"性交\",\n    \"性器\",\n    \"性无能\",\n    \"性爱\",\n    \"情色\",\n    \"想上你\",\n    \"懆您妈\",\n    \"懆您娘\",\n    \"懒8\",\n    \"懒八\",\n    \"懒叫\",\n    \"懒教\",\n    \"成人\",\n    \"我操你祖宗十八代\",\n    \"扒光\",\n    \"打炮\",\n    \"打飞机\",\n    \"抽插\",\n    \"招妓\",\n    \"插你\",\n    \"插死你\",\n    \"撒尿\",\n    \"操你\",\n    \"操你全家\",\n    \"操你奶奶\",\n    \"操你妈\",\n    \"操你娘\",\n    \"操你祖宗\",\n    \"操你老妈\",\n    \"操你老母\",\n    \"操妳\",\n    \"操妳全家\",\n    \"操妳妈\",\n    \"操妳娘\",\n    \"操妳祖宗\",\n    \"操机掰\",\n    \"操比\",\n    \"操逼\",\n    \"放荡\",\n    \"日他娘\",\n    \"日你\",\n    \"日你妈\",\n    \"日你老娘\",\n    \"日你老母\",\n    \"日批\",\n    \"月经\",\n    \"机八\",\n    \"机巴\",\n    \"机机歪歪\",\n    \"杂种\",\n    \"浪叫\",\n    \"淫\",\n    \"淫乱\",\n    \"淫妇\",\n    \"淫棍\",\n    \"淫水\",\n    \"淫秽\",\n    \"淫荡\",\n    \"淫西\",\n    \"湿透的内裤\",\n    \"激情\",\n    \"灨你娘\",\n    \"烂货\",\n    \"烂逼\",\n    \"爛\",\n    \"狗屁\",\n    \"狗日\",\n    \"狗狼养的\",\n    \"玉杵\",\n    \"王八蛋\",\n    \"瓜娃子\",\n    \"瓜婆娘\",\n    \"瓜批\",\n    \"瘪三\",\n    \"白烂\",\n    \"白痴\",\n    \"白癡\",\n    \"祖宗\",\n    \"私服\",\n    \"笨蛋\",\n    \"精子\",\n    \"老二\",\n    \"老味\",\n    \"老母\",\n    \"老瘪三\",\n    \"老骚比\",\n    \"老骚货\",\n    \"肉壁\",\n    \"肉棍子\",\n    \"肉棒\",\n    \"肉缝\",\n    \"肏\",\n    \"肛交\",\n    \"肥西\",\n    \"色情\",\n    \"花柳\",\n    \"荡妇\",\n    \"賤\",\n    \"贝肉\",\n    \"贱B\",\n    \"贱人\",\n    \"贱货\",\n    \"贼你妈\",\n    \"赛你老母\",\n    \"赛妳阿母\",\n    \"赣您娘\",\n    \"轮奸\",\n    \"迷药\",\n    \"逼\",\n    \"逼样\",\n    \"野鸡\",\n    \"阳具\",\n    \"阳萎\",\n    \"阴唇\",\n    \"阴户\",\n    \"阴核\",\n    \"阴毛\",\n    \"阴茎\",\n    \"阴道\",\n    \"阴部\",\n    \"雞巴\",\n    \"靠北\",\n    \"靠母\",\n    \"靠爸\",\n    \"靠背\",\n    \"靠腰\",\n    \"驶你公\",\n    \"驶你娘\",\n    \"驶你母\",\n    \"驶你爸\",\n    \"驶你老师\",\n    \"驶你老母\",\n    \"骚比\",\n    \"骚货\",\n    \"骚逼\",\n    \"鬼公\",\n    \"鸡8\",\n    \"鸡八\",\n    \"鸡叭\",\n    \"鸡吧\",\n    \"鸡奸\",\n    \"鸡巴\",\n    \"鸡芭\",\n    \"鸡鸡\",\n    \"龟儿子\",\n    \"龟头\",\n    \"𨳒\",\n    \"陰莖\",\n    \"㞗\",\n    \"尻\",\n    \"𨳊\",\n    \"鳩\",\n    \"𡳞\",\n    \"𨶙\",\n    \"撚\",\n    \"𨳍\",\n    \"柒\",\n    \"閪\",\n    \"仆街\",\n    \"咸家鏟\",\n    \"冚家鏟\",\n    \"咸家伶\",\n    \"冚家拎\",\n    \"笨實\",\n    \"粉腸\",\n    \"屎忽\",\n    \"躝癱\",\n    \"你老闆\",\n    \"你老味\",\n    \"你老母\",\n    \"硬膠\",\n]\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS5.swift",
    "content": "//\n//  RS5.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection5: ResultSection {\n    var emptyLineCount: Int = 0\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        emptyLineCount = 0\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                emptyLineCount += commit\n                    .diffFiles\n                    .map(\\.emptyLineAdded)\n                    .reduce(0, +)\n            }\n        }\n        if emptyLineCount > 233_333 {\n            return .init(achievement: .init(name: \"摸鱼流量百分百\", describe: \"写了超过 233333 行空行\"))\n        }\n        return nil\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            emptyLineCount: emptyLineCount\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let emptyLineCount: Int\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                HStack {\n                    container\n                }\n                .padding(50)\n            }\n        }\n\n        var container: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                Group {\n                    Text(\"\\(makeBigNumber(emptyLineCount))  行\") // double space\n                        .frame(height: preferredContentHeight)\n                    Text(\"这是我今年写的空行的数量。空行，没错，就是只有空格或者什么都没有的那一行。\")\n                        .frame(height: preferredContentHeight)\n                    Spacer()\n                        .frame(height: 20)\n                }\n                Group {\n                    if emptyLineCount < 1 {\n                        Text(\"我从来不摸鱼，因为没有鱼给我摸。🐟\")\n                            .frame(height: preferredContentHeight)\n                    } else if emptyLineCount < 1000 {\n                        Text(\"空行能让我的代码变得好看，我很喜欢。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"我想你也会很喜欢的，我如此说道，我如此和你说道。\")\n                            .frame(height: preferredContentHeight)\n                    } else if emptyLineCount < 233_333 {\n                        Text(\"人们说色即是空，空即是色。\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"我着实不能理解其中的含义。\")\n                            .frame(height: preferredContentHeight)\n                    } else {\n                        Text(\"天啦噜！我的摸鱼流量超过了 100TB 呢！\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"这相当于好几百只 🐳🐳🐳🐳🐳🐳 从我身边游过\")\n                            .frame(height: preferredContentHeight)\n                    }\n                }\n                Group {\n                    Text(\"你看到了吗，这一页，有 1010 行空行呢。\")\n                        .frame(height: preferredContentHeight)\n                }\n                Group {\n                    Divider().hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n\n        func makeBigNumber(_ number: Int) -> Text {\n            Text(\"\\(number)\")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.pink)\n        }\n\n        func makeLarge(text: String) -> Text {\n            Text(text)\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(.orange)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS6.swift",
    "content": "//\n//  RS6.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection6: ResultSection {\n    var specialDay: Int = 0\n    var specialMonth: Int = 0\n    var specialCount: Int = 0\n\n    struct MonDayPair: Hashable {\n        let mon: Int\n        let day: Int\n    }\n\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        var builder = [MonDayPair: Int]()\n        let cal = Calendar.current\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                let date = commit.date\n                if let mon = cal.ordinality(of: .month, in: .year, for: date),\n                   let day = cal.ordinality(of: .day, in: .month, for: date)\n                {\n                    builder[MonDayPair(mon: mon, day: day), default: 0] += 1\n                }\n            }\n        }\n        specialMonth = 0\n        specialDay = 0\n        specialCount = 0\n        if let nbDay = DicCounter.mostUsedKeyword(from: builder) {\n            specialMonth = nbDay.0.mon\n            specialDay = nbDay.0.day\n            specialCount = nbDay.1\n        }\n\n        if specialCount > 50 {\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"Bufeature 制造机\", comment: \"\"),\n                describe: NSLocalizedString(\"今年有一天的提交次数超过五十次\", comment: \"\")\n            ))\n        } else if specialCount > 100 {\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"我是奥特曼\", comment: \"\"),\n                describe: NSLocalizedString(\"今年有一天的提交次数超过百次\", comment: \"\")\n            ))\n        }\n\n        return nil\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            renderingPrint: false,\n            specialDay: specialDay,\n            specialMonth: specialMonth,\n            specialCount: specialCount\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        AnyView(AssociatedView(\n            renderingPrint: true,\n            specialDay: specialDay,\n            specialMonth: specialMonth,\n            specialCount: specialCount\n        ))\n    }\n\n    struct AssociatedView: View {\n        let renderingPrint: Bool\n\n        let specialDay: Int\n        let specialMonth: Int\n        let specialCount: Int\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                HStack {\n                    container\n                }\n                .padding(50)\n            }\n        }\n\n        let weathers = [\n            \"sun.min\",\n            \"sun.max\",\n            \"sun.max.circle\",\n            \"sunrise\",\n            \"sunset\",\n            \"sun.and.horizon\",\n            \"sun.dust\",\n            \"sun.haze\",\n            \"moon\",\n            \"moon.circle\",\n            \"sparkles\",\n            \"moon.stars\",\n            \"cloud\",\n            \"cloud.drizzle\",\n            \"cloud.rain\",\n            \"cloud.heavyrain\",\n            \"cloud.fog\",\n            \"cloud.hail\",\n            \"cloud.snow\",\n            \"cloud.sleet\",\n            \"cloud.bolt\",\n            \"cloud.bolt.rain\",\n            \"cloud.sun\",\n            \"cloud.sun.rain\",\n            \"cloud.sun.bolt\",\n            \"cloud.moon\",\n            \"cloud.moon.rain\",\n            \"cloud.moon.bolt\",\n            \"smoke\",\n            \"wind\",\n            \"wind.snow\",\n            \"snowflake\",\n            \"snowflake.circle\",\n            \"tornado\",\n            \"tropicalstorm\",\n            \"hurricane\",\n            \"thermometer.sun\",\n            \"thermometer.snowflake\",\n            \"thermometer\",\n            \"aqi.low\",\n            \"aqi.medium\",\n            \"aqi.high\",\n            \"humidity\",\n        ]\n\n        var container: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                Group {\n                    makeLarge(text: \"\\(specialMonth) 月 \\(specialDay) 日\")\n                        .frame(height: preferredContentHeight)\n                    Text(\"大概是很特别的一天。\")\n                        .frame(height: preferredContentHeight)\n                    Spacer()\n                        .frame(height: 20)\n                }\n                Group {\n                    HStack {\n                        if let iconList = makeIconList() {\n                            ForEach(0 ..< iconList.count, id: \\.self) { index in\n                                if renderingPrint {\n                                    makeImage(with: \"custom.\\(iconList[index])\")\n                                        .antialiased(true)\n                                        .resizable()\n                                        .scaledToFit()\n                                        .frame(width: preferredContextSize, height: preferredContextSize)\n                                } else {\n                                    Image(\"custom.\\(iconList[index])\")\n                                        .renderingMode(.template)\n                                        .antialiased(true)\n                                        .resizable()\n                                        .scaledToFit()\n                                        .frame(width: preferredContextSize, height: preferredContextSize)\n                                        .foregroundColor(Color(NSColor.textColor))\n                                }\n                            }\n                        }\n                    }\n                    Spacer()\n                        .frame(height: 20)\n                }\n                Group {\n                    Text(\"在这短短的一天里，你一共提交了 \\(makeBigNumber(specialCount)) 次代码。\")\n                        .frame(height: preferredContentHeight)\n                    if specialCount > 233 {\n                        Text(\"狗急了会跳墙，我急了会骂娘。一天这么多次的提交，肯定是有人被我骂得很惨吧。\")\n                            .frame(height: preferredContentHeight)\n                    } else if specialCount > 80 {\n                        Text(\"这一天，是忙碌的自己，没吃好，没睡好。\")\n                    } else if specialCount > 25 {\n                        Text(\"我的 Bufeature 做好了吗？\")\n                            .frame(height: preferredContentHeight)\n                        Text(\"Bufeature: <noun> bug feature, feature with bug.\")\n                            .font(.system(size: 10, weight: .semibold, design: .rounded))\n                            .opacity(0.5)\n                    } else if specialCount > 10 {\n                        Text(\"上串下跳的提交，是开心，是愉悦，还是害怕，或者担心呢？\")\n                            .frame(height: preferredContentHeight)\n                    }\n                }\n                Group {\n                    Divider().hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n\n        // so we can use if let to get it\n        func makeIconList() -> [String]? {\n            // no we don't use random\n            let hash = \"wiki.qaq.\\(specialMonth).\\(specialDay)\".sha256\n            let mod = 8\n            var seed = [Character](hash)\n                .map { Int(String($0)) }\n                .compactMap(\\.self)\n                .reduce(0, +) % mod\n            var result = [String]()\n            while result.count < 16 {\n                result.append(weathers[seed])\n                seed += mod\n                if seed >= weathers.count {\n                    seed -= weathers.count\n                }\n            }\n            return result\n        }\n\n        func makeBigNumber(_ number: Int) -> Text {\n            Text(\" \\(number) \")\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color.orange)\n        }\n\n        func makeLarge(text: LocalizedStringKey) -> Text {\n            Text(text)\n                .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                .foregroundColor(Color(NSColor(red: 87 / 255, green: 86 / 255, blue: 206 / 255, alpha: 1)))\n        }\n\n        func makeImage(with: String) -> Image {\n            let image = NSImage(named: with)\n            let color = NSColor.textColor\n            guard let imageTinted = image?.tint(color: color) else {\n                return Image(systemName: \"xmark\")\n            }\n            return Image(nsImage: imageTinted)\n        }\n    }\n}\n\nprivate extension NSImage {\n    func tint(color: NSColor) -> NSImage {\n        let image = copy() as! NSImage\n        image.lockFocus()\n\n        color.set()\n\n        let imageRect = NSRect(origin: NSZeroPoint, size: image.size)\n        imageRect.fill(using: .sourceAtop)\n\n        image.unlockFocus()\n\n        return image\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS7.swift",
    "content": "//\n//  RS7.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection7: ResultSection, ResultSectionBadgeData {\n    var badgeElements: [ResultSectionUpdateRecipe] = []\n\n    func update(with _: ResultPackage.DataSource) -> (ResultSectionUpdateRecipe)? {\n        nil\n    }\n\n    func setBadge(_ items: [ResultSectionUpdateRecipe]) {\n        badgeElements = items\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            badgeElements: badgeElements\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let badgeElements: [ResultSectionUpdateRecipe]\n\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            Group {\n                HStack {\n                    container\n                }\n                .padding(50)\n            }\n            .background(\n                HStack {\n                    Spacer()\n                    Image(\"badge\")\n                        .resizable()\n                        .aspectRatio(contentMode: .fill)\n                        .frame(width: 250, height: 340)\n                        .rotationEffect(Angle(degrees: -90))\n                        .opacity(0.8)\n                        .offset(x: 24, y: 0)\n//                        .clipped()\n                }\n            )\n        }\n\n        var container: some View {\n            VStack(alignment: .leading, spacing: 0) {\n                Group {\n                    Text(\"成就墙\")\n                        .foregroundColor(Color(NSColor(red: 89 / 255, green: 196 / 255, blue: 189 / 255, alpha: 1)))\n                        .font(.system(size: preferredContextSize * 2, weight: .semibold, design: .rounded))\n                    Spacer()\n                        .frame(height: 20)\n                    Text(\"今年，我获得了不少成就。下面是我愿意和你分享的一些。\")\n                        .frame(height: preferredContentHeight)\n                }\n                Spacer()\n                    .frame(height: 20)\n\n                Group {\n                    ForEach(0 ..< badgeElements.count, id: \\.self) { index in\n                        HStack {\n                            VStack(alignment: .leading, spacing: 4) {\n                                HStack {\n                                    Image(\"custom.circle.fill\")\n                                        .resizable()\n                                        .scaledToFit()\n                                        .frame(width: 6, height: 6)\n                                    Text(\"\\(badgeElements[index].achievement.name)\")\n                                        .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n                                    Spacer()\n                                }\n                                HStack {\n                                    Spacer()\n                                        .frame(width: 16, height: 0)\n                                    Text(\"\\(badgeElements[index].achievement.describe)\")\n                                        .font(.system(size: 8, weight: .semibold, design: .rounded))\n                                        .opacity(0.5)\n                                }\n                            }\n                        }\n                        .padding(.vertical, 5)\n                    }\n                }\n\n                Group {\n                    Divider().hidden()\n                }\n            }\n            .font(.system(size: preferredContextSize, weight: .semibold, design: .rounded))\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS8.swift",
    "content": "//\n//  RS8.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/30.\n//\n\nimport Foundation\nimport SwiftUI\n\nclass ResultSection8: ResultSection {\n    func update(with _: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        nil\n    }\n\n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n        ))\n    }\n\n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n\n    struct AssociatedView: View {\n        let preferredContextSize: CGFloat = 12\n        let preferredContentHeight: CGFloat = 30\n\n        var body: some View {\n            GeometryReader { r in\n                ZStack {\n                    VStack(alignment: .center, spacing: 15) {\n                        Image(\"qrcode\")\n                            .resizable()\n                            .aspectRatio(contentMode: .fit)\n                            .frame(width: 128, height: 128, alignment: .center)\n                        Divider()\n                            .padding(.horizontal, 50)\n                        VStack(spacing: 4) {\n                            Text(\"扫码开启你的专属年度代码提交报告\")\n                                .font(.system(size: 12, weight: .semibold, design: .rounded))\n                                .opacity(0.5)\n                            Text(\"© \\(requiredYear) 标准件厂长@砍砍 & 他的朋友们\")\n                                .font(.system(size: 12, weight: .semibold, design: .rounded))\n                                .opacity(0.5)\n                        }\n                    }\n                }\n                .frame(width: r.size.width, height: r.size.height, alignment: .center)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RS9.swift",
    "content": "//\n//  RS9.swift\n//  MyYearWithGit\n//\n//  Created by diablohl on 2025/1/5.\n//\n\nimport Foundation\nimport SwiftUI\n\nprivate let calendar = Calendar.current\n\nclass ResultSection9: ResultSection {\n    // 每天的提交次数 [日期字符串: 提交次数]\n    var dailyCommits: [String: Int] = [:]\n    // 每月的代码行数 [月份: 代码行数]\n    var monthlyLines: [Int: Int] = [:]\n    // 一年中的最大提交天数（用于热力图）\n    var maxCommitsInDay: Int = 0\n    \n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe? {\n        dailyCommits = [:]\n        monthlyLines = [:]\n        maxCommitsInDay = 0\n        \n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"yyyy-MM-dd\"\n        \n        // 统计每天的提交次数和每月的代码行数\n        for repo in scannerResult.repoResult.repos {\n            for commit in repo.commits {\n                // 统计每天的提交次数\n                let dateKey = dateFormatter.string(from: commit.date)\n                dailyCommits[dateKey, default: 0] += 1\n                \n                // 统计每月的代码行数\n                let month = calendar.component(.month, from: commit.date)\n                let linesAdded = commit.diffFiles.map(\\.increasedLine).reduce(0, +)\n                monthlyLines[month, default: 0] += linesAdded\n            }\n        }\n        \n        // 找出单日最大提交次数\n        maxCommitsInDay = dailyCommits.values.max() ?? 0\n        \n        // 如果全年每天都有提交，返回成就\n        if dailyCommits.count >= 365 {\n            return .init(achievement: .init(\n                name: NSLocalizedString(\"全勤战士\", comment: \"\"),\n                describe: NSLocalizedString(\"全年每天都有提交记录\", comment: \"\")\n            ))\n        }\n        \n        return nil\n    }\n    \n    func makeView() -> AnyView {\n        AnyView(AssociatedView(\n            dailyCommits: dailyCommits,\n            monthlyLines: monthlyLines,\n            maxCommitsInDay: maxCommitsInDay\n        ))\n    }\n    \n    func makeScreenShotView() -> AnyView {\n        makeView()\n    }\n    \n    struct AssociatedView: View {\n        let dailyCommits: [String: Int]\n        let monthlyLines: [Int: Int]\n        let maxCommitsInDay: Int\n        \n        let preferredContextSize: CGFloat = 12\n        \n        // 日期格式化器\n        private var dateFormatter: DateFormatter {\n            let formatter = DateFormatter()\n            formatter.dateFormat = \"yyyy-MM-dd\"\n            return formatter\n        }\n        \n        var body: some View {\n            Group {\n                container\n                    .padding(50)\n            }\n        }\n        \n        var container: some View {\n            VStack(alignment: .center, spacing: 20) {\n                // 标题\n                Text(\"时光印记\")\n                    .font(.system(size: preferredContextSize * 2, weight: .bold, design: .rounded))\n                    .foregroundColor(.orange)\n                \n                // 热力图\n                VStack(alignment: .leading, spacing: 5) {\n                    Text(\"全年提交热力图\")\n                        .font(.system(size: 10, weight: .semibold, design: .rounded))\n                        .foregroundColor(.secondary)\n                    \n                    VStack(spacing: 0) {\n                        heatmapView\n                            .frame(height: 90)\n                        \n                        // 月份时间轴\n                        monthAxisView\n                            .frame(height: 15)\n                    }\n                }\n                \n                Spacer().frame(height: 10)\n                \n                // 每月代码量柱状图\n                VStack(alignment: .leading, spacing: 5) {\n                    Text(\"每月代码量统计\")\n                        .font(.system(size: 10, weight: .semibold, design: .rounded))\n                        .foregroundColor(.secondary)\n                    \n                    HStack(spacing: 0) {\n                        // 纵坐标\n                        yAxisView\n                            .frame(width: 35)\n                        \n                        // 柱状图\n                        monthlyChartView\n                            .frame(height: 120)\n                    }\n                }\n            }\n        }\n        \n        // 热力图视图\n        var heatmapView: some View {\n            GeometryReader { geometry in\n                // 计算每个方块的大小\n                let columns = 53 // 一年约52-53周\n                let rows = 7 // 一周7天\n                let cellWidth = (geometry.size.width - CGFloat(columns + 1) * 2) / CGFloat(columns)\n                let cellHeight = (geometry.size.height - CGFloat(rows + 1) * 2) / CGFloat(rows)\n                let cellSize = min(cellWidth, cellHeight)\n                \n                VStack(spacing: 2) {\n                    ForEach(0..<rows, id: \\.self) { row in\n                        HStack(spacing: 2) {\n                            ForEach(0..<columns, id: \\.self) { col in\n                                let dayIndex = col * 7 + row\n                                let date = getDateForDay(dayIndex)\n                                let dateKey = dateFormatter.string(from: date)\n                                let commits = dailyCommits[dateKey] ?? 0\n                                \n                                Rectangle()\n                                    .fill(getColorForCommits(commits))\n                                    .frame(width: cellSize, height: cellSize)\n                                    .cornerRadius(2)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        \n        // 月份时间轴\n        var monthAxisView: some View {\n            GeometryReader { geometry in\n                let columns = 53\n                let cellWidth = (geometry.size.width - CGFloat(columns + 1) * 2) / CGFloat(columns)\n                \n                ZStack(alignment: .leading) {\n                    ForEach(0..<12, id: \\.self) { monthIndex in\n                        let month = monthIndex + 1\n                        let weekPosition = getWeekPositionForMonth(month)\n                        \n                        if weekPosition < columns {\n                            Text(getMonthAbbreviation(month))\n                                .font(.system(size: 7, weight: .regular, design: .rounded))\n                                .foregroundColor(.secondary)\n                                .frame(width: cellWidth * 4, alignment: .leading)\n                                .offset(x: CGFloat(weekPosition) * (cellWidth + 2))\n                        }\n                    }\n                }\n                .frame(width: geometry.size.width, alignment: .leading)\n            }\n        }\n        \n        // 每月柱状图\n        var monthlyChartView: some View {\n            GeometryReader { geometry in\n                let maxLines = monthlyLines.values.max() ?? 1\n                \n                HStack(alignment: .bottom, spacing: 4) {\n                    ForEach(1...12, id: \\.self) { month in\n                        VStack(spacing: 2) {\n                            // 柱状条\n                            let lines = monthlyLines[month] ?? 0\n                            let height = geometry.size.height - 15\n                            let barHeight = height * CGFloat(lines) / CGFloat(maxLines)\n                            \n                            Spacer()\n                            \n                            RoundedRectangle(cornerRadius: 3)\n                                .fill(\n                                    LinearGradient(\n                                        gradient: Gradient(colors: [\n                                            getColorForMonth(month),\n                                            getColorForMonth(month).opacity(0.6)\n                                        ]),\n                                        startPoint: .top,\n                                        endPoint: .bottom\n                                    )\n                                )\n                                .frame(height: max(barHeight, 2))\n                            \n                            // 月份标签\n                            Text(\"\\(month)\")\n                                .font(.system(size: 8, weight: .regular, design: .rounded))\n                                .foregroundColor(.secondary)\n                        }\n                        .frame(maxWidth: .infinity)\n                    }\n                }\n            }\n        }\n        \n        // 纵坐标轴\n        var yAxisView: some View {\n            GeometryReader { geometry in\n                let maxLines = monthlyLines.values.max() ?? 1\n                let height = geometry.size.height - 15\n                \n                VStack(alignment: .trailing, spacing: 0) {\n                    // 显示 4 个刻度\n                    ForEach([1.0, 0.75, 0.5, 0.25, 0.0], id: \\.self) { ratio in\n                        let value = Int(Double(maxLines) * ratio)\n                        Spacer()\n                            .frame(height: ratio == 1.0 ? 0 : height * 0.25)\n                        HStack(spacing: 2) {\n                            Text(formatNumber(value))\n                                .font(.system(size: 7, weight: .regular, design: .monospaced))\n                                .foregroundColor(.secondary)\n                            Rectangle()\n                                .fill(Color.gray.opacity(0.3))\n                                .frame(width: 3, height: 1)\n                        }\n                    }\n                }\n                .frame(height: height)\n            }\n        }\n        \n        // 根据提交次数返回颜色\n        func getColorForCommits(_ commits: Int) -> Color {\n            if commits == 0 {\n                return Color.gray.opacity(0.1)\n            } else if commits <= maxCommitsInDay / 4 {\n                return Color.green.opacity(0.3)\n            } else if commits <= maxCommitsInDay / 2 {\n                return Color.green.opacity(0.5)\n            } else if commits <= maxCommitsInDay * 3 / 4 {\n                return Color.green.opacity(0.7)\n            } else {\n                return Color.green.opacity(0.9)\n            }\n        }\n        \n        // 获取一年中第几天的日期\n        func getDateForDay(_ dayIndex: Int) -> Date {\n            let year = requiredYear\n            var components = DateComponents()\n            components.year = year\n            components.month = 1\n            components.day = 1\n            \n            if let startDate = calendar.date(from: components),\n               let date = calendar.date(byAdding: .day, value: dayIndex, to: startDate) {\n                return date\n            }\n            return Date()\n        }\n        \n        // 为不同月份分配颜色\n        func getColorForMonth(_ month: Int) -> Color {\n            let colors: [Color] = [\n                .blue, .cyan, .green, .mint, .yellow, .orange,\n                .red, .pink, .purple, .indigo, .teal, .brown\n            ]\n            return colors[(month - 1) % colors.count]\n        }\n        \n        // 格式化数字（简化大数字显示）\n        func formatNumber(_ number: Int) -> String {\n            if number >= 10000 {\n                return String(format: \"%.1fk\", Double(number) / 1000.0)\n            } else if number >= 1000 {\n                return String(format: \"%.1fk\", Double(number) / 1000.0)\n            } else {\n                return \"\\(number)\"\n            }\n        }\n        \n        // 获取月份的周位置（更准确的计算）\n        func getWeekPositionForMonth(_ month: Int) -> Int {\n            let year = requiredYear\n            var components = DateComponents()\n            components.year = year\n            components.month = month\n            components.day = 1\n            \n            guard let monthStart = calendar.date(from: components),\n                  let yearStart = calendar.date(from: DateComponents(year: year, month: 1, day: 1))\n            else {\n                // 降级为粗略估算\n                let daysInMonths = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]\n                return daysInMonths[month - 1] / 7\n            }\n            \n            let days = calendar.dateComponents([.day], from: yearStart, to: monthStart).day ?? 0\n            return days / 7\n        }\n        \n        // 获取月份缩写\n        func getMonthAbbreviation(_ month: Int) -> String {\n            let months = [\"1月\", \"2月\", \"3月\", \"4月\", \"5月\", \"6月\",\n                         \"7月\", \"8月\", \"9月\", \"10月\", \"11月\", \"12月\"]\n            return months[month - 1]\n        }\n    }\n}\n\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/RSProtocol.swift.swift",
    "content": "//\n//  RSProtocol.swift.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport SwiftUI\n\nextension Notification.Name {\n    static let resultContextSwitch = Notification.Name(\"wiki.qaq.result.context.switch\")\n}\n\nstruct ResultSectionUpdateRecipe {\n    let achievement: Achievement\n    struct Achievement: Codable {\n        let name: String\n        let describe: String\n    }\n}\n\nprotocol ResultSection {\n    // get any badge if have\n    func update(with scannerResult: ResultPackage.DataSource) -> ResultSectionUpdateRecipe?\n    func makeView() -> AnyView\n    func makeScreenShotView() -> AnyView\n}\n\nprotocol ResultSectionBadgeData {\n    func setBadge(_: [ResultSectionUpdateRecipe])\n}\n\nenum CommitDateInDay: String, HumanReadable {\n    case midnight // 0:00 <-> 5:00\n    case morning // 5:00 <-> 10:00\n    case noon // 10:00 <-> 14:00\n    case afternoon // 14:00 <-> 17:00\n    case dinner // 17:00 <-> 19:00\n    case night // 19:00 <-> 24:00\n\n    func readableDescription() -> String {\n        switch self {\n        case .midnight: // 0:00 <-> 5:00\n            NSLocalizedString(\"凌晨\", comment: \"\")\n        case .morning: // 5:00 <-> 10:00\n            NSLocalizedString(\"早晨\", comment: \"\")\n        case .noon: // 10:00 <-> 14:00\n            NSLocalizedString(\"中午\", comment: \"\")\n        case .afternoon: // 14:00 <-> 17:00\n            NSLocalizedString(\"下午\", comment: \"\")\n        case .dinner: // 17:00 <-> 19:00\n            NSLocalizedString(\"晚餐时间\", comment: \"\")\n        case .night: // 19:00 <-> 24:00\n            NSLocalizedString(\"晚上\", comment: \"\")\n        }\n    }\n\n    static func convertFrom(hour: Int) -> Self {\n        if (0 ..< 5).contains(hour) {\n            return .midnight\n        }\n        if (5 ..< 10).contains(hour) {\n            return .morning\n        }\n        if (10 ..< 14).contains(hour) {\n            return .noon\n        }\n        if (14 ..< 17).contains(hour) {\n            return .afternoon\n        }\n        if (17 ..< 19).contains(hour) {\n            return .dinner\n        }\n        return .night\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Data/ResultPackage/ResultPackage.swift",
    "content": "//\n//  ResultPackage.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport Foundation\n\nclass ResultPackage {\n    var representedData: DataSource\n\n    init() {\n        representedData = .init(\n            repoResult: .init(repos: []),\n            dictionaryIncrease: [:],\n            dictionaryDecrease: [:],\n            dictionaryCommit: [:]\n        )\n    }\n\n    init(data: DataSource) {\n        representedData = data\n    }\n\n    struct DataSource: Codable {\n        let repoResult: RepoAnalyser.FinalReportCodeable\n        let dictionaryIncrease: [String: Int]\n        let dictionaryDecrease: [String: Int]\n        let dictionaryCommit: [String: Int]\n    }\n\n    var badgeEarned = [ResultSectionUpdateRecipe]()\n\n    let resultSections: [ResultSection] = [\n        // put all sections here\n        ResultSection0(),\n        ResultSection1(),\n        ResultSection2(),\n        ResultSection3(),\n        ResultSection4(),\n        ResultSection5(),\n        ResultSection6(),\n        ResultSection9(),\n        ResultSection7(),\n        ResultSection8(),\n    ]\n\n    func update() {\n        badgeEarned = resultSections\n            .map { $0.update(with: representedData) }\n            .compactMap(\\.self)\n        for section in resultSections {\n            if let object = section as? ResultSectionBadgeData {\n                object.setBadge(badgeEarned)\n            }\n        }\n    }\n\n    func update(with scannerPackage: DataSource) {\n        representedData = scannerPackage\n        update()\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/Entitlements.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "MyYearWithGit/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>ITSAppUsesNonExemptEncryption</key>\n\t<false/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t</dict>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeIconSystemGenerated</key>\n\t\t\t<integer>1</integer>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>年度代码报告</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t\t<key>LSItemContentTypes</key>\n\t\t\t<array>\n\t\t\t\t<string>wiki.qaq.mygitreport</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>UTExportedTypeDeclarations</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>UTTypeConformsTo</key>\n\t\t\t<array>\n\t\t\t\t<string>public.data</string>\n\t\t\t</array>\n\t\t\t<key>UTTypeDescription</key>\n\t\t\t<string>年度代码报告</string>\n\t\t\t<key>UTTypeIconFile</key>\n\t\t\t<string>Avatar</string>\n\t\t\t<key>UTTypeIcons</key>\n\t\t\t<dict>\n\t\t\t\t<key>UTTypeIconBackgroundName</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>UTTypeIconBadgeName</key>\n\t\t\t\t<string>Avatar</string>\n\t\t\t\t<key>UTTypeIconText</key>\n\t\t\t\t<string>Annually Code Report</string>\n\t\t\t</dict>\n\t\t\t<key>UTTypeIdentifier</key>\n\t\t\t<string>wiki.qaq.mygitreport</string>\n\t\t\t<key>UTTypeTagSpecification</key>\n\t\t\t<dict>\n\t\t\t\t<key>public.filename-extension</key>\n\t\t\t\t<array>\n\t\t\t\t\t<string>mygitreport</string>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/BitbucketSheet.swift",
    "content": "//\n//  BitbucketSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/3.\n//\n\nimport SwiftUI\n\nstruct BitbucketSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var username: String = \"\"\n    @State var password: String = \"\"\n    @State var progressSheet: Bool = false\n    @State var currentRepos: [URL] = []\n    @State var deleteKeyword: String = \"\"\n    @State var shouldSetValueOnExit: Bool = false\n\n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"添加来自 Bitbucket 的仓库\",\n            body: AnyView(container)\n        ) { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            if confirmed || shouldSetValueOnExit, currentRepos.count > 0 {\n                NotificationCenter.default.post(name: .sourceAdd, object: makeSourceRegData())\n            }\n            presentationMode.wrappedValue.dismiss()\n        }\n        .sheet(isPresented: $progressSheet, onDismiss: nil) {\n            SheetTemplate.makeProgress(text: \"正在查找代码仓库...\")\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .sourceLoad, object: nil)) { notification in\n            guard let source = (notification as Notification).object as? SourceRegistrationData else {\n                return\n            }\n            print(\"receiving source reg data \\(source)\")\n            shouldSetValueOnExit = true\n            currentRepos = source\n                .repos\n                .compactMap { $0.representedData[.remoteUrl] }\n                .compactMap { URL(string: $0) }\n            username = source\n                .repos\n                .first?\n                .representedData[.username]\n                ?? \"\"\n            password = source\n                .repos\n                .first?\n                .representedData[.token]\n                ?? \"\"\n        }\n    }\n\n    func makeSourceRegData() -> SourceRegistrationData {\n        var repos = [SourceRegistrationData.RepoElement]()\n        for currentRepo in currentRepos {\n            repos.append(.init(remoteUrl: currentRepo, username: username, token: password))\n        }\n        return .init(\n            register: .bitbucket,\n            mainUrl: URL(string: \"https://bitbucket.org/\")!,\n            repos: repos\n        )\n    }\n\n    var container: some View {\n        VStack(alignment: .leading) {\n            helperNotice\n            Divider()\n            if currentRepos.count > 0 {\n                repoDeleter\n                Divider().hidden()\n                ScrollView {\n                    repoStack\n                        .padding(10)\n                }\n                .padding(-10)\n            } else {\n                HStack {\n                    Text(\"用户名\")\n                    TextField(\"\", text: $username)\n                    Text(\"应用密码\")\n                    TextField(\"xxxxxx\", text: $password)\n                }\n                Button {\n                    progressSheet = true\n                    gatheringData(\n                        username: username.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines),\n                        password: password.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)\n                    ) { result in\n                        progressSheet = false\n                        switch result {\n                        case let .success(repos):\n                            currentRepos = repos\n                        case let .failure(error):\n                            print(error.localizedDescription)\n                            SheetTemplate.makeErrorAlert(with: error, delay: 0.5)\n                        }\n                    }\n                } label: {\n                    Text(\"获取数据\")\n                }\n                .disabled(password.count < 1 || username.count < 1)\n            }\n        }\n    }\n\n    var repoDeleter: some View {\n        HStack {\n            TextField(\"删除包含 <关键词> 的仓库，区分大小写。\", text: $deleteKeyword)\n            Button {\n                if deleteKeyword.count > 0 {\n                    currentRepos = currentRepos\n                        .filter { !$0.path.contains(deleteKeyword) }\n                }\n                deleteKeyword = \"\"\n            } label: {\n                Text(\"删除关键词\")\n            }\n            .disabled(deleteKeyword.count < 1)\n        }\n    }\n\n    var repoStack: some View {\n        // validated! show the items\n        ForEach(0 ..< currentRepos.count, id: \\.self) { index in\n            Group {\n                HStack {\n                    Text(currentRepos[index].absoluteString)\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                    Spacer()\n                    Button {\n                        currentRepos.remove(at: index)\n                    } label: {\n                        Text(\"删除\")\n                    }\n                }\n                if index < currentRepos.count - 1 {\n                    Divider()\n                }\n            }\n        }\n    }\n\n    var helperNotice: some View {\n        VStack(alignment: .leading, spacing: 2) {\n            HStack {\n                Text(\"若要连接到 Bitbucket，请参考\")\n                Button {\n                    NSWorkspace.shared.open(URL(\n                        string: \"https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/\"\n                    )!)\n                } label: {\n                    Text(\"这份文档\")\n                }\n                .makeHoverPointer()\n                .buttonStyle(PlainButtonStyle())\n                .foregroundColor(.orange)\n                Text(\"以获取 App 专用密码。\")\n            }\n            Text(\"我们将会列出你的全部仓库地址供你挑选，稍后克隆到本地为分析作准备。\")\n            Text(\"请为 App 专用密码添加 Account 和 Repositories 的 Read 权限。\")\n        }\n        .font(.system(size: 12, weight: .semibold, design: .rounded))\n    }\n}\n\nprivate func gatheringData(username: String, password: String, complete: @escaping (Result<[URL], Error>) -> Void) {\n    func dispatchWorks() -> Result<[URL], Error> {\n        let api = BitbucketApi(config: .init(token: \"\\(username):\\(password)\"))\n        var result = Set<URL>()\n        do {\n            try api.validate()\n            let repos = try api.repositories()\n            repos.forEach { result.insert($0) }\n        } catch {\n            return .failure(error)\n        }\n        guard result.count > 0 else {\n            return .failure(ApiError.emptyData)\n        }\n        return .success([URL](result).sorted { $0.path < $1.path })\n    }\n\n    DispatchQueue.global().async {\n        let sender = dispatchWorks()\n        DispatchQueue.main.async {\n            complete(sender)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/CommitFilterSheet.swift",
    "content": "//\n//  CommitFilterSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/12/1.\n//\n\nimport SwiftUI\n\nstruct FilterSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var inputText: String = \"\"\n    @State var inputCase: CommitFileFilter.BlockType = .nameKeyWord\n\n    @State var blockList: [CommitFileFilter.BlockItem] = []\n\n    var body: some View {\n        SheetTemplate.makeSheet(title: \"排除列表\",\n                                body: AnyView(sheet))\n        { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            presentationMode.wrappedValue.dismiss()\n        }\n    }\n\n    var sheet: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            Text(\n                \"\"\"\n                若要忽略指定的文件，请在此处填写。\n                我们将会传入提交文件的相对路径与你的排除项逐一进行匹配。\n                若有一项满足，则该文件不会被记入。此次提交的其他文件仍会进行统计。\n                \"\"\"\n            )\n            .font(.system(size: 12, weight: .semibold, design: .rounded))\n            HStack {\n                TextField(\"在这里输入表达式\", text: $inputText)\n                Picker(\"过滤器\", selection: $inputCase) {\n                    ForEach(CommitFileFilter.BlockType.allCases, id: \\.self) { val in\n                        Text(val.readableDescription())\n                    }\n                }\n                Button {\n                    blockList.append(.init(type: inputCase, value: inputText))\n                    inputText = \"\"\n                } label: {\n                    Text(\"添加\")\n                }\n                .disabled(inputText.count < 1)\n            }\n            ScrollView {\n                ForEach(0 ..< blockList.count, id: \\.self) { index in\n                    HStack {\n                        Text(\"\\(blockList[index].value) - \\(blockList[index].type.readableDescription())\")\n                        Spacer()\n                        Button {\n                            blockList.remove(at: index)\n                        } label: {\n                            Text(\"删除\")\n                        }\n                    }\n                }\n                .padding(10)\n            }\n            .padding(-10)\n        }\n        .onAppear {\n            blockList = CommitFileFilter.shared.commitBlockList\n        }\n        .onChange(of: blockList) { newValue in\n            CommitFileFilter.shared.commitBlockList = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/ConfigEmailSheet.swift",
    "content": "//\n//  ConfigEmailSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport SwiftUI\n\nstruct ConfigEmailSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var currentEmails: [String] = []\n\n    @State var inputBuffer: String = \"\"\n\n    var body: some View {\n        SheetTemplate.makeSheet(title: \"邮件地址\",\n                                body: AnyView(sheet))\n        { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            presentationMode.wrappedValue.dismiss()\n        }\n        .onAppear {\n            updateEmails()\n        }\n    }\n\n    var sheet: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            Text(\n                \"\"\"\n                若要添加提交邮件地址，请在下方的文本框中输入。一次仅能输入一个，且只支持小写字符。\n                若提交的电子邮件地址不在该列表内，则不会计算此次提交。\n                我们会为你保存电子邮件地址记录，仅需配置一次即可。\n                登录到 GitHub 或 GitLab 账号会自动添加账号所属的电子邮件地址。\n                若有不愿使用的电子邮件地址，请在配置完成仓库以后，再来此处删除。\n                \"\"\"\n            )\n            .font(.system(size: 12, weight: .semibold, design: .rounded))\n            HStack {\n                TextField(\"输入电子邮件地址\", text: $inputBuffer)\n                Button {\n                    guard inputBuffer.count > 0 else {\n                        return\n                    }\n                    User.current.email.insert(inputBuffer.lowercased())\n                    inputBuffer = \"\"\n                    updateEmails()\n                } label: {\n                    Text(\"添加\")\n                }\n                .disabled(!inputBuffer.contains(\"@\"))\n            }\n            ScrollView {\n                ForEach(0 ..< currentEmails.count, id: \\.self) { index in\n                    HStack {\n                        Text(currentEmails[index])\n                        Spacer()\n                        Button {\n                            removeEmail(matching: currentEmails[index])\n                        } label: {\n                            Text(\"删除\")\n                        }\n                    }\n                }\n                .padding(10)\n            }\n            .padding(-10)\n        }\n    }\n\n    func updateEmails() {\n        currentEmails = [String](User.current.email)\n    }\n\n    func removeEmail(matching: String) {\n        let new = User\n            .current\n            .email\n            .filter { $0 != matching }\n        User.current.email = new\n        updateEmails()\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/GitHubRepoSheet.swift",
    "content": "//\n//  GitHubRepoSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nimport OctoKit\n\nstruct GitHubRepoSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var token: String = \"\"\n    @State var progressSheet: Bool = false\n    @State var currentRepos: [URL] = []\n    @State var deleteKeyword: String = \"\"\n    @State var shouldSetValueOnExit: Bool = false\n\n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"添加来自 GitHub 的仓库\",\n            body: AnyView(container)\n        ) { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            if confirmed || shouldSetValueOnExit, currentRepos.count > 0 {\n                NotificationCenter.default.post(name: .sourceAdd, object: makeSourceRegData())\n            }\n            presentationMode.wrappedValue.dismiss()\n        }\n        .sheet(isPresented: $progressSheet, onDismiss: nil) {\n            SheetTemplate.makeProgress(text: \"正在查找代码仓库...\")\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .sourceLoad, object: nil)) { notification in\n            guard let source = (notification as Notification).object as? SourceRegistrationData else {\n                return\n            }\n            print(\"receiving source reg data \\(source)\")\n            shouldSetValueOnExit = true\n            currentRepos = source\n                .repos\n                .compactMap { $0.representedData[.remoteUrl] }\n                .compactMap { URL(string: $0) }\n            token = source\n                .repos\n                .first?\n                .representedData[.token]\n                ?? \"\"\n        }\n    }\n\n    func makeSourceRegData() -> SourceRegistrationData {\n        var repos = [SourceRegistrationData.RepoElement]()\n        for currentRepo in currentRepos {\n            repos.append(.init(remoteUrl: currentRepo, username: \"redacted\", token: token))\n        }\n        return .init(\n            register: .github,\n            mainUrl: URL(string: \"https://github.com/\")!,\n            repos: repos\n        )\n    }\n\n    var container: some View {\n        VStack(alignment: .leading) {\n            helperNotice\n            Divider()\n            if currentRepos.count > 0 {\n                repoDeleter\n                Divider().hidden()\n                ScrollView {\n                    repoStack\n                        .padding(10)\n                }\n                .padding(-10)\n            } else {\n                HStack {\n                    Text(\"个人访问令牌\")\n                    TextField(\"ghp_xxxxxx\", text: $token)\n                }\n                Button {\n                    progressSheet = true\n                    gatheringData(token: token.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)) { result in\n                        progressSheet = false\n                        switch result {\n                        case let .success(repos):\n                            currentRepos = repos\n                        case let .failure(error):\n                            print(error.localizedDescription)\n                            SheetTemplate.makeErrorAlert(with: error, delay: 0.5)\n                        }\n                    }\n                } label: {\n                    Text(\"获取数据\")\n                }\n                .disabled(token.count < 1)\n            }\n        }\n    }\n\n    var repoDeleter: some View {\n        HStack {\n            TextField(\"删除包含 <关键词> 的仓库，区分大小写。\", text: $deleteKeyword)\n            Button {\n                if deleteKeyword.count > 0 {\n                    currentRepos = currentRepos\n                        .filter { !$0.path.contains(deleteKeyword) }\n                }\n                deleteKeyword = \"\"\n            } label: {\n                Text(\"删除关键词\")\n            }\n            .disabled(deleteKeyword.count < 1)\n        }\n    }\n\n    var repoStack: some View {\n        // validated! show the items\n        ForEach(0 ..< currentRepos.count, id: \\.self) { index in\n            Group {\n                HStack {\n                    Text(currentRepos[index].absoluteString)\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                    Spacer()\n                    Button {\n                        currentRepos.remove(at: index)\n                    } label: {\n                        Text(\"删除\")\n                    }\n                }\n                if index < currentRepos.count - 1 {\n                    Divider()\n                }\n            }\n        }\n    }\n\n    var helperNotice: some View {\n        VStack(alignment: .leading, spacing: 2) {\n            HStack {\n                Text(\"若要连接到 GitHub，请参考\")\n                Button {\n                    NSWorkspace.shared.open(URL(\n                        string: \"https://docs.github.com/cn/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token\"\n                    )!)\n                } label: {\n                    Text(\"这份文档\")\n                }\n                .makeHoverPointer()\n                .buttonStyle(PlainButtonStyle())\n                .foregroundColor(.orange)\n                Text(\"以获取个人访问令牌。\")\n            }\n            Text(\"我们将会列出你的全部仓库地址供你挑选，稍后克隆到本地为分析作准备。\")\n            Text(\"请为令牌添加 repo 和 user 的全部权限。\")\n        }\n        .font(.system(size: 12, weight: .semibold, design: .rounded))\n    }\n}\n\nprivate func gatheringData(token: String, complete: @escaping (Result<[URL], Error>) -> Void) {\n    func dispatchWorks() -> Result<[URL], Error> {\n        let api = GitHubApi(config: .init(token: token))\n        var result = Set<URL>()\n        do {\n            try api.validate()\n            let repos = try api.repositories()\n            repos.forEach { result.insert($0) }\n        } catch {\n            return .failure(error)\n        }\n        guard result.count > 0 else {\n            return .failure(ApiError.emptyData)\n        }\n        return .success([URL](result).sorted { $0.path < $1.path })\n    }\n\n    DispatchQueue.global().async {\n        let sender = dispatchWorks()\n        DispatchQueue.main.async {\n            complete(sender)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/GitLabRepoSheet.swift",
    "content": "//\n//  GitLabRepoSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nstruct GitLabRepoSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var instanceUrl: String = \"\"\n    @State var token: String = \"\"\n    @State var progressSheet: Bool = false\n    @State var currentRepos: [URL] = []\n    @State var deleteKeyword: String = \"\"\n    @State var shouldSetValueOnExit: Bool = false\n\n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"添加来自 GitLab 的仓库\",\n            body: AnyView(container)\n        ) { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            if confirmed || shouldSetValueOnExit, currentRepos.count > 0 {\n                NotificationCenter.default.post(name: .sourceAdd, object: makeSourceRegData())\n            }\n            presentationMode.wrappedValue.dismiss()\n        }\n        .sheet(isPresented: $progressSheet, onDismiss: nil) {\n            SheetTemplate.makeProgress(text: \"正在查找代码仓库...\")\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .sourceLoad, object: nil)) { notification in\n            guard let source = (notification as Notification).object as? SourceRegistrationData else {\n                return\n            }\n            print(\"receiving source reg data \\(source)\")\n            shouldSetValueOnExit = true\n            currentRepos = source\n                .repos\n                .compactMap { $0.representedData[.remoteUrl] }\n                .compactMap { URL(string: $0) }\n            instanceUrl = source.mainUrl.absoluteString\n            token = source\n                .repos\n                .first?\n                .representedData[.token]\n                ?? \"\"\n        }\n    }\n\n    func makeSourceRegData() -> SourceRegistrationData {\n        var repos = [SourceRegistrationData.RepoElement]()\n        for currentRepo in currentRepos {\n            repos.append(.init(remoteUrl: currentRepo, username: \"redacted\", token: token))\n        }\n        return .init(\n            register: .gitlab,\n            mainUrl: URL(string: instanceUrl)!,\n            repos: repos\n        )\n    }\n\n    var container: some View {\n        VStack(alignment: .leading) {\n            helperNotice\n            Divider().hidden()\n            if currentRepos.count > 0 {\n                repoDeleter\n                Divider()\n                ScrollView {\n                    repoStack\n                        .padding(10)\n                }\n                .padding(-10)\n            } else {\n                HStack {\n                    Text(\"地址\")\n                    TextField(\"https://your.git.lab.example.com\", text: $instanceUrl)\n                    Text(\"个人访问令牌\")\n                    TextField(\"xxxxxx\", text: $token)\n                }\n                Button {\n                    progressSheet = true\n                    gatheringData(\n                        endpoint: instanceUrl.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines),\n                        token: token.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)\n                    ) { result in\n                        progressSheet = false\n                        switch result {\n                        case let .success(repos):\n                            currentRepos = repos\n                        case let .failure(error):\n                            print(error.localizedDescription)\n                            SheetTemplate.makeErrorAlert(with: error, delay: 0.5)\n                        }\n                    }\n                } label: {\n                    Text(\"获取数据\")\n                }\n                .disabled(token.count < 1 || instanceUrl.count < 1 || URL(string: instanceUrl) == nil)\n            }\n        }\n    }\n\n    var repoDeleter: some View {\n        HStack {\n            TextField(\"删除包含 <关键词> 的仓库，区分大小写。\", text: $deleteKeyword)\n            Button {\n                if deleteKeyword.count > 0 {\n                    currentRepos = currentRepos\n                        .filter { !$0.path.contains(deleteKeyword) }\n                }\n                deleteKeyword = \"\"\n            } label: {\n                Text(\"删除关键词\")\n            }\n            .disabled(deleteKeyword.count < 1)\n        }\n    }\n\n    var repoStack: some View {\n        // validated! show the items\n        ForEach(0 ..< currentRepos.count, id: \\.self) { index in\n            Group {\n                HStack {\n                    Text(currentRepos[index].absoluteString)\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                    Spacer()\n                    Button {\n                        currentRepos.remove(at: index)\n                    } label: {\n                        Text(\"删除\")\n                    }\n                }\n                if index < currentRepos.count - 1 {\n                    Divider()\n                }\n            }\n        }\n    }\n\n    var helperNotice: some View {\n        VStack(alignment: .leading, spacing: 2) {\n            HStack {\n                Text(\"若要连接到 GitLab，请参考\")\n                Button {\n                    NSWorkspace.shared.open(URL(\n                        string: \"https://docs.gitlab.cn/jh/user/profile/personal_access_tokens.html\"\n                    )!)\n                } label: {\n                    Text(\"这份文档\")\n                }\n                .makeHoverPointer()\n                .buttonStyle(PlainButtonStyle())\n                .foregroundColor(.orange)\n                Text(\"以获取个人访问令牌。\")\n            }\n            Text(\"我们将会列出你的全部仓库地址供你挑选，稍后克隆到本地为分析作准备。\")\n            Text(\"请为令牌添加 read_user, read_api, read_repository 权限，需要服务端支持 v4 api。\")\n        }\n        .font(.system(size: 12, weight: .semibold, design: .rounded))\n    }\n}\n\nprivate func gatheringData(endpoint: String, token: String, complete: @escaping (Result<[URL], Error>) -> Void) {\n    func dispatchWorks() -> Result<[URL], Error> {\n        guard let url = URL(string: endpoint) else {\n            return .failure(ApiError.invalidUrl)\n        }\n        let config = GitLabApi.Config(endpoint: url, token: token)\n        let api = GitLabApi(config: config)\n        var result = Set<URL>()\n        do {\n            try api.validate()\n            let repos = try api.repositories()\n            repos.forEach { result.insert($0) }\n        } catch {\n            return .failure(error)\n        }\n        guard result.count > 0 else {\n            return .failure(ApiError.emptyData)\n        }\n        return .success([URL](result).sorted { $0.path < $1.path })\n    }\n\n    DispatchQueue.global().async {\n        let sender = dispatchWorks()\n        DispatchQueue.main.async {\n            complete(sender)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/LocalRepoSheet.swift",
    "content": "//\n//  LocalRepoSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nvar searchShouldStop: Bool = false\n\nstruct LocalRepoSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var locationTint: String = \"\"\n    @State var currentRepos: [URL] = []\n    @State var progressSheet: Bool = false\n    @State var deleteKeyword: String = \"\"\n    @State var shouldSetValueOnExit: Bool = false\n\n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"添加来自本地的仓库\",\n            body: AnyView(container)\n        ) { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            if confirmed || shouldSetValueOnExit, currentRepos.count > 0 {\n                NotificationCenter.default.post(name: .sourceAdd, object: makeSourceRegData())\n            }\n            presentationMode.wrappedValue.dismiss()\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .sourceLoad, object: nil)) { notification in\n            guard let source = (notification as Notification).object as? SourceRegistrationData else {\n                return\n            }\n            print(\"receiving source reg data \\(source)\")\n            shouldSetValueOnExit = true\n            locationTint = source.mainUrl.path\n            currentRepos = source\n                .repos\n                .compactMap { $0.representedData[.localUrl] }\n                .map { URL(fileURLWithPath: $0) }\n        }\n    }\n\n    func makeSourceRegData() -> SourceRegistrationData {\n        var mainUrl = locationTint\n        if !mainUrl.hasPrefix(\"/\") {\n            mainUrl = \"/\" + mainUrl\n        }\n        var repos = [SourceRegistrationData.RepoElement]()\n        for currentRepo in currentRepos {\n            repos.append(.init(localUrl: currentRepo))\n        }\n        return .init(\n            register: .local,\n            mainUrl: URL(fileURLWithPath: mainUrl),\n            repos: repos\n        )\n    }\n\n    var container: some View {\n        VStack(alignment: .leading) {\n            head\n            Divider()\n            if currentRepos.count > 0 {\n                repoDeleter\n                Divider().hidden()\n            }\n            ScrollView {\n                VStack(alignment: .leading) {\n                    if currentRepos.count < 1 {\n                        Text(\"未找到有效的仓库。\")\n                    } else {\n                        repositoriesView\n                    }\n                }\n                .padding(10)\n            }\n            .padding(-10)\n        }\n        .sheet(isPresented: $progressSheet) {} content: {\n            progressSheetView\n        }\n    }\n\n    var head: some View {\n        HStack {\n            TextField(\"请选择一些文件夹，我们将从中搜索仓库。\", text: $locationTint)\n                .disabled(true)\n            Button {\n                select()\n            } label: {\n                Text(\"选择...\")\n            }\n        }\n    }\n\n    var repoDeleter: some View {\n        HStack {\n            TextField(\"删除包含 <关键词> 的仓库，区分大小写。\", text: $deleteKeyword)\n            Button {\n                if deleteKeyword.count > 0 {\n                    currentRepos = currentRepos\n                        .filter { !$0.path.contains(deleteKeyword) }\n                }\n                deleteKeyword = \"\"\n            } label: {\n                Text(\"删除关键词\")\n            }\n            .disabled(deleteKeyword.count < 1)\n        }\n    }\n\n    var repositoriesView: some View {\n        ForEach(0 ..< currentRepos.count, id: \\.self) { index in\n            Group {\n                HStack {\n                    Text(currentRepos[index].path)\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                    Spacer()\n                    Button {\n                        currentRepos.remove(at: index)\n                    } label: {\n                        Text(\"删除\")\n                    }\n                }\n                if index < currentRepos.count - 1 {\n                    Divider()\n                }\n            }\n        }\n    }\n\n    var progressSheetView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n            Text(\"正在查找代码仓库...\")\n            Button {\n                searchShouldStop = true\n            } label: {\n                Text(\"取消\")\n            }\n        }\n        .frame(width: 400, height: 200)\n    }\n\n    func select() {\n        func searchBegin() {\n            let searchRoots = panel\n                .urls\n                .compactMap(\\.self)\n            guard searchRoots.count > 0 else {\n                return\n            }\n            locationTint = searchRoots\n                .map(\\.path)\n                .joined(separator: \", \")\n            progressSheet = true\n            repoEmulator(searchPaths: searchRoots) { result in\n                currentRepos = result\n                progressSheet = false\n            }\n        }\n        let panel = NSOpenPanel()\n        panel.allowsMultipleSelection = true\n        panel.canChooseDirectories = true\n        if let window = NSApp.keyWindow {\n            panel.beginSheetModal(for: window) { response in\n                guard response == .OK else {\n                    return\n                }\n                searchBegin()\n            }\n        } else {\n            guard panel.runModal() == .OK else {\n                return\n            }\n            searchBegin()\n        }\n    }\n}\n\nprivate let blockedDirectoryName: Set<String> = [\n    \"node_modules\"\n]\n\nprivate func repoEmulator(searchPaths: [URL], complete: @escaping ([URL]) -> Void) {\n    var searchResults: Set<URL> = []\n    func search(searchRoot: URL, depth: Int) {\n        guard depth < 64 else {\n            // avoid stack overflow!\n            return\n        }\n        guard !blockedDirectoryName.contains(searchRoot.lastPathComponent.lowercased()) else {\n            print(\"[*] enumerator skipped a blocked dir \\(searchRoot)\")\n            return\n        }\n        guard FileManager.default.fileExists(atPath: searchRoot.path) else {\n            return\n        }\n        let items = (\n            try? FileManager\n                .default\n                .contentsOfDirectory(atPath: searchRoot.path)\n        ) ?? []\n        // a little trick so we don't need to write this block twice\n        // if .git exists and is dir, break down all the loop\n        for item in [\".git\"] + items where !searchShouldStop {\n            let location = searchRoot.appendingPathComponent(item)\n            var isDir = ObjCBool(false)\n            let exists = FileManager\n                .default\n                .fileExists(\n                    atPath: location.path,\n                    isDirectory: &isDir\n                )\n            guard exists else {\n                continue\n            }\n            if isDir.boolValue {\n                if item == \".git\" {\n                    searchResults.insert(searchRoot)\n                }\n                // for submodules\n                search(searchRoot: location, depth: depth + 1)\n            }\n        }\n    }\n    DispatchQueue.global().async {\n        for searchRoot in searchPaths {\n            search(searchRoot: searchRoot, depth: 0)\n        }\n        let returnValue = [URL](\n            searchResults\n        )\n        .sorted {\n            $0.path < $1.path\n        }\n        searchShouldStop = false\n        DispatchQueue.main.async {\n            complete(returnValue)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/MainSheet.swift",
    "content": "//\n//  MainSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nextension Notification.Name {\n    static let sourceAdd = Notification.Name(\"wiki.qaq.mywg.source.add\")\n    static let sourceLoad = Notification.Name(\"wiki.qaq.mywg.source.load\")\n}\n\nstruct MainSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var openSourcePickerSheet: Bool = false\n\n    @State var openLocalSheet: Bool = false\n    @State var openGitHubSheet: Bool = false\n    @State var openGitLabSheet: Bool = false\n    @State var openBitbucketSheet: Bool = false\n    @State var openEmailSheet: Bool = false\n    @State var openFilterSheet: Bool = false\n\n    @State var currentSources: [SourceRegistrationData] = []\n\n    @State var inputNamespaceBuffer: String = \"\"\n\n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"数据源\",\n            body: AnyView(sheetBody)\n        ) { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            if !confirmed, currentSources.count > 0 {\n                let alert = NSAlert()\n                alert.alertStyle = .critical\n                alert.messageText = \"本次配置的数据源将不会保存。\"\n                alert.addButton(withTitle: \"确定\")\n                alert.addButton(withTitle: \"取消\")\n                guard let window = NSApp.keyWindow else {\n                    presentationMode.wrappedValue.dismiss()\n                    return\n                }\n                alert.beginSheetModal(for: window) { response in\n                    if response == .alertFirstButtonReturn {\n                        presentationMode.wrappedValue.dismiss()\n                    }\n                }\n            } else if confirmed, currentSources.count < 1 {\n                let alert = NSAlert()\n                alert.alertStyle = .critical\n                alert.messageText = NSLocalizedString(\"没有可用的数据源，分析被取消。\", comment: \"\")\n                alert.addButton(withTitle: NSLocalizedString(\"确定\", comment: \"\"))\n                presentationMode.wrappedValue.dismiss()\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n                    alert.beginSheetModal(for: NSApp.keyWindow ?? NSWindow(), completionHandler: nil)\n                }\n            } else {\n                presentationMode.wrappedValue.dismiss()\n            }\n            if currentSources.count > 0, confirmed {\n                SourcePackage(sources: currentSources).postToAnalysis()\n            }\n        }\n        .sheet(isPresented: $openSourcePickerSheet, onDismiss: nil, content: {\n            PickSourceSheet()\n                .frame(width: 400, height: 165, alignment: .center)\n        })\n        .sheet(isPresented: $openLocalSheet, onDismiss: nil, content: {\n            prepareFor(sheet: LocalRepoSheet())\n        })\n        .sheet(isPresented: $openGitHubSheet, onDismiss: nil, content: {\n            prepareFor(sheet: GitHubRepoSheet())\n        })\n        .sheet(isPresented: $openGitLabSheet, onDismiss: nil, content: {\n            prepareFor(sheet: GitLabRepoSheet())\n        })\n        .sheet(isPresented: $openBitbucketSheet, onDismiss: nil, content: {\n            prepareFor(sheet: BitbucketSheet())\n        })\n        .sheet(isPresented: $openEmailSheet, onDismiss: nil, content: {\n            prepareFor(sheet: ConfigEmailSheet())\n        })\n        .sheet(isPresented: $openFilterSheet, onDismiss: nil) {\n            prepareFor(sheet: FilterSheet())\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .openSheet, object: nil)) { notification in\n            guard let source = (notification as Notification).object as? SourceRegisters else {\n                return\n            }\n            print(\"opening sheet for \\(source.readableDescription())\")\n            openSheet(for: source)\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .sourceAdd, object: nil)) { notification in\n            guard let source = (notification as Notification).object as? SourceRegistrationData else {\n                return\n            }\n            print(\"receiving source reg data \\(source)\")\n            currentSources.append(source)\n        }\n    }\n\n    func openSheet(for source: SourceRegisters) {\n        switch source {\n        case .local:\n            openLocalSheet = true\n        case .github:\n            openGitHubSheet = true\n        case .gitlab:\n            openGitLabSheet = true\n        case .bitbucket:\n            openBitbucketSheet = true\n        }\n    }\n\n    func prepareFor(sheet: some View) -> some View {\n        sheet\n            .frame(\n                width: preferredApplicationSize.width * 0.9,\n                height: preferredApplicationSize.height * 0.9,\n                alignment: .center\n            )\n    }\n\n    var buttonStack: some View {\n        HStack {\n            Button {\n                openSourcePickerSheet.toggle()\n            } label: {\n                Text(\"添加\")\n            }\n            Button {\n                openEmailSheet.toggle()\n            } label: {\n                Text(\"配置邮箱地址\")\n            }\n            Button {\n                openFilterSheet.toggle()\n            } label: {\n                Text(\"配置排除项\")\n            }\n            HStack(spacing: 4) {\n                Spacer()\n                    .frame(width: 20)\n                Text(\"namespace::\")\n                    .font(Font.system(.body, design: .monospaced))\n                TextField(\"输入昵称 可选\", text: $inputNamespaceBuffer)\n                    .onChange(of: inputNamespaceBuffer) { newValue in\n                        User.current.namespace = newValue\n                    }\n                    .onAppear {\n                        inputNamespaceBuffer = User.current.namespace\n                    }\n            }\n        }\n    }\n\n    var repoStack: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            ForEach(0 ..< currentSources.count, id: \\.self) { index in\n                HStack {\n                    VStack(alignment: .leading) {\n                        Text(currentSources[index].register.readableDescription())\n                            .font(.system(size: 14, weight: .semibold, design: .rounded))\n                        if currentSources[index].register == .local {\n                            Text(currentSources[index].mainUrl.path)\n                                .font(.system(size: 10, weight: .regular, design: .rounded))\n                        } else {\n                            Text(currentSources[index].mainUrl.absoluteString)\n                                .font(.system(size: 10, weight: .regular, design: .rounded))\n                        }\n                    }\n                    Spacer()\n                    Text(\"共 \\(currentSources[index].repos.count) 个仓库\")\n                        .font(.system(size: 10, weight: .regular, design: .rounded))\n                    Button {\n                        repoEdit(on: index)\n                    } label: {\n                        Text(\"编辑\")\n                            .font(.system(size: 10, weight: .semibold, design: .rounded))\n                    }\n                    Button {\n                        currentSources.remove(at: index)\n                    } label: {\n                        Text(\"删除\")\n                            .font(.system(size: 10, weight: .semibold, design: .rounded))\n                    }\n                }\n                if index < currentSources.count - 1 {\n                    Divider()\n                }\n            }\n        }\n    }\n\n    var sheetBody: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            Text(\n                \"\"\"\n                你可添加本地或远端的仓库，不用担心仓库重复，相同的提交哈希仅计算一次。\n                远端仓库在分析时会被载入本地临时文件夹内，分析完成以后会从本地删除。\n                你可能需要额外的步骤来获取远端仓库的访问令牌，登录时会自动添加邮件地址。\n                克隆仅支持使用 HTTPS 协议。\n                \"\"\"\n            )\n            .font(.system(size: 12, weight: .semibold, design: .rounded))\n            Divider()\n            buttonStack\n            Divider()\n            ScrollView {\n                if currentSources.count == 0 {\n                    Text(\"没有可用的数据源，请点击上面的按钮来添加。\")\n                } else {\n                    repoStack\n                }\n            }\n        }\n    }\n\n    func repoEdit(on index: Int) {\n        let data = currentSources.remove(at: index)\n        openSheet(for: data.register)\n        // if user was too fast to close or pressing esc immediately\n        // fuck he/her\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n            NotificationCenter.default.post(name: .sourceLoad, object: data)\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Configuration/PickSourceSheet.swift",
    "content": "//\n//  PickSourceSheet.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nextension Notification.Name {\n    static let openSheet = Notification.Name(\"wiki.qaq.mywg.open.sheet\")\n}\n\nstruct PickSourceSheet: View {\n    @Environment(\\.presentationMode) var presentationMode\n\n    @State var pickerData: SourceRegisters = .local\n\n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"天才第一步\",\n            body: AnyView(container)\n        ) { confirmed in\n            print(\"sheet completed \\(confirmed)\")\n            if confirmed {\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n                    NotificationCenter.default.post(name: .openSheet, object: pickerData)\n                }\n            }\n            presentationMode.wrappedValue.dismiss()\n        }\n    }\n\n    var container: some View {\n        VStack(alignment: .leading) {\n            Text(\"请选择一个需要分析的仓库的来源，我们将在稍后填写具体信息。\")\n                .font(.system(size: 12, weight: .regular, design: .rounded))\n            Picker(\">>\", selection: $pickerData) {\n                ForEach(SourceRegisters.allCases, id: \\.self) { val in\n                    Text(val.readableDescription())\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Generic/AnalysisView.swift",
    "content": "//\n//  AnalysisView.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport SwiftUI\n\n// After analysis, let user to choose his emails\n\nextension Notification.Name {\n    static let analysisComlete = Notification.Name(\"wiki.qaq.analysis.complete\")\n    static let analysisErase = Notification.Name(\"wiki.qaq.analysis.erase\")\n}\n\nstruct AnalysisView: View {\n    let sourcePackage: SourcePackage\n\n    init(sourcePackage: SourcePackage) {\n        self.sourcePackage = sourcePackage\n        _progressTitle = State<String>(initialValue: NSLocalizedString(\"正在处理...\", comment: \"\"))\n        _completed = State<Int>(initialValue: 0)\n        let count = sourcePackage\n            .representedObjects\n            .map(\\.repos.count)\n            .reduce(0, +)\n            + 1\n        _total = State<Int>(initialValue: count)\n    }\n\n    @State var analyserSession = UUID()\n\n    @State var progressTitle: String\n    @State var completed: Int\n    @State var total: Int\n    @State var progress = Progress(totalUnitCount: 0)\n\n    var body: some View {\n        GeometryReader { r in\n            ZStack {\n                VStack {\n                    ProgressView()\n                    ProgressView(progress)\n                    Divider().hidden()\n                    Text(progressTitle)\n                        .font(.system(size: 12, weight: .semibold, design: .rounded))\n                }\n                .animation(.interactiveSpring(), value: progress)\n                .padding()\n            }\n            .frame(width: r.size.width, height: r.size.height, alignment: .center)\n        }\n        .onChange(of: completed) { _ in\n            updateProgress()\n        }\n        .onChange(of: total) { _ in\n            updateProgress()\n        }\n        .onAppear {\n            protectWindowFromClose()\n            constructAnalysis()\n        }\n    }\n\n    func updateProgress() {\n        let builder = Progress(totalUnitCount: Int64(total))\n        builder.completedUnitCount = Int64(completed)\n        progress = builder\n    }\n\n    func constructAnalysis() {\n        DispatchQueue.global().async {\n            // iterate over the source\n            let session = RepoAnalyser.shared.beginSession()\n            analyserSession = session\n            RepoAnalyser.shared.submitEmails(with: [String](User.current.email))\n            let tempDir = sourcePackage.tempDir\n            for package in sourcePackage.representedObjects {\n                switch package.register {\n                case .local:\n                    prepareLocalRepos(with: package, andTempDir: tempDir)\n                case .gitlab, .github, .bitbucket:\n                    prepareRemoteRepos(with: package, andTempDir: tempDir)\n                }\n            }\n            update(title: NSLocalizedString(\"正在生成汇总...\", comment: \"\"))\n            // completed! now let's pass the analysis result\n            let result = RepoAnalyser.shared.commitResult()\n            DispatchQueue.main.async {\n                NotificationCenter.default.post(name: .analysisComlete, object: result)\n            }\n        }\n    }\n\n    func completeOneUnit() {\n        print(\"progress update \\(completed + 1)/\\(total)\")\n        DispatchQueue.main.async {\n            completed += 1\n        }\n    }\n\n    func prepareLocalRepos(with package: SourceRegistrationData, andTempDir tempDir: URL) {\n        func process(repo: SourceRegistrationData.RepoElement) {\n            defer { completeOneUnit() }\n            guard let identifier = repo.representedData[.identifier],\n                  let location = repo.representedData[.localUrl]\n            else {\n                return\n            }\n            let from = URL(fileURLWithPath: location)\n            let dest = tempDir\n                .appendingPathComponent(identifier)\n            update(title: String(format:\n                NSLocalizedString(\"正在创建分析副本 %@...\", comment: \"\"),\n                from.lastPathComponent))\n            // we copy .git file only, and call a reset after that\n            // so .gitignore like node_modules won't go too far\n//            try? FileManager.default.copyItem(at: from, to: dest)\n            do {\n                try FileManager.default.createDirectory(\n                    at: dest,\n                    withIntermediateDirectories: true,\n                    attributes: nil\n                )\n                let git = from.appendingPathComponent(\".git\")\n                let destGit = dest.appendingPathComponent(\".git\")\n                try FileManager.default.copyItem(at: git, to: destGit)\n                let currentDir = FileManager.default.currentDirectoryPath\n                FileManager.default.changeCurrentDirectoryPath(dest.path)\n                _ = AuxiliaryExecuteWrapper.spawn(\n                    command: AuxiliaryExecuteWrapper.git,\n                    args: [\n                        \"reset\",\n                        \"--hard\",\n                    ],\n                    timeout: 0\n                ) { _ in\n                }\n                FileManager.default.changeCurrentDirectoryPath(currentDir)\n            } catch {\n                print(error.localizedDescription)\n                return\n            }\n\n            update(title: String(format:\n                NSLocalizedString(\"正在分析 %@...\", comment: \"\"),\n                from.lastPathComponent))\n            autoreleasepool {\n                analysisRepo(at: dest)\n            }\n        }\n        for repo in package.repos {\n            process(repo: repo)\n        }\n    }\n\n    func prepareRemoteRepos(with package: SourceRegistrationData, andTempDir tempDir: URL) {\n        func process(repo: SourceRegistrationData.RepoElement) {\n            defer { completeOneUnit() }\n            guard let identifier = repo.representedData[.identifier],\n                  let location = repo.representedData[.remoteUrl]\n            else {\n                return\n            }\n            let dest = tempDir\n                .appendingPathComponent(identifier)\n            if package.register == .github {\n                guard let token = repo.representedData[.token] else {\n                    return\n                }\n                downloadRepoFromHub(\n                    token: token,\n                    location: location,\n                    dest: dest\n                )\n            }\n            if package.register == .gitlab {\n                guard let token = repo.representedData[.token] else {\n                    return\n                }\n                downloadRepoFromLab(\n                    mainUrl: package.mainUrl.absoluteString,\n                    token: token,\n                    location: location,\n                    dest: dest\n                )\n            }\n            if package.register == .bitbucket {\n                guard let token = repo.representedData[.token] else {\n                    return\n                }\n                downloadRepoFromBitbucket(\n                    mainUrl: package.mainUrl.absoluteString,\n                    username: repo.representedData[.username] ?? \"broken-auth\",\n                    token: token,\n                    location: location,\n                    dest: dest\n                )\n            }\n            update(title: String(format:\n                NSLocalizedString(\"正在分析 %@...\", comment: \"\"),\n                location))\n            analysisRepo(at: dest)\n        }\n        for repo in package.repos {\n            process(repo: repo)\n        }\n    }\n\n    func downloadRepoFromHub(token: String, location: String, dest: URL) {\n        update(title: String(format:\n            NSLocalizedString(\"正在从 Github 下载仓库 %@...\", comment: \"\"),\n            location))\n        var location = location\n        if location.hasPrefix(\"http\"), let url = URL(string: location) {\n            location = url.path\n        }\n        let realCloneLink = \"https://\"\n            + token\n            + \"@github.com\"\n            + location\n        AuxiliaryExecuteWrapper.spawn(\n            command: AuxiliaryExecuteWrapper.git,\n            args: [\"clone\", realCloneLink, dest.path],\n            timeout: 0\n        ) { output in\n            print(output)\n        }\n    }\n\n    func downloadRepoFromLab(mainUrl: String, token: String, location: String, dest: URL) {\n        update(title: String(format:\n            NSLocalizedString(\"正在从 GitLab 下载仓库 %@...\", comment: \"\"),\n            location))\n        var location = location\n        if location.hasPrefix(\"http\"), let url = URL(string: location) {\n            location = url.path\n        }\n        var realCloneLink = \"\"\n        var trimmer = mainUrl\n        if trimmer.hasPrefix(\"http://\") {\n            trimmer.removeFirst(\"http://\".count)\n            realCloneLink = \"http://oauth2:\\(token)@\\(trimmer)\"\n        }\n        if trimmer.hasPrefix(\"https://\") {\n            trimmer.removeFirst(\"https://\".count)\n            realCloneLink = \"https://oauth2:\\(token)@\\(trimmer)\"\n        }\n        if realCloneLink.hasSuffix(\"/\") {\n            realCloneLink.removeLast()\n        }\n        realCloneLink += location\n        AuxiliaryExecuteWrapper.spawn(\n            command: AuxiliaryExecuteWrapper.git,\n            args: [\"clone\", realCloneLink, dest.path],\n            timeout: 0\n        ) { output in\n            print(output)\n        }\n    }\n\n    func downloadRepoFromBitbucket(mainUrl: String, username: String, token: String, location: String, dest: URL) {\n        update(title: String(format:\n            NSLocalizedString(\"正在从 Bitbucket 下载仓库 %@...\", comment: \"\"),\n            location))\n        var location = location\n        if location.hasPrefix(\"http\"), let url = URL(string: location) {\n            location = url.path\n        }\n        var realCloneLink = \"\"\n        var trimmer = mainUrl\n        if trimmer.hasPrefix(\"http://\") {\n            trimmer.removeFirst(\"http://\".count)\n            realCloneLink = \"http://\\(username):\\(token)@\\(trimmer)\"\n        }\n        if trimmer.hasPrefix(\"https://\") {\n            trimmer.removeFirst(\"https://\".count)\n            realCloneLink = \"https://\\(username):\\(token)@\\(trimmer)\"\n        }\n        if realCloneLink.hasSuffix(\"/\") {\n            realCloneLink.removeLast()\n        }\n        realCloneLink += location\n        AuxiliaryExecuteWrapper.spawn(\n            command: AuxiliaryExecuteWrapper.git,\n            args: [\"clone\", realCloneLink, dest.path],\n            timeout: 0\n        ) { output in\n            print(output)\n        }\n    }\n\n    func analysisRepo(at location: URL) {\n        RepoAnalyser.shared.analysis(at: location, session: analyserSession)\n    }\n\n    func update(title: String) {\n        DispatchQueue.main.async {\n            progressTitle = title\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Generic/MainView.swift",
    "content": "//\n//  MainView.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport ColorfulX\nimport SwiftUI\n\nprivate let imageSize: CGFloat = 12\n\nprivate let mainTitleTextList = [\n    NSLocalizedString(\"我和我的代码，还有这一年。\", comment: \"\"),\n] + [\n    #\"print(\"Hello World\")\"#, // Python\n    #\"System.out.println(\"Hello World\");\"#, // Java\n    #\"printf(\"Hello World\\n\");\"#, // C\n    #\"std::cout << \"Hello World\" << std::endl;\"#, // C++\n    #\"console.log(\"Hello World\");\"#, // JavaScript\n    #\"puts \"Hello World\"\"#, // Ruby\n    #\"<?php echo \"Hello World\"; ?>\"#, // PHP\n    #\"print(\"Hello World\")\"#, // Swift\n    #\"fmt.Println(\"Hello World\")\"#, // Go\n    #\"Console.WriteLine(\"Hello World\");\"#, // C#\n    #\"echo \"Hello World\";\"#, // Bash\n    #\"Write-Output \"Hello World\"#, // PowerShell\n    #\"echo('Hello World')\"#, // Lua\n    #\"(println \"Hello World\")\"#, // Clojure\n    #\"echo Hello, World!\"#, // Batch\n    #\"DISPLAY 'Hello World'\"#, // COBOL\n    #\"write('Hello World')\"#, // Pascal\n    #\"io.write(\"Hello World\\n\")\"#, // Lua\n    #\"print *, \"Hello World\"#, // Fortran\n    #\"PRINT \"Hello World\" \"#, // BASIC\n    #\"printf(\"Hello World\\n\");\"#, // Kotlin (via C interop)\n    #\"System.Console.WriteLine(\"Hello World\");\"#, // F#\n    #\"print_endline \"Hello World\";\"#, // OCaml\n    #\"IO.puts(\"Hello World\")\"#, // Elixir\n    #\"'Hello World'\"#, // Haskell (via GHCi)\n    #\"print(\"Hello World!\")\"#, // R\n]\n.shuffled()\n\nprivate let lightColorfulTheme: [ColorfulPreset] = [\n    .spring, .winter,\n]\nprivate let darkColorfulTheme: [ColorfulPreset] = [\n    .aurora, .starry, .jelly, .lavandula, .summer,\n]\n\nstruct MainView: View {\n    @State var colors: [Color] = []\n    @State var openMainSheet: Bool = false\n    @State var openThankSheet: Bool = false\n    @Environment(\\.colorScheme) var colorScheme\n\n    let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()\n\n    var body: some View {\n        content\n            .padding(60)\n            .frame(maxWidth: .infinity, maxHeight: .infinity)\n            .overlay(\n                VStack {\n                    Spacer()\n                    Text(\"由 标准件厂长@砍砍 制作\")\n                        .font(.system(size: 8, weight: .semibold, design: .rounded))\n                        .multilineTextAlignment(.center)\n                        .opacity(0.5)\n                        .padding()\n                        .onTapGesture {\n                            NSWorkspace.shared.open(URL(string: \"https://twitter.com/Lakr233\")!)\n                        }\n                        .makeHoverPointer()\n                }\n            )\n            .background(\n                ColorfulView(color: $colors, speed: .constant(0.5))\n                    .opacity(0.25)\n                    .ignoresSafeArea()\n                    .onChange(of: colorScheme) { _ in\n                        updateColorScheme()\n                    }\n                    .onReceive(timer) { _ in\n                        updateColorScheme()\n                    }\n                    .onAppear { updateColorScheme() }\n            )\n            .sheet(isPresented: $openMainSheet) {} content: {\n                MainSheet()\n                    .frame(\n                        width: preferredApplicationSize.width * 0.8,\n                        height: preferredApplicationSize.height * 0.8,\n                        alignment: .center\n                    )\n            }\n            .sheet(isPresented: $openThankSheet) {} content: {\n                ThanksView()\n                    .frame(\n                        width: preferredApplicationSize.width * 0.8,\n                        height: preferredApplicationSize.height * 0.8,\n                        alignment: .center\n                    )\n            }\n    }\n\n    var content: some View {\n        VStack(alignment: .leading, spacing: 15) {\n            Spacer()\n                .frame(height: 80)\n            TextTypeEffectView(\n                size: preferredTitleSize,\n                textList: mainTitleTextList\n            )\n            .frame(height: 60)\n            HStack {\n                Button {\n                    openMainSheet.toggle()\n                } label: {\n                    HStack {\n                        Image(systemName: \"arrow.right\")\n                        Text(\"开启我的年度报告\")\n                    }\n                }\n                Button {\n                    openThankSheet.toggle()\n                } label: {\n                    HStack {\n                        Text(\"致谢\")\n                    }\n                }\n                #if DEBUG\n                    Button {\n                        NotificationCenter.default.post(\n                            name: .analysisComlete,\n                            object: ResultPackage()\n                        )\n                    } label: {\n                        HStack {\n                            Text(String(\"任意门\"))\n                                .foregroundColor(.orange)\n                        }\n                    }\n                #endif\n            }\n            Divider().hidden()\n            sourceLink\n        }\n    }\n\n    var sourceLink: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            Text(\"此年度报告支持以下数据源\")\n                .font(.system(size: imageSize, weight: .regular, design: .rounded))\n            HStack {\n                makeIconImage(with: \"git\")\n                Text(\"Git\")\n                    .font(.system(size: imageSize, weight: .regular, design: .rounded))\n            }\n            .onTapGesture {\n                NSWorkspace.shared.open(URL(string: \"https://git-scm.com/\")!)\n            }\n            HStack {\n                makeIconImage(with: \"gitlab\")\n                Text(\"GitLab\")\n                    .font(.system(size: imageSize, weight: .regular, design: .rounded))\n            }\n            .onTapGesture {\n                NSWorkspace.shared.open(URL(string: \"https://gitlab.com/\")!)\n            }\n            HStack {\n                makeIconImage(with: \"github\")\n                Text(\"GitHub\")\n                    .font(.system(size: imageSize, weight: .regular, design: .rounded))\n            }\n            .onTapGesture {\n                NSWorkspace.shared.open(URL(string: \"https://github.com/\")!)\n            }\n            HStack {\n                makeIconImage(with: \"bitbucket\")\n                Text(\"Bitbucket\")\n                    .font(.system(size: imageSize, weight: .regular, design: .rounded))\n            }\n            .onTapGesture {\n                NSWorkspace.shared.open(URL(string: \"https://Bitbucket.org/\")!)\n            }\n        }\n    }\n\n    func makeIconImage(with: String) -> some View {\n        Image(with)\n            .resizable()\n            .antialiased(true)\n            .aspectRatio(contentMode: .fit)\n            .frame(width: imageSize, height: imageSize, alignment: .center)\n    }\n\n    func updateColorScheme() {\n        let candidates: [ColorfulPreset] = colorScheme == .light ? lightColorfulTheme : darkColorfulTheme\n        guard let c = candidates.randomElement() else { return }\n        colors = c.colors.map { .init($0) }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Generic/ResultView.swift",
    "content": "//\n//  ResultView.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport Quartz\nimport SwiftUI\n\nextension Notification.Name {\n    static let scroll = Notification.Name(\"wiki.qaq.scroll.result\")\n}\n\nfinal class PageData: ObservableObject {\n    var pageIndexes: [Int] = []\n    var pageRects: [CGRect] = []\n    var numberOfPages: Int {\n        pageIndexes.count\n    }\n\n    func rect(of pageAtIndex: Int) -> CGRect? {\n        if let pageIndex = pageIndexes.firstIndex(of: pageAtIndex) {\n            return pageRects[pageIndex]\n        }\n        return nil\n    }\n}\n\nstruct ResultView: View {\n    let resultPackage: ResultPackage\n    @EnvironmentObject var pageData: PageData\n\n    var displayPackage: some View {\n        VStack(alignment: .center, spacing: 25) {\n            ForEach(0 ..< resultPackage.resultSections.count, id: \\.self) { index in\n                ZStack {\n                    Rectangle()\n                        .foregroundColor(Color(NSColor.textBackgroundColor))\n                        .frame(\n                            width: preferredApplicationSize.width * 0.9,\n                            height: preferredApplicationSize.height * 0.9,\n                            alignment: .center\n                        )\n                        .shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 0)\n                    resultPackage\n                        .resultSections[index]\n                        .makeView()\n                        .frame(\n                            width: preferredApplicationSize.width * 0.9,\n                            height: preferredApplicationSize.height * 0.9,\n                            alignment: .center\n                        )\n                        .clipped()\n                }\n\n                // MARK: INDEX IS USED AS IDENTITY\n\n                .id(index)\n            }\n            Spacer()\n                .frame(height: 20)\n        }\n    }\n\n    var screenShotPackage: some View {\n        VStack(alignment: .center, spacing: 25) {\n            ForEach(0 ..< resultPackage.resultSections.count, id: \\.self) { index in\n                ZStack {\n                    Rectangle()\n                        .foregroundColor(Color(NSColor.textBackgroundColor))\n                        .frame(\n                            width: preferredApplicationSize.width * 0.9,\n                            height: preferredApplicationSize.height * 0.9,\n                            alignment: .center\n                        )\n                        .shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 0)\n                    resultPackage\n                        .resultSections[index]\n                        .makeScreenShotView()\n                        .frame(\n                            width: preferredApplicationSize.width * 0.9,\n                            height: preferredApplicationSize.height * 0.9,\n                            alignment: .center\n                        )\n                        .clipped()\n                    GeometryReader { g -> Color in\n                        let frame = g.frame(in: CoordinateSpace.global)\n                        if !pageData.pageIndexes.contains(index) {\n                            pageData.pageIndexes.append(index)\n                            pageData.pageRects.append(frame)\n                        }\n                        return Color.clear\n                    }\n                }\n            }\n        }\n        .padding(50)\n        .background(Color(NSColor.textBackgroundColor))\n    }\n\n    var body: some View {\n        ScrollView(.vertical, showsIndicators: false) {\n            Group {\n                ScrollViewReader { reader in\n                    Group {\n                        VStack(alignment: .center, spacing: 20) {\n                            Divider()\n                                .hidden()\n\n                            Spacer()\n                                .frame(height: 30)\n                            Text(\"↓ 向下滑动开启报告 ↓\")\n                                .font(.system(size: 10, weight: .bold, design: .rounded))\n                                .opacity(0.5)\n\n                            Spacer()\n                                .frame(height: 10)\n                            displayPackage\n\n                            HStack(alignment: .center, spacing: 12.0) {\n                                Button {\n                                    saveReport()\n                                } label: {\n                                    Text(\"导出分析数据\")\n                                }\n\n                                Button {\n                                    guard let keyWindow = NSApp.keyWindow,\n                                          let image = takeSnapshot(of: screenShotPackage)\n                                    else {\n                                        print(\"failed to create image\")\n                                        return\n                                    }\n                                    DispatchQueue.main.async {\n                                        let savePanel = NSSavePanel()\n                                        savePanel.nameFieldStringValue = NSLocalizedString(\"我的年度代码总结\", comment: \"\") + \".png\"\n                                        savePanel.beginSheetModal(for: keyWindow) { result in\n                                            if result.rawValue == NSApplication.ModalResponse.OK.rawValue,\n                                               let url = savePanel.url,\n                                               let data = image.pngData\n                                            {\n                                                try? data.write(to: url)\n                                            }\n                                        }\n                                    }\n                                } label: {\n                                    Text(\"生成截图\")\n                                }\n\n                                Button {\n                                    guard let keyWindow = NSApp.keyWindow,\n                                          let pdfURL = generatePDF(of: screenShotPackage, with: pageData)\n                                    else {\n                                        print(\"failed to create pdf\")\n                                        return\n                                    }\n                                    DispatchQueue.main.async {\n                                        if let pdfDocument = PDFDocument(url: pdfURL) {\n                                            pdfDocument.documentAttributes?[\"Title\"] = NSLocalizedString(\"我的年度代码总结\", comment: \"\")\n                                            let printInfo = NSPrintInfo.shared\n                                            printInfo.orientation = .landscape\n                                            printInfo.topMargin = 0\n                                            printInfo.bottomMargin = 0\n                                            printInfo.leftMargin = 0\n                                            printInfo.rightMargin = 0\n                                            printInfo.horizontalPagination = .fit\n                                            printInfo.verticalPagination = .fit\n                                            if let printOperation = pdfDocument.printOperation(\n                                                for: printInfo,\n                                                scalingMode: .pageScaleDownToFit,\n                                                autoRotate: true\n                                            ) {\n                                                printOperation.runModal(\n                                                    for: keyWindow,\n                                                    delegate: nil,\n                                                    didRun: nil,\n                                                    contextInfo: nil\n                                                )\n                                            }\n                                        }\n                                    }\n                                } label: {\n                                    Text(\"打印\")\n                                }\n\n                                Button {\n                                    let alert = NSAlert()\n                                    alert.alertStyle = .critical\n                                    alert.messageText = NSLocalizedString(\"这会删除分析记录。\", comment: \"\")\n                                    alert.addButton(withTitle: NSLocalizedString(\"删除\", comment: \"\"))\n                                    alert.addButton(withTitle: NSLocalizedString(\"取消\", comment: \"\"))\n                                    guard let window = NSApp.keyWindow else {\n                                        NotificationCenter.default.post(name: .analysisErase, object: nil)\n                                        return\n                                    }\n                                    alert.beginSheetModal(for: window) { response in\n                                        if response == .alertFirstButtonReturn {\n                                            NotificationCenter.default.post(name: .analysisErase, object: nil)\n                                        }\n                                    }\n                                } label: {\n                                    Text(\"重新开始\")\n                                }\n                            }\n\n                            Spacer()\n                                .frame(height: 50)\n                            Divider()\n                                .hidden()\n                        }\n                    }\n                    .onReceive(NotificationCenter.default.publisher(for: .scroll, object: nil)) { notification in\n                        guard let identity = notification.object as? Int else {\n                            return\n                        }\n                        reader.scrollTo(identity)\n                        guard resultPackage.resultSections.count > identity, identity >= 0 else {\n                            return\n                        }\n                        // tell our view to perform the animation if needed\n                        // we are passing the reference from the struct so we can check object == Self\n                        NotificationCenter.default.post(\n                            name: .resultContextSwitch,\n                            object: resultPackage.resultSections[identity]\n                        )\n                    }\n                }\n            }\n            .padding(10)\n        }\n        .padding(-10)\n    }\n\n    func saveReport() {\n        let data = resultPackage.representedData\n        guard let jsonData = try? JSONEncoder().encode(data) else {\n            print(\"failed to create json data\")\n            return\n        }\n        guard let keyWindow = NSApp.keyWindow else {\n            return\n        }\n        DispatchQueue.main.async {\n            let savePanel = NSSavePanel()\n            savePanel.nameFieldStringValue = String(\n                format: NSLocalizedString(\"%@的年度代码报告\", comment: \"\"),\n                User.current.namespace.isEmpty ? NSLocalizedString(\"无名氏\", comment: \"\") : User.current.namespace\n            ) + \".mygitreport\"\n            savePanel.beginSheetModal(for: keyWindow) { result in\n                if result.rawValue == NSApplication.ModalResponse.OK.rawValue,\n                   let url = savePanel.url\n                {\n                    try? jsonData.write(to: url)\n                }\n            }\n        }\n    }\n\n    func takeSnapshot(of view: some View) -> NSImage? {\n        let hostingView = NSHostingView(rootView: view)\n        hostingView.frame = .init(origin: .zero, size: hostingView.fittingSize)\n\n        let bounds = hostingView.bounds\n        guard let bitmapRep = hostingView.bitmapImageRepForCachingDisplay(in: bounds) else {\n            return nil\n        }\n        hostingView.cacheDisplay(in: bounds, to: bitmapRep)\n\n        let image = NSImage(size: bounds.size)\n        image.addRepresentation(bitmapRep)\n\n        return image\n    }\n\n    func generatePDF(of view: some View, with pageData: PageData) -> URL? {\n        let hostingView = NSHostingView(rootView: view)\n        hostingView.frame = .init(origin: .zero, size: hostingView.fittingSize)\n\n        let bounds = hostingView.bounds\n        guard let bitmapRep = hostingView.bitmapImageRepForCachingDisplay(in: bounds) else {\n            return nil\n        }\n        hostingView.cacheDisplay(in: bounds, to: bitmapRep)\n\n        if let pageBounds = pageData.rect(of: 0) {\n            let dpi: CGFloat = 300.0 / 72.0\n            var mediaBox = CGRect(origin: .zero, size: CGSize(width: pageBounds.size.width * dpi, height: pageBounds.size.height * dpi))\n            let tempPathComponent = \"MyYearWithGit.pdf\"\n            var tempPath = URL(fileURLWithPath: NSTemporaryDirectory())\n            tempPath.appendPathComponent(tempPathComponent)\n            if let pdfCtx = CGContext(tempPath as CFURL, mediaBox: &mediaBox, nil) {\n                for pageIndex in (0 ... pageData.numberOfPages - 1).reversed() {\n                    guard let pageRect = pageData.rect(of: pageIndex) else {\n                        continue\n                    }\n                    pdfCtx.beginPDFPage(nil)\n                    pdfCtx.saveGState()\n                    pdfCtx.translateBy(x: -pageRect.minX * dpi, y: -pageRect.minY * dpi)\n                    pdfCtx.scaleBy(x: dpi, y: dpi)\n                    pdfCtx.concatenate(CGAffineTransform(\n                        a: 1,\n                        b: 0,\n                        c: 0,\n                        d: -1,\n                        tx: 0,\n                        ty: bounds.height\n                    ))\n                    hostingView.layer?.render(in: pdfCtx)\n                    pdfCtx.restoreGState()\n                    pdfCtx.endPDFPage()\n                }\n\n                pdfCtx.closePDF()\n                return tempPath\n            }\n        }\n\n        return nil\n    }\n}\n\nextension NSImage {\n    var pngData: Data? {\n        guard let tiffRepresentation, let bitmapImage = NSBitmapImageRep(data: tiffRepresentation) else { return nil }\n        return bitmapImage.representation(using: .png, properties: [:])\n    }\n\n    func pngWrite(to url: URL, options: Data.WritingOptions = .atomic) -> Bool {\n        do {\n            try pngData?.write(to: url, options: options)\n            return true\n        } catch {\n            print(error)\n            return false\n        }\n    }\n}\n\nextension String {\n    var uppercase: String {\n        #if DEBUG\n            return lowercased()\n        #else\n            return uppercased()\n        #endif\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Generic/ThanksView.swift",
    "content": "//\n//  ThanksView.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/29.\n//\n\nimport MarkdownUI\nimport SwiftUI\n\nstruct ThanksView: View {\n    @Environment(\\.presentationMode) var presentationMode\n    \n    var body: some View {\n        SheetTemplate.makeSheet(\n            title: \"致谢\",\n            body: AnyView(container)\n        ) { _ in\n            presentationMode.wrappedValue.dismiss()\n        }\n    }\n\n    @ViewBuilder\n    var container: some View {\n            ScrollView {\n                Markdown {\n                    NSLocalizedString(\n                        \"\"\"\n                        **制作名单**\n\n                        此项目由 [标准件厂长@砍砍](https://twitter.com/Lakr233) 发起，设计，并完成撰写。\n\n                        与此同时，感谢 [Cyandev](https://twitter.com/unixzii) [拾一](https://twitter.com/__oquery) [82Flex](https://twitter.com/82flex) 与我一同前行。排名不分先后。\n\n                        **软件许可证**\n\n                        此项目使用了 [Octokit](https://github.com/nerdishbynature/octokit.swift) 与 [RequestKit](https://github.com/nerdishbynature/RequestKit.git) 来处理 GitHub 相关 Api，请参考他们的使用许可。\n\n                        此项目使用了来自 [Git](https://git-scm.com/downloads/logos) [GitHub](https://github.com/logos) [GitLab](https://about.gitlab.com/press/press-kit/) [Bitbucket](https://Bitbucket.org/) 的相关图标，请参考他们的使用许可。\n\n                        2021 冬，最后更新于 2024 年。\n                        \"\"\"\n                        , comment: \"\")\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Helper/CodeTiles.swift",
    "content": "//\n//  CodeTiles.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/28.\n//\n\nimport SwiftUI\n\nstruct CodeTiles: View {\n    // day of the year -> commit counts\n    let commits: [Int: Int]\n\n    init(commits: [Int: Int], animated: Bool = true) {\n        self.commits = commits\n        print(\"total commits: \\(commits.values.reduce(0, +))\")\n        if animated {\n            _displayCommit = State<[Int: Int]>(initialValue: [:])\n        } else {\n            _displayCommit = State<[Int: Int]>(initialValue: commits)\n        }\n    }\n\n    let size: CGFloat = 14\n\n    @State var currentLighter = 0\n    @State var displayCommit: [Int: Int]\n\n    let timer = Timer\n        .publish(every: 0.01, on: .main, in: .common)\n        .autoconnect()\n\n    var body: some View {\n        LazyVGrid(\n            columns: [\n                GridItem(.adaptive(minimum: size, maximum: size), spacing: 1),\n            ],\n            alignment: .leading,\n            spacing: 1\n        ) {\n            ForEach(1 ..< 365) { index in\n                Rectangle()\n                    .foregroundColor(obtainColor(for: index))\n                    .frame(width: size, height: size)\n                    .animation(.interactiveSpring(), value: displayCommit)\n            }\n        }\n        .frame(width: 14 * (size + 1))\n        .onReceive(timer) { _ in\n            if currentLighter > 400 { // for robust\n                return\n            }\n            repeat {\n//                print(\"timer load \\(currentLighter) \\(commits[currentLighter, default: 0])\")\n                displayCommit[currentLighter] = commits[currentLighter, default: 0]\n                currentLighter += 1\n            } while commits[currentLighter, default: 0] == 0 && currentLighter < 400\n        }\n    }\n\n    // MARK: if color goes wrong, check here, pass index not commit count\n\n    func obtainColor(for index: Int) -> Color {\n        let commitCount = displayCommit[index] ?? 0\n        var opacity = 0.05\n        if commitCount > 0 {\n            opacity = 0.2\n        }\n        opacity += CGFloat(commitCount) * 0.2\n        if opacity > 1 {\n            opacity = 1\n        }\n        return Color(\n            #colorLiteral(red: 0.7458761334, green: 0.7851135731, blue: 0.9899476171, alpha: 1)\n        )\n        .opacity(opacity)\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Helper/SheetTemplate.swift",
    "content": "//\n//  SheetTemplate.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nlet preferredSheetTitleSize: CGFloat = 20\n\nenum SheetTemplate {\n    typealias Confirmed = Bool\n    static func makeSheet(title: LocalizedStringKey, body: AnyView, complete: @escaping (Confirmed) -> Void) -> some View {\n        VStack(alignment: .leading, spacing: 10) {\n            Text(title)\n                .font(.system(size: preferredSheetTitleSize, weight: .semibold, design: .rounded))\n            Divider()\n            GeometryReader { _ in\n                body\n            }\n            Divider()\n            HStack {\n                Button {\n                    complete(false)\n                } label: {\n                    Text(\"取消\")\n                }\n                .keyboardShortcut(.cancelAction)\n                Spacer()\n                Button {\n                    complete(true)\n                } label: {\n                    Text(\"完成\")\n                }\n            }\n        }\n        .padding()\n    }\n\n    static func makeProgress(text: String) -> some View {\n        ProgressView(text)\n            .frame(width: 400, height: 200)\n    }\n\n    static func makeErrorAlert(with error: Error, delay: Double = 0) {\n        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {\n            let alert = NSAlert()\n            alert.alertStyle = .critical\n            alert.messageText = error.localizedDescription\n            alert.addButton(withTitle: \"确定\")\n            alert.beginSheetModal(for: NSApp.keyWindow ?? NSWindow()) { _ in\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Helper/TextIncrementEffectView.swift",
    "content": "//\n//  TextIncrementEffectView.swift\n//  MyYearWithGit\n//\n//  Created by Cyandev on 2021/11/28.\n//\n\nimport SwiftUI\n\nstruct TextIncrementEffectView: View {\n    let number: Int\n    let timer = Timer\n        .publish(every: 0.02, on: .main, in: .common)\n        .autoconnect()\n\n    @State var currentNumber = 0\n\n    var body: some View {\n        Group {\n            Text(\"\\(currentNumber)\")\n                .font(.system(size: 32, design: .rounded).bold())\n        }\n        .background(\n            number == currentNumber ? nil : Color.clear\n                .onReceive(timer) { _ in\n                    let newNumber = Int(Double(currentNumber) + Double(number) / 80.0)\n                    currentNumber = min(number, newNumber)\n                }\n        )\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Helper/TextTypeEffectView.swift",
    "content": "//\n//  TextTypeEffectView.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/26.\n//\n\nimport SwiftUI\n\nstruct TextTypeEffectView: View {\n    let size: CGFloat\n    let timer = Timer\n        .publish(every: 0.1, on: .main, in: .common)\n        .autoconnect()\n\n    let textList: [String]\n\n    @State var displayText: String = \"\"\n\n    @State var currentText: String = \"\"\n    @State var currentIndex = -1\n    @State var currentArray: String = \"\"\n    @State var switchAwaitControl: Int = 0\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            Divider().hidden()\n            if #available(macOS 13.0, *) {\n                Text(displayText)\n                    .font(.system(size: preferredTitleSize, weight: .semibold, design: .rounded))\n                    .contentTransition(.numericText())\n                    .animation(.interactiveSpring, value: displayText)\n            } else {\n                Text(displayText)\n                    .font(.system(size: preferredTitleSize, weight: .semibold, design: .rounded))\n                    .animation(.interactiveSpring, value: displayText)\n            }\n            Divider().hidden()\n        }\n        .onReceive(timer) { _ in\n            if switchAwaitControl > 0 {\n                switchAwaitControl -= 1\n                return\n            }\n            // if nothing to append to currentText\n            if currentArray.count < 1 {\n                // check if we are able to move the index to next\n                currentIndex += 1\n                // overflow! reset!\n                if currentIndex >= textList.count {\n                    currentIndex = 0\n                }\n                // list is empty?\n                if currentIndex >= textList.count || currentIndex < 0 {\n                    // nothing available\n                    return\n                }\n                // move the payload to updated texts\n                currentText = \"\"\n                currentArray = textList[currentIndex]\n            }\n            if currentArray.count < 1 {\n                // what? empty string here\n                return\n            }\n            // now append it!\n            currentText.append(currentArray.removeFirst())\n            if currentArray.count == 0 {\n                // wait around 1 second before switch to next text\n                switchAwaitControl += 10\n            }\n        }\n        .onChange(of: currentText) { newValue in\n            displayText = newValue + \"_\"\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/Helper/sha256sum.swift",
    "content": "//\n//  sha256sum.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/30.\n//\n\nimport CommonCrypto\nimport Foundation\n\nextension String {\n    var sha256: String {\n        let data = data(using: .utf8) ?? Data()\n        var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))\n        data.withUnsafeBytes {\n            _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &digest)\n        }\n        let hexBytes = digest.map { String(format: \"%02hhx\", $0) }\n        let sha256Hex = hexBytes.joined()\n        return sha256Hex\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit/View/NavigatorView.swift",
    "content": "//\n//  NavigatorView.swift\n//  MyYearWithGit\n//\n//  Created by Lakr Aream on 2021/11/27.\n//\n\nimport SwiftUI\n\nstruct NavigatorView: View {\n    @State var progress: Bool = false\n\n    @State var sourcePackage: SourcePackage? = nil\n    @State var resultPackage: ResultPackage? = nil\n\n    var body: some View {\n        Group {\n            if let resultPackage {\n                ResultView(resultPackage: resultPackage)\n                    .environmentObject(PageData())\n                    .onAppear {\n                        protectWindowFromClose()\n                    }\n            } else if let sourcePackage {\n                AnalysisView(sourcePackage: sourcePackage)\n            } else {\n                MainView()\n            }\n        }\n        .onDrop(of: [\"public.url\", \"public.file-url\"], isTargeted: nil, perform: { items -> Bool in\n            guard sourcePackage == nil else {\n                let alert = NSAlert()\n                alert.alertStyle = .critical\n                alert.messageText = NSLocalizedString(\"分析过程中不可载入数据，请稍后再试。\", comment: \"\")\n                alert.addButton(withTitle: NSLocalizedString(\"确定\", comment: \"\"))\n                alert.beginSheetModal(for: NSApp.keyWindow ?? NSWindow()) { _ in\n                }\n                return false\n            }\n            guard let item = items.first,\n                  let identifier = item.registeredTypeIdentifiers.first,\n                  identifier == \"public.url\" || identifier == \"public.file-url\"\n            else {\n                return false\n            }\n            item.loadItem(forTypeIdentifier: identifier, options: nil) { provider, _ in\n                if let provider = provider as? Data,\n                   let location = URL(dataRepresentation: provider, relativeTo: nil, isAbsolute: true),\n                   let data = try? Data(contentsOf: location),\n                   let object = try? JSONDecoder().decode(ResultPackage.DataSource.self, from: data)\n                {\n                    progress = true\n                    DispatchQueue.global().async {\n                        let package = ResultPackage(data: object)\n                        package.update()\n                        DispatchQueue.main.async {\n                            progress = false\n                            resultPackage = package\n                        }\n                    }\n                }\n            }\n            return true\n        })\n        .sheet(isPresented: $progress, onDismiss: nil, content: {\n            SheetTemplate.makeProgress(text: NSLocalizedString(\"正在解析数据...\", comment: \"\"))\n        })\n        .onReceive(NotificationCenter.default.publisher(for: .postAnalysis, object: nil)) { notification in\n            guard let sourcePackage = notification.object as? SourcePackage else {\n                return\n            }\n            self.sourcePackage = sourcePackage\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .analysisComlete, object: nil)) { notification in\n            guard let resultPackage = notification.object as? ResultPackage else {\n                return\n            }\n            self.resultPackage = resultPackage\n            sourcePackage = nil\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .analysisErase, object: nil)) { _ in\n            resultPackage = nil\n            sourcePackage = nil\n            unprotectWindowFromClose()\n        }\n    }\n}\n"
  },
  {
    "path": "MyYearWithGit.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t501B048D2754A1BB0077C8FB /* RS1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B048C2754A1BB0077C8FB /* RS1.swift */; };\n\t\t501B048F2754AE450077C8FB /* RS2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B048E2754AE450077C8FB /* RS2.swift */; };\n\t\t501B04962754D2F50077C8FB /* RS3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B04952754D2F50077C8FB /* RS3.swift */; };\n\t\t501B04982754DF100077C8FB /* RS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B04972754DF100077C8FB /* RS4.swift */; };\n\t\t501B049A2754DFAC0077C8FB /* DicCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B04992754DFAC0077C8FB /* DicCounter.swift */; };\n\t\t501B049C2754E8180077C8FB /* RS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B049B2754E8180077C8FB /* RS5.swift */; };\n\t\t501B049E2754F9680077C8FB /* RS6.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B049D2754F9680077C8FB /* RS6.swift */; };\n\t\t501B04A0275503040077C8FB /* RS7.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501B049F275503040077C8FB /* RS7.swift */; };\n\t\t5036504D27546B56007B4B72 /* DictionaryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5036504C27546B56007B4B72 /* DictionaryBuilder.swift */; };\n\t\t50572E8D294C1DD7004BC0C6 /* Hooker.m in Sources */ = {isa = PBXBuildFile; fileRef = 50572E8C294C1DD7004BC0C6 /* Hooker.m */; };\n\t\t506CB6E527537ABE00CF2A6E /* GitRepoResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506CB6E427537ABE00CF2A6E /* GitRepoResult.swift */; };\n\t\t506CB6E827537F9F00CF2A6E /* RSProtocol.swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506CB6E727537F9F00CF2A6E /* RSProtocol.swift.swift */; };\n\t\t5070B09127572A1400D23A75 /* CommitFilterSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5070B09027572A1400D23A75 /* CommitFilterSheet.swift */; };\n\t\t5070B09327572C4E00D23A75 /* CommitFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5070B09227572C4E00D23A75 /* CommitFilter.swift */; };\n\t\t50720BEA2B2A2120004A2B8D /* ColorfulX in Frameworks */ = {isa = PBXBuildFile; productRef = 50720BE92B2A2120004A2B8D /* ColorfulX */; };\n\t\t50742544275BAE3B006B5354 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50742543275BAE3B006B5354 /* User.swift */; };\n\t\t50742548275BB0E4006B5354 /* UserDefault.swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50742547275BB0E4006B5354 /* UserDefault.swift.swift */; };\n\t\t507528EA2753510B001F373E /* GitLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507528E92753510B001F373E /* GitLog.swift */; };\n\t\t507528EE27535BE2001F373E /* GitDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507528ED27535BE2001F373E /* GitDiff.swift */; };\n\t\t507528F027535C23001F373E /* SourceLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507528EF27535C23001F373E /* SourceLanguage.swift */; };\n\t\t50802D642D1133EC00694021 /* ColorfulX in Frameworks */ = {isa = PBXBuildFile; productRef = 50802D632D1133EC00694021 /* ColorfulX */; };\n\t\t50802D672D1133F600694021 /* AuxiliaryExecute in Frameworks */ = {isa = PBXBuildFile; productRef = 50802D662D1133F600694021 /* AuxiliaryExecute */; };\n\t\t50802D6A2D11343E00694021 /* OctoKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50802D692D11343E00694021 /* OctoKit */; };\n\t\t50802D6C2D11347E00694021 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50802D6B2D11347E00694021 /* main.swift */; };\n\t\t50802D6F2D1135BF00694021 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 50802D6E2D1135BF00694021 /* MarkdownUI */; };\n\t\t50802D712D11379100694021 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50802D702D11379100694021 /* Localizable.xcstrings */; };\n\t\t50802D732D11379600694021 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50802D722D11379600694021 /* InfoPlist.xcstrings */; };\n\t\t50802D752D1147B800694021 /* Avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 50802D742D1147B800694021 /* Avatar.png */; };\n\t\t5083A0BA275279A900BDF048 /* ConfigEmailSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5083A0B9275279A900BDF048 /* ConfigEmailSheet.swift */; };\n\t\t509071382752123A002D2C14 /* OctoKit in Frameworks */ = {isa = PBXBuildFile; productRef = 509071372752123A002D2C14 /* OctoKit */; };\n\t\t5090713B275223DF002D2C14 /* GitLabApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5090713A275223DF002D2C14 /* GitLabApi.swift */; };\n\t\t5090713D27523242002D2C14 /* SourcePackage.swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5090713C27523242002D2C14 /* SourcePackage.swift.swift */; };\n\t\t509071402752337C002D2C14 /* NavigatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5090713F2752337C002D2C14 /* NavigatorView.swift */; };\n\t\t50907142275233A7002D2C14 /* ResultPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50907141275233A7002D2C14 /* ResultPackage.swift */; };\n\t\t50907144275233FE002D2C14 /* ResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50907143275233FE002D2C14 /* ResultView.swift */; };\n\t\t509071462752340B002D2C14 /* AnalysisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509071452752340B002D2C14 /* AnalysisView.swift */; };\n\t\t5090714827523992002D2C14 /* AuxiliaryExecute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5090714727523992002D2C14 /* AuxiliaryExecute.swift */; };\n\t\t50907160275252A0002D2C14 /* RepoAnalyser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5090715F275252A0002D2C14 /* RepoAnalyser.swift */; };\n\t\t50A8C3F52751143A00C545BC /* TextTypeEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A8C3F42751143A00C545BC /* TextTypeEffectView.swift */; };\n\t\t50A8C3F92751222900C545BC /* MainSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A8C3F82751222900C545BC /* MainSheet.swift */; };\n\t\t50A8C3FB2751240C00C545BC /* SheetTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A8C3FA2751240C00C545BC /* SheetTemplate.swift */; };\n\t\t50B18D972753B29C00293163 /* CodeTiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B18D962753B29C00293163 /* CodeTiles.swift */; };\n\t\t50B18D992753B3AD00293163 /* RS0.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B18D982753B3AD00293163 /* RS0.swift */; };\n\t\t50BAB6D7275A446E00409089 /* BitbucketApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BAB6D6275A446E00409089 /* BitbucketApi.swift */; };\n\t\t50BAB6D9275A44C700409089 /* BitbucketSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BAB6D8275A44C700409089 /* BitbucketSheet.swift */; };\n\t\t50DC0FCF2755DA320014E94E /* RS8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DC0FCE2755DA320014E94E /* RS8.swift */; };\n\t\t50DF1108275674F2003FF552 /* sha256sum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DF1107275674F2003FF552 /* sha256sum.swift */; };\n\t\t50E3A98F27512D88003CBE07 /* Exts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E3A98E27512D88003CBE07 /* Exts.swift */; };\n\t\t50E3A99227513266003CBE07 /* PickSourceSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E3A99127513266003CBE07 /* PickSourceSheet.swift */; };\n\t\t50E3A99427513279003CBE07 /* LocalRepoSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E3A99327513279003CBE07 /* LocalRepoSheet.swift */; };\n\t\t50E3A996275132C7003CBE07 /* GitHubRepoSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E3A995275132C7003CBE07 /* GitHubRepoSheet.swift */; };\n\t\t50E3A998275132CF003CBE07 /* GitLabRepoSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E3A997275132CF003CBE07 /* GitLabRepoSheet.swift */; };\n\t\t50E3A99A2751331B003CBE07 /* SourceRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E3A9992751331B003CBE07 /* SourceRegister.swift */; };\n\t\t50E5007F275C7C33003E6C9A /* GitHubApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E5007E275C7C33003E6C9A /* GitHubApi.swift */; };\n\t\t50E50081275C7C84003E6C9A /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E50080275C7C84003E6C9A /* ApiProtocol.swift */; };\n\t\t50EA933E278C09570059EF8B /* AuxiliaryExecute in Frameworks */ = {isa = PBXBuildFile; productRef = 50EA933D278C09570059EF8B /* AuxiliaryExecute */; };\n\t\t50EB62BB275508E700F6E6FF /* ThanksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EB62BA275508E700F6E6FF /* ThanksView.swift */; };\n\t\t50FD1005275101D80090EAFB /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FD1004275101D80090EAFB /* App.swift */; };\n\t\t50FD1007275101D80090EAFB /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FD1006275101D80090EAFB /* MainView.swift */; };\n\t\t50FD1009275101D90090EAFB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50FD1008275101D90090EAFB /* Assets.xcassets */; };\n\t\tA988C1792F0A37CC00F418E0 /* RS9.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988C1782F0A37CC00F418E0 /* RS9.swift */; };\n\t\tFAA2B69127529057008A21E8 /* TextIncrementEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA2B69027529057008A21E8 /* TextIncrementEffectView.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t501B048C2754A1BB0077C8FB /* RS1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS1.swift; sourceTree = \"<group>\"; };\n\t\t501B048E2754AE450077C8FB /* RS2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS2.swift; sourceTree = \"<group>\"; };\n\t\t501B04952754D2F50077C8FB /* RS3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS3.swift; sourceTree = \"<group>\"; };\n\t\t501B04972754DF100077C8FB /* RS4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS4.swift; sourceTree = \"<group>\"; };\n\t\t501B04992754DFAC0077C8FB /* DicCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DicCounter.swift; sourceTree = \"<group>\"; };\n\t\t501B049B2754E8180077C8FB /* RS5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS5.swift; sourceTree = \"<group>\"; };\n\t\t501B049D2754F9680077C8FB /* RS6.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS6.swift; sourceTree = \"<group>\"; };\n\t\t501B049F275503040077C8FB /* RS7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS7.swift; sourceTree = \"<group>\"; };\n\t\t5036504C27546B56007B4B72 /* DictionaryBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryBuilder.swift; sourceTree = \"<group>\"; };\n\t\t50572E8C294C1DD7004BC0C6 /* Hooker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Hooker.m; sourceTree = \"<group>\"; };\n\t\t506CB6E427537ABE00CF2A6E /* GitRepoResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitRepoResult.swift; sourceTree = \"<group>\"; };\n\t\t506CB6E727537F9F00CF2A6E /* RSProtocol.swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSProtocol.swift.swift; sourceTree = \"<group>\"; };\n\t\t5070B09027572A1400D23A75 /* CommitFilterSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommitFilterSheet.swift; sourceTree = \"<group>\"; };\n\t\t5070B09227572C4E00D23A75 /* CommitFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommitFilter.swift; sourceTree = \"<group>\"; };\n\t\t50742543275BAE3B006B5354 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = \"<group>\"; };\n\t\t50742547275BB0E4006B5354 /* UserDefault.swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift.swift; sourceTree = \"<group>\"; };\n\t\t507528E92753510B001F373E /* GitLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitLog.swift; sourceTree = \"<group>\"; };\n\t\t507528ED27535BE2001F373E /* GitDiff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitDiff.swift; sourceTree = \"<group>\"; };\n\t\t507528EF27535C23001F373E /* SourceLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceLanguage.swift; sourceTree = \"<group>\"; };\n\t\t50802D6B2D11347E00694021 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = \"<group>\"; };\n\t\t50802D702D11379100694021 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = \"<group>\"; };\n\t\t50802D722D11379600694021 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = \"<group>\"; };\n\t\t50802D742D1147B800694021 /* Avatar.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Avatar.png; sourceTree = \"<group>\"; };\n\t\t5083A0B9275279A900BDF048 /* ConfigEmailSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigEmailSheet.swift; sourceTree = \"<group>\"; };\n\t\t5090713A275223DF002D2C14 /* GitLabApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitLabApi.swift; sourceTree = \"<group>\"; };\n\t\t5090713C27523242002D2C14 /* SourcePackage.swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcePackage.swift.swift; sourceTree = \"<group>\"; };\n\t\t5090713F2752337C002D2C14 /* NavigatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorView.swift; sourceTree = \"<group>\"; };\n\t\t50907141275233A7002D2C14 /* ResultPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultPackage.swift; sourceTree = \"<group>\"; };\n\t\t50907143275233FE002D2C14 /* ResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultView.swift; sourceTree = \"<group>\"; };\n\t\t509071452752340B002D2C14 /* AnalysisView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalysisView.swift; sourceTree = \"<group>\"; };\n\t\t5090714727523992002D2C14 /* AuxiliaryExecute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuxiliaryExecute.swift; sourceTree = \"<group>\"; };\n\t\t5090715F275252A0002D2C14 /* RepoAnalyser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoAnalyser.swift; sourceTree = \"<group>\"; };\n\t\t50A8C3F42751143A00C545BC /* TextTypeEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextTypeEffectView.swift; sourceTree = \"<group>\"; };\n\t\t50A8C3F82751222900C545BC /* MainSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSheet.swift; sourceTree = \"<group>\"; };\n\t\t50A8C3FA2751240C00C545BC /* SheetTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetTemplate.swift; sourceTree = \"<group>\"; };\n\t\t50A8C400275126C100C545BC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t50B18D962753B29C00293163 /* CodeTiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeTiles.swift; sourceTree = \"<group>\"; };\n\t\t50B18D982753B3AD00293163 /* RS0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS0.swift; sourceTree = \"<group>\"; };\n\t\t50BAB6D6275A446E00409089 /* BitbucketApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitbucketApi.swift; sourceTree = \"<group>\"; };\n\t\t50BAB6D8275A44C700409089 /* BitbucketSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitbucketSheet.swift; sourceTree = \"<group>\"; };\n\t\t50DC0FCE2755DA320014E94E /* RS8.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS8.swift; sourceTree = \"<group>\"; };\n\t\t50DF1107275674F2003FF552 /* sha256sum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = sha256sum.swift; sourceTree = \"<group>\"; };\n\t\t50E3A98E27512D88003CBE07 /* Exts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exts.swift; sourceTree = \"<group>\"; };\n\t\t50E3A99127513266003CBE07 /* PickSourceSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickSourceSheet.swift; sourceTree = \"<group>\"; };\n\t\t50E3A99327513279003CBE07 /* LocalRepoSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalRepoSheet.swift; sourceTree = \"<group>\"; };\n\t\t50E3A995275132C7003CBE07 /* GitHubRepoSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubRepoSheet.swift; sourceTree = \"<group>\"; };\n\t\t50E3A997275132CF003CBE07 /* GitLabRepoSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitLabRepoSheet.swift; sourceTree = \"<group>\"; };\n\t\t50E3A9992751331B003CBE07 /* SourceRegister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceRegister.swift; sourceTree = \"<group>\"; };\n\t\t50E5007E275C7C33003E6C9A /* GitHubApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubApi.swift; sourceTree = \"<group>\"; };\n\t\t50E50080275C7C84003E6C9A /* ApiProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiProtocol.swift; sourceTree = \"<group>\"; };\n\t\t50EB62BA275508E700F6E6FF /* ThanksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThanksView.swift; sourceTree = \"<group>\"; };\n\t\t50FD1001275101D80090EAFB /* MyYearWithGit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = MyYearWithGit.app; path = /Users/hele/CursorProjects/myyearwithgit/build/Debug/MyYearWithGit.app; sourceTree = \"<absolute>\"; };\n\t\t50FD1004275101D80090EAFB /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = \"<group>\"; };\n\t\t50FD1006275101D80090EAFB /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = \"<group>\"; };\n\t\t50FD1008275101D90090EAFB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t50FD100D275101D90090EAFB /* Entitlements.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.entitlements; sourceTree = \"<group>\"; };\n\t\tA988C1782F0A37CC00F418E0 /* RS9.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RS9.swift; sourceTree = \"<group>\"; };\n\t\tFAA2B69027529057008A21E8 /* TextIncrementEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextIncrementEffectView.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t50FD0FFE275101D80090EAFB /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t50802D6A2D11343E00694021 /* OctoKit in Frameworks */,\n\t\t\t\t50802D6F2D1135BF00694021 /* MarkdownUI in Frameworks */,\n\t\t\t\t509071382752123A002D2C14 /* OctoKit in Frameworks */,\n\t\t\t\t50802D672D1133F600694021 /* AuxiliaryExecute in Frameworks */,\n\t\t\t\t50EA933E278C09570059EF8B /* AuxiliaryExecute in Frameworks */,\n\t\t\t\t50720BEA2B2A2120004A2B8D /* ColorfulX in Frameworks */,\n\t\t\t\t50802D642D1133EC00694021 /* ColorfulX in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t50572E8B294C1DD0004BC0C6 /* App */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50802D6B2D11347E00694021 /* main.swift */,\n\t\t\t\t50FD1004275101D80090EAFB /* App.swift */,\n\t\t\t\t50572E8C294C1DD7004BC0C6 /* Hooker.m */,\n\t\t\t\t50FD1008275101D90090EAFB /* Assets.xcassets */,\n\t\t\t\t50802D742D1147B800694021 /* Avatar.png */,\n\t\t\t\t50802D702D11379100694021 /* Localizable.xcstrings */,\n\t\t\t\t50802D722D11379600694021 /* InfoPlist.xcstrings */,\n\t\t\t);\n\t\t\tpath = App;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t506CB6E627537CE000CF2A6E /* ResultPackage */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA988C1782F0A37CC00F418E0 /* RS9.swift */,\n\t\t\t\t506CB6E727537F9F00CF2A6E /* RSProtocol.swift.swift */,\n\t\t\t\t50907141275233A7002D2C14 /* ResultPackage.swift */,\n\t\t\t\t501B04992754DFAC0077C8FB /* DicCounter.swift */,\n\t\t\t\t50B18D982753B3AD00293163 /* RS0.swift */,\n\t\t\t\t501B048C2754A1BB0077C8FB /* RS1.swift */,\n\t\t\t\t501B048E2754AE450077C8FB /* RS2.swift */,\n\t\t\t\t501B04952754D2F50077C8FB /* RS3.swift */,\n\t\t\t\t501B04972754DF100077C8FB /* RS4.swift */,\n\t\t\t\t501B049B2754E8180077C8FB /* RS5.swift */,\n\t\t\t\t501B049D2754F9680077C8FB /* RS6.swift */,\n\t\t\t\t501B049F275503040077C8FB /* RS7.swift */,\n\t\t\t\t50DC0FCE2755DA320014E94E /* RS8.swift */,\n\t\t\t);\n\t\t\tpath = ResultPackage;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t506CB6E92753813B00CF2A6E /* Generic */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50FD1006275101D80090EAFB /* MainView.swift */,\n\t\t\t\t509071452752340B002D2C14 /* AnalysisView.swift */,\n\t\t\t\t50907143275233FE002D2C14 /* ResultView.swift */,\n\t\t\t\t50EB62BA275508E700F6E6FF /* ThanksView.swift */,\n\t\t\t);\n\t\t\tpath = Generic;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t507528EB27535BC7001F373E /* Analyser */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t5090715F275252A0002D2C14 /* RepoAnalyser.swift */,\n\t\t\t\t5070B09227572C4E00D23A75 /* CommitFilter.swift */,\n\t\t\t\t5036504C27546B56007B4B72 /* DictionaryBuilder.swift */,\n\t\t\t\t506CB6E427537ABE00CF2A6E /* GitRepoResult.swift */,\n\t\t\t\t507528E92753510B001F373E /* GitLog.swift */,\n\t\t\t\t507528ED27535BE2001F373E /* GitDiff.swift */,\n\t\t\t);\n\t\t\tpath = Analyser;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t507528EC27535BD1001F373E /* Generic */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50E3A98E27512D88003CBE07 /* Exts.swift */,\n\t\t\t\t50742543275BAE3B006B5354 /* User.swift */,\n\t\t\t\t50E3A9992751331B003CBE07 /* SourceRegister.swift */,\n\t\t\t\t507528EF27535C23001F373E /* SourceLanguage.swift */,\n\t\t\t\t5090713C27523242002D2C14 /* SourcePackage.swift.swift */,\n\t\t\t\t5090714727523992002D2C14 /* AuxiliaryExecute.swift */,\n\t\t\t\t50742547275BB0E4006B5354 /* UserDefault.swift.swift */,\n\t\t\t);\n\t\t\tpath = Generic;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50907139275223D6002D2C14 /* Api */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50E50080275C7C84003E6C9A /* ApiProtocol.swift */,\n\t\t\t\t50E5007E275C7C33003E6C9A /* GitHubApi.swift */,\n\t\t\t\t5090713A275223DF002D2C14 /* GitLabApi.swift */,\n\t\t\t\t50BAB6D6275A446E00409089 /* BitbucketApi.swift */,\n\t\t\t);\n\t\t\tpath = Api;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t5090713E27523367002D2C14 /* Helper */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50B18D962753B29C00293163 /* CodeTiles.swift */,\n\t\t\t\t50A8C3FA2751240C00C545BC /* SheetTemplate.swift */,\n\t\t\t\t50A8C3F42751143A00C545BC /* TextTypeEffectView.swift */,\n\t\t\t\tFAA2B69027529057008A21E8 /* TextIncrementEffectView.swift */,\n\t\t\t\t50DF1107275674F2003FF552 /* sha256sum.swift */,\n\t\t\t);\n\t\t\tpath = Helper;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t5096DBC62751031A007AA4C7 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50E3A98C27512D70003CBE07 /* View */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t5090713F2752337C002D2C14 /* NavigatorView.swift */,\n\t\t\t\t506CB6E92753813B00CF2A6E /* Generic */,\n\t\t\t\t50E3A99027513254003CBE07 /* Configuration */,\n\t\t\t\t5090713E27523367002D2C14 /* Helper */,\n\t\t\t);\n\t\t\tpath = View;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50E3A98D27512D74003CBE07 /* Data */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t507528EC27535BD1001F373E /* Generic */,\n\t\t\t\t506CB6E627537CE000CF2A6E /* ResultPackage */,\n\t\t\t\t507528EB27535BC7001F373E /* Analyser */,\n\t\t\t\t50907139275223D6002D2C14 /* Api */,\n\t\t\t);\n\t\t\tpath = Data;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50E3A99027513254003CBE07 /* Configuration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50A8C3F82751222900C545BC /* MainSheet.swift */,\n\t\t\t\t50E3A99127513266003CBE07 /* PickSourceSheet.swift */,\n\t\t\t\t5083A0B9275279A900BDF048 /* ConfigEmailSheet.swift */,\n\t\t\t\t5070B09027572A1400D23A75 /* CommitFilterSheet.swift */,\n\t\t\t\t50E3A99327513279003CBE07 /* LocalRepoSheet.swift */,\n\t\t\t\t50E3A995275132C7003CBE07 /* GitHubRepoSheet.swift */,\n\t\t\t\t50E3A997275132CF003CBE07 /* GitLabRepoSheet.swift */,\n\t\t\t\t50BAB6D8275A44C700409089 /* BitbucketSheet.swift */,\n\t\t\t);\n\t\t\tpath = Configuration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50FD0FF8275101D80090EAFB = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50FD1003275101D80090EAFB /* MyYearWithGit */,\n\t\t\t\t50FD1002275101D80090EAFB /* Products */,\n\t\t\t\t5096DBC62751031A007AA4C7 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50FD1002275101D80090EAFB /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t50FD1003275101D80090EAFB /* MyYearWithGit */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t50A8C400275126C100C545BC /* Info.plist */,\n\t\t\t\t50FD100D275101D90090EAFB /* Entitlements.entitlements */,\n\t\t\t\t50572E8B294C1DD0004BC0C6 /* App */,\n\t\t\t\t50E3A98D27512D74003CBE07 /* Data */,\n\t\t\t\t50E3A98C27512D70003CBE07 /* View */,\n\t\t\t);\n\t\t\tpath = MyYearWithGit;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t50FD1000275101D80090EAFB /* MyYearWithGit */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 50FD1010275101D90090EAFB /* Build configuration list for PBXNativeTarget \"MyYearWithGit\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t50FD0FFD275101D80090EAFB /* Sources */,\n\t\t\t\t50FD0FFE275101D80090EAFB /* Frameworks */,\n\t\t\t\t50FD0FFF275101D80090EAFB /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = MyYearWithGit;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t509071372752123A002D2C14 /* OctoKit */,\n\t\t\t\t50EA933D278C09570059EF8B /* AuxiliaryExecute */,\n\t\t\t\t50720BE92B2A2120004A2B8D /* ColorfulX */,\n\t\t\t\t50802D632D1133EC00694021 /* ColorfulX */,\n\t\t\t\t50802D662D1133F600694021 /* AuxiliaryExecute */,\n\t\t\t\t50802D692D11343E00694021 /* OctoKit */,\n\t\t\t\t50802D6E2D1135BF00694021 /* MarkdownUI */,\n\t\t\t);\n\t\t\tproductName = MyYearWithGit;\n\t\t\tproductReference = 50FD1001275101D80090EAFB /* MyYearWithGit.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t50FD0FF9275101D80090EAFB /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1310;\n\t\t\t\tLastUpgradeCheck = 2610;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t50FD1000275101D80090EAFB = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 50FD0FFC275101D80090EAFB /* Build configuration list for PBXProject \"MyYearWithGit\" */;\n\t\t\tcompatibilityVersion = \"Xcode 13.0\";\n\t\t\tdevelopmentRegion = \"zh-Hans\";\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t\t\"zh-Hans\",\n\t\t\t);\n\t\t\tmainGroup = 50FD0FF8275101D80090EAFB;\n\t\t\tpackageReferences = (\n\t\t\t\t50802D622D1133EC00694021 /* XCRemoteSwiftPackageReference \"ColorfulX\" */,\n\t\t\t\t50802D652D1133F600694021 /* XCRemoteSwiftPackageReference \"AuxiliaryExecute\" */,\n\t\t\t\t50802D682D11343E00694021 /* XCRemoteSwiftPackageReference \"octokit\" */,\n\t\t\t\t50802D6D2D1135BF00694021 /* XCRemoteSwiftPackageReference \"swift-markdown-ui\" */,\n\t\t\t);\n\t\t\tproductRefGroup = 50FD1002275101D80090EAFB /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t50FD1000275101D80090EAFB /* MyYearWithGit */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t50FD0FFF275101D80090EAFB /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t50802D752D1147B800694021 /* Avatar.png in Resources */,\n\t\t\t\t50802D732D11379600694021 /* InfoPlist.xcstrings in Resources */,\n\t\t\t\t50802D712D11379100694021 /* Localizable.xcstrings in Resources */,\n\t\t\t\t50FD1009275101D90090EAFB /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t50FD0FFD275101D80090EAFB /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t506CB6E827537F9F00CF2A6E /* RSProtocol.swift.swift in Sources */,\n\t\t\t\t5083A0BA275279A900BDF048 /* ConfigEmailSheet.swift in Sources */,\n\t\t\t\t50A8C3FB2751240C00C545BC /* SheetTemplate.swift in Sources */,\n\t\t\t\t50907142275233A7002D2C14 /* ResultPackage.swift in Sources */,\n\t\t\t\t5090713D27523242002D2C14 /* SourcePackage.swift.swift in Sources */,\n\t\t\t\t50E3A99427513279003CBE07 /* LocalRepoSheet.swift in Sources */,\n\t\t\t\t5090714827523992002D2C14 /* AuxiliaryExecute.swift in Sources */,\n\t\t\t\t507528F027535C23001F373E /* SourceLanguage.swift in Sources */,\n\t\t\t\t50FD1007275101D80090EAFB /* MainView.swift in Sources */,\n\t\t\t\t50B18D992753B3AD00293163 /* RS0.swift in Sources */,\n\t\t\t\t50907160275252A0002D2C14 /* RepoAnalyser.swift in Sources */,\n\t\t\t\t50907144275233FE002D2C14 /* ResultView.swift in Sources */,\n\t\t\t\t507528EA2753510B001F373E /* GitLog.swift in Sources */,\n\t\t\t\t50FD1005275101D80090EAFB /* App.swift in Sources */,\n\t\t\t\t50E5007F275C7C33003E6C9A /* GitHubApi.swift in Sources */,\n\t\t\t\t50DC0FCF2755DA320014E94E /* RS8.swift in Sources */,\n\t\t\t\t50802D6C2D11347E00694021 /* main.swift in Sources */,\n\t\t\t\t501B048D2754A1BB0077C8FB /* RS1.swift in Sources */,\n\t\t\t\t50E3A99A2751331B003CBE07 /* SourceRegister.swift in Sources */,\n\t\t\t\t50BAB6D9275A44C700409089 /* BitbucketSheet.swift in Sources */,\n\t\t\t\t50E3A998275132CF003CBE07 /* GitLabRepoSheet.swift in Sources */,\n\t\t\t\t50572E8D294C1DD7004BC0C6 /* Hooker.m in Sources */,\n\t\t\t\t5070B09127572A1400D23A75 /* CommitFilterSheet.swift in Sources */,\n\t\t\t\t501B04982754DF100077C8FB /* RS4.swift in Sources */,\n\t\t\t\t501B048F2754AE450077C8FB /* RS2.swift in Sources */,\n\t\t\t\t506CB6E527537ABE00CF2A6E /* GitRepoResult.swift in Sources */,\n\t\t\t\t50B18D972753B29C00293163 /* CodeTiles.swift in Sources */,\n\t\t\t\t50742544275BAE3B006B5354 /* User.swift in Sources */,\n\t\t\t\t507528EE27535BE2001F373E /* GitDiff.swift in Sources */,\n\t\t\t\t50BAB6D7275A446E00409089 /* BitbucketApi.swift in Sources */,\n\t\t\t\t5036504D27546B56007B4B72 /* DictionaryBuilder.swift in Sources */,\n\t\t\t\t50A8C3F52751143A00C545BC /* TextTypeEffectView.swift in Sources */,\n\t\t\t\t5090713B275223DF002D2C14 /* GitLabApi.swift in Sources */,\n\t\t\t\t50E3A99227513266003CBE07 /* PickSourceSheet.swift in Sources */,\n\t\t\t\t50742548275BB0E4006B5354 /* UserDefault.swift.swift in Sources */,\n\t\t\t\t50E3A98F27512D88003CBE07 /* Exts.swift in Sources */,\n\t\t\t\t509071402752337C002D2C14 /* NavigatorView.swift in Sources */,\n\t\t\t\t5070B09327572C4E00D23A75 /* CommitFilter.swift in Sources */,\n\t\t\t\t501B04A0275503040077C8FB /* RS7.swift in Sources */,\n\t\t\t\t509071462752340B002D2C14 /* AnalysisView.swift in Sources */,\n\t\t\t\t501B04962754D2F50077C8FB /* RS3.swift in Sources */,\n\t\t\t\t50DF1108275674F2003FF552 /* sha256sum.swift in Sources */,\n\t\t\t\t50E50081275C7C84003E6C9A /* ApiProtocol.swift in Sources */,\n\t\t\t\t50EB62BB275508E700F6E6FF /* ThanksView.swift in Sources */,\n\t\t\t\t50E3A996275132C7003CBE07 /* GitHubRepoSheet.swift in Sources */,\n\t\t\t\tFAA2B69127529057008A21E8 /* TextIncrementEffectView.swift in Sources */,\n\t\t\t\t50A8C3F92751222900C545BC /* MainSheet.swift in Sources */,\n\t\t\t\tA988C1792F0A37CC00F418E0 /* RS9.swift in Sources */,\n\t\t\t\t501B049A2754DFAC0077C8FB /* DicCounter.swift in Sources */,\n\t\t\t\t501B049C2754E8180077C8FB /* RS5.swift in Sources */,\n\t\t\t\t501B049E2754F9680077C8FB /* RS6.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t50FD100E275101D90090EAFB /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = 964G86XT2P;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 12.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t50FD100F275101D90090EAFB /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tDEVELOPMENT_TEAM = 964G86XT2P;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 12.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSTRING_CATALOG_GENERATE_SYMBOLS = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t50FD1011275101D90090EAFB /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = MyYearWithGit/Entitlements.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 2025;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\";\n\t\t\t\tDEVELOPMENT_TEAM = DZUF88A58A;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = MyYearWithGit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"年度代码\";\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"标准件厂长@砍砍\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 12.0;\n\t\t\t\tMARKETING_VERSION = 2.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.MyYearWithGit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t50FD1012275101D90090EAFB /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = MyYearWithGit/Entitlements.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 2025;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\";\n\t\t\t\tDEVELOPMENT_TEAM = DZUF88A58A;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = MyYearWithGit/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"年度代码\";\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.developer-tools\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"标准件厂长@砍砍\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 12.0;\n\t\t\t\tMARKETING_VERSION = 2.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = wiki.qaq.MyYearWithGit;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t50FD0FFC275101D80090EAFB /* Build configuration list for PBXProject \"MyYearWithGit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t50FD100E275101D90090EAFB /* Debug */,\n\t\t\t\t50FD100F275101D90090EAFB /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t50FD1010275101D90090EAFB /* Build configuration list for PBXNativeTarget \"MyYearWithGit\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t50FD1011275101D90090EAFB /* Debug */,\n\t\t\t\t50FD1012275101D90090EAFB /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\t50802D622D1133EC00694021 /* XCRemoteSwiftPackageReference \"ColorfulX\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/Lakr233/ColorfulX\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 6.0.2;\n\t\t\t};\n\t\t};\n\t\t50802D652D1133F600694021 /* XCRemoteSwiftPackageReference \"AuxiliaryExecute\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/Lakr233/AuxiliaryExecute\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 2.1.0;\n\t\t\t};\n\t\t};\n\t\t50802D682D11343E00694021 /* XCRemoteSwiftPackageReference \"octokit\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/nerdishbynature/octokit.swift\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.14.0;\n\t\t\t};\n\t\t};\n\t\t50802D6D2D1135BF00694021 /* XCRemoteSwiftPackageReference \"swift-markdown-ui\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/gonzalezreal/swift-markdown-ui\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 2.4.1;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t50720BE92B2A2120004A2B8D /* ColorfulX */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = ColorfulX;\n\t\t};\n\t\t50802D632D1133EC00694021 /* ColorfulX */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 50802D622D1133EC00694021 /* XCRemoteSwiftPackageReference \"ColorfulX\" */;\n\t\t\tproductName = ColorfulX;\n\t\t};\n\t\t50802D662D1133F600694021 /* AuxiliaryExecute */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 50802D652D1133F600694021 /* XCRemoteSwiftPackageReference \"AuxiliaryExecute\" */;\n\t\t\tproductName = AuxiliaryExecute;\n\t\t};\n\t\t50802D692D11343E00694021 /* OctoKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 50802D682D11343E00694021 /* XCRemoteSwiftPackageReference \"octokit\" */;\n\t\t\tproductName = OctoKit;\n\t\t};\n\t\t50802D6E2D1135BF00694021 /* MarkdownUI */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 50802D6D2D1135BF00694021 /* XCRemoteSwiftPackageReference \"swift-markdown-ui\" */;\n\t\t\tproductName = MarkdownUI;\n\t\t};\n\t\t509071372752123A002D2C14 /* OctoKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = OctoKit;\n\t\t};\n\t\t50EA933D278C09570059EF8B /* AuxiliaryExecute */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = AuxiliaryExecute;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = 50FD0FF9275101D80090EAFB /* Project object */;\n}\n"
  },
  {
    "path": "MyYearWithGit.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:MyYearWithGit.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "MyYearWithGit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "MyYearWithGit.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"df25ac7eb41fe53aced11a9d0d1c121c77d4b5e5a38899baff74360177e957dc\",\n  \"pins\" : [\n    {\n      \"identity\" : \"auxiliaryexecute\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Lakr233/AuxiliaryExecute\",\n      \"state\" : {\n        \"revision\" : \"7fb043c2d18ef947c23537f43be26f92e837d1af\",\n        \"version\" : \"2.1.0\"\n      }\n    },\n    {\n      \"identity\" : \"colorfulx\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Lakr233/ColorfulX\",\n      \"state\" : {\n        \"revision\" : \"2f008fcffc7f23811b2ba70eda77c57223815bc3\",\n        \"version\" : \"6.0.2\"\n      }\n    },\n    {\n      \"identity\" : \"colorvector\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Lakr233/ColorVector.git\",\n      \"state\" : {\n        \"revision\" : \"6da8726bf38d68eb943d0f2139ac2a1fac70e65b\",\n        \"version\" : \"1.0.4\"\n      }\n    },\n    {\n      \"identity\" : \"msdisplaylink\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Lakr233/MSDisplayLink.git\",\n      \"state\" : {\n        \"revision\" : \"ebf5823cb5fc1326639d9a05bc06d16bbe82989f\",\n        \"version\" : \"2.0.8\"\n      }\n    },\n    {\n      \"identity\" : \"networkimage\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/gonzalezreal/NetworkImage\",\n      \"state\" : {\n        \"revision\" : \"2849f5323265386e200484b0d0f896e73c3411b9\",\n        \"version\" : \"6.0.1\"\n      }\n    },\n    {\n      \"identity\" : \"octokit.swift\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/nerdishbynature/octokit.swift\",\n      \"state\" : {\n        \"revision\" : \"cd108b387782d8509e64e298cf4800dc47fc40ae\",\n        \"version\" : \"0.14.0\"\n      }\n    },\n    {\n      \"identity\" : \"requestkit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/nerdishbynature/RequestKit.git\",\n      \"state\" : {\n        \"revision\" : \"e4d905fed938807e36d87f28375f88b7c1c26840\",\n        \"version\" : \"3.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"springinterpolation\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Lakr233/SpringInterpolation.git\",\n      \"state\" : {\n        \"revision\" : \"cdb556516daa9b43c16aae9436dd39e19ff930fd\",\n        \"version\" : \"1.4.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-cmark\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/swiftlang/swift-cmark\",\n      \"state\" : {\n        \"revision\" : \"3ccff77b2dc5b96b77db3da0d68d28068593fa53\",\n        \"version\" : \"0.5.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-markdown-ui\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/gonzalezreal/swift-markdown-ui\",\n      \"state\" : {\n        \"revision\" : \"5f613358148239d0292c0cef674a3c2314737f9e\",\n        \"version\" : \"2.4.1\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n# 我和我的代码，还有这一年。\n\n<p align=\"center\">\n  <a href=\"README.md\">简体中文</a> |\n  <a href=\"./Resources/i18n/en/README.md\">English</a> |\n</p>\n\n</div>\n\n## 预览\n\n按道理这里是要放几张预览图片并配上一句话的说明的。本程序目前支持使用常见 Git 仓库托管提供商以及本地代码仓库进行分析。\n\n![预览](./Resources/Screenshot.png)\n\n## 彩蛋\n\n此程序包含一些彩蛋，如果能在不修改程序源码的情况下触发彩蛋，可以上推发送完整截图并联系 @Lakr233 获取奖品 0 份。彩蛋的格式为 flag{xxxxxx}。\n\n## 一些说明\n\n### 记得分享\n\n不给别人看的年度报告是没有灵魂的。\n\n### 关于没有数据\n\n- 请检查你的提交邮箱，虽然会自动配置但是可能存在遗漏的问题。\n- 请检查你的 git log 格式，请勿手动修改时间格式。\n- 请检查 git 版本，测试的版本为 `git version 2.30.1 (Apple Git-130)`。\n\n### 关于沙盒\n\n请悉知此应用程序并没有开启沙盒安全选项，他有能力访问你系统中的全部资源。考虑到此程序需要处理的数据的特殊性，我们在此说明一些情况。\n\n- 由于此程序使用系统内置的 git 进行相关操作，不能启用沙盒。如果需要启用沙盒，你需要自行内嵌 git 二进制。\n- 此应用仅在本地分析和处理数据，**不会上传任何数据**。\n- 苹果针对敏感的数据进行了加强保护，即使没有启用沙盒，你出存在文档、照片等目录的文件仍然收到额外的保护。访问时会要求你明确授权。\n- 应用程序已经上传 Apple 进行无害化公证，运行前请检查代码签名。\n\n### 关于使用程序的一些注意事项\n\n- 如果统计数据存在问题，请检查你的提交邮箱。\n- 我们**不对数据的完整性、准确性做任何保证**。\n- **使用 token 批量拉取仓库可能会触发你司的安全警报，公司仓库建议使用本地分析。**\n- 自行上传分析结果分享到社交网络前**请通读报告并检查其中内容是否涉密**。\n\n## 免责声明\n\n我们不对使用本程序造成的任何后果承担任何责任。下文中，我们列出了一些可能发生的内容，请悉知。\n\n- 计算机死机，卡顿，重启。\n- 计算机芯片烧毁。\n- 花屏，白屏，黑屏，闪屏 。\n- 你上班迟到。\n- 被妻子女儿痛骂写的什么玩意。\n- 被老板看到你在摸鱼。\n- 被辞退。\n- 泄漏机密信息被安全部门处理。\n- 变得不幸。\n- **变成猫猫。**\n- 地球爆炸。\n- 宇宙重启。\n\n## 使用许可\n\n本程序及其源码和编译产物附属[MIT](LICENSE)许可，其生成展示的内容与相关图标和符号不做许可承诺，请参考他们的原始许可。\n\n**请不要售卖本程序。因为这样做会有人受伤。**\n\n🥺\n\n---\n\nCopyright © 2024 Lakr Aream. All Rights Reserved.\n"
  },
  {
    "path": "Resources/i18n/en/README.md",
    "content": "<div align=\"center\">\n\n# My Code & This Year\n\n<p align=\"center\">\n  <a href=\"../../README.md\">简体中文</a> |\n  <a href=\"README.md\">English</a> |\n</p>\n\n</div>\n\n## Preview\n\nWe analysis git repository from various remote or local sources.\n\n![Preview](../../Screenshot.png)\n\n## Easter Eggs\n\nThis program contains some easter eggs. If you can trigger an easter egg without modifying the source code, take a full screenshot and contact @Lakr233 to claim the, not actually exists, prize. Easter eggs are in the format flag{xxxxxx}.\n\n## Some Notes\n\n### Remember to Share\n\nShare or soulless.\n\n### About Missing Data\n\n- Check your commit email, although it's auto-configured.\n- Verify your git log format. Do not manually modify the time format.\n- Check your git version. The tested version is `git version 2.30.1 (Apple Git-130)`.\n\n### About Sandbox\n\nPlease be aware that this application does not run inside the sandbox. It has the ability to access all resources in your system. Given the special nature of the data this program processes, we want to clarify a few things:\n\n- Because the program uses the system's built-in git for operations, sandbox cannot be enabled. If you want to enable sandbox, you'll need to embed the git binary yourself.\n- This application only analyzes and processes data locally and **will not upload any data**.\n- Apple has enhanced protection for sensitive data. Even without sandbox enabled, files in documents, photos, and other directories still receive additional protection. You'll be explicitly asked for authorization when accessing them.\n- The application has been uploaded to Apple for harmless notarization. Please check the code signature before running.\n\n### Some Usage Precautions\n\n- If the statistical data seems problematic, check your commit email.\n- We **make no guarantees about the completeness or accuracy of the data**.\n- **Using tokens to batch-pull repositories might trigger security alerts from your organization. For company repositories, local analysis is recommended.**\n- Before sharing analysis results on social networks, **please thoroughly read the report and check if any content involves confidential information**.\n\n## Disclaimer\n\nWe assume no responsibility for any consequences arising from the use of this program. Below, we list some potential scenarios for your awareness:\n\n- Computer freezing, lag, or restart\n- Computer chip burnout\n- Screen glitches: color, white, black, or flickering\n- Being late for work\n- Being scolded by your wife or daughter for \"what kind of thing is this\"\n- Getting caught slacking by your boss\n- Getting fired\n- Leaking confidential information and being handled by the security department\n- Becoming unfortunate\n- **Turning you into a cat**\n- Earth explosion\n- Universe reboot\n\n## License\n\nThis program, its source code, and compiled products are under the [MIT](LICENSE) license. The generated content, related icons, and symbols are not licensed, please refer to their original licenses.\n\n**Please do not sell this program. Because doing so would hurt someone.**\n\n🥺\n\n---\n\nCopyright © 2024 Lakr Aream. All Rights Reserved.\n"
  }
]