Swift脚本:列出项目中各编程语言文件占比

前言

最近看到一个有趣的东西,用swift编写脚本,实现诸如自动打包程序呀,自动扫描项目中未使用的文件呀等等。其中就用到了用swift编写CLT(commandlinetool)。

使用Swift编写CLI工具入门教程
Swift参数解析工具

看完后我觉得可以做个练习,写了个列出项目中各编程语言文件占比的脚本程序

创建项目

Swift Package Manager 为我们提供了不亚于cocoapods的包管理能力,这里我们就使用 SPM 来构建和管理我们的项目。

准备一个空的文件夹FileCounter,输入如下命令来创建模版文件:

swift package init --type executable // 创建可执行程序的模版
swift package generate-xcodeproj // 生成*.xcodeproj工程
复制代码

–type executable 表示我们创建的是一个可执行程序,默认不传是Dynamic Library类型。这个可以在Target->Build Setting->Mach-O Type 下查看。

此时,大家的文件结构应该如下所示:

.
├── FileCounter.xcodeproj
│   ├── FileCounterTests_Info.plist
│   ├── project.pbxproj
│   ├── project.xcworkspace
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata
│   │       └── WorkspaceSettings.xcsettings
│   └── xcshareddata
│       └── xcschemes
│           ├── FileCounter-Package.xcscheme
│           └── FileCounter.xcscheme
├── Package.swift
├── README.md
├── Sources
│   └── FileCounter
│       └── main.swift
└── Tests
    └── FileCounterTests
        └── FileCounterTests.swift

9 directories, 10 files
复制代码

其中main.swift就是我们的程序入口,也就是我们要编写代码的地方。打开项目FileCounter.xcodeproj,添加一个依赖库,修改Package.swift文件如下:

let package = Package(
    name: "FileCounter",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-argument-parser", from: "0.4.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "FileCounter",
            dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser"),]),
    
    ]
)
复制代码

修改依赖配之后,我们需要下载并更新我们的项目:

swift package update
swift package generate-xcodeproj
复制代码

至此,项目的环境就算搭好了。

思路

这个程序的逻辑很简单,通过遍历项目的所有文件,读取文件的后缀判断这个文件属于哪个编程语言的,从而实现语言占比。例如 Objective-C 的文件后缀有 .h,.m,.mmSwift 的文件后缀有 .swift。其他没有后缀的都统一归为其他即可。

实现

/// 编程语言
struct CodeLanguage {
    var itemType: ItemType = .other
    var name: String {
        switch itemType {
        case .oc:
            return "Objective-C"
        case .swift:
            return "Swift"
        case .c:
            return "C/C++"
        case .ruby:
            return "Ruby"
        case .shell:
            return "Shell"
        case .java:
            return "Java"
        default:
            return "Other"
        }
    }
    var count: Int = 0

    static func +=( lhs: inout CodeLanguage, rhs: Int) {
        lhs.count = lhs.count + rhs
    }
}

/// 文件对象
struct FileItem {
    var name: String
    var absolutePath: String
    var itemType: ItemType = .other
}

/// 列出文件夹下所有的文件对象:FileItem
func list(path: String) throws -> [FileItem] {
    let l = try FileManager.default.subpathsOfDirectory(atPath: path)
    var items = [FileItem]()
    for p in l {
        if p.isDirectory() {
            continue
        }
        
        let url = URL(string: path)?.appendingPathComponent(p)
        let absolutePath = url?.path ?? "\(path)\\\(p)"
        var item = FileItem(name: p.name(), absolutePath: absolutePath)
        
        // 根据文件后缀辨识编程语言种类 
        let extname = absolutePath.extname()
        switch extname {
        case "h","m", "mm":
            item.itemType = .oc
        case "swift":
            item.itemType = .swift
        case "c", "hpp", "cpp":
            item.itemType = .c
        case "rb":
            item.itemType = .ruby
        case "sh":
            item.itemType = .shell
        case "java":
            item.itemType = .java
        default:
            item.itemType = .other
        }
        
        items.append(item)
    }
    return items
}

复制代码

脚本逻辑实现:

import Foundation
import ArgumentParser


struct FileCounter: ParsableCommand {
    /// 用户输入参数
    @Argument(help: "List all files")
    var file: String?
    
    func run() throws {
        let root = currentPath()
        
        print(root)
        var total = 0
        var oc = CodeLanguage(itemType: .oc)
        var swift = CodeLanguage(itemType: .swift)
        var c = CodeLanguage(itemType: .c)
        var ruby = CodeLanguage(itemType: .ruby)
        var sh = CodeLanguage(itemType: .shell)
        var java = CodeLanguage(itemType: .java)
        var other = CodeLanguage(itemType: .other)
        
        do {
            let _l = try list(path: root)
            for item in _l {
                total += 1
                switch item.itemType {
                case .oc:
                    oc += 1
                case .swift:
                    swift += 1
                case .c:
                    c += 1
                case .ruby:
                    ruby += 1
                case .shell:
                    sh += 1
                case .java:
                    java += 1
                default:
                    other += 1
                }
            }
            
            print("Total \(total) files percentile:")
            let inputc = true
            let filter = file != nil

// 根据文件数降序
            var sorts = [oc, swift, c, ruby, java,sh, other]
            sorts.sort { (c1, c2) -> Bool in
                c1.count > c2.count
            }
            for c in sorts {
                let p = Float(c.count) / Float(total)
                
                if p == 0 {
                    continue
                }
                
                var pstr = "\(p * 100)"
                
                if pstr.contains(".") {
                    let parr = pstr.components(separatedBy: ".")
                    let last = parr.last ?? ""
                    if Int(last) != 0 {
                        var pindex = 1
                        for c in last {
                            if c == "0" {
                                pindex += 1
                            } else {
                                break
                            }
                        }
                        pstr = String(format: "%.\(pindex)f", p * 100)
                    } else {
                        pstr = parr.first ?? ""
                    }
                }
                
                var output = "\(c.name)"

                if filter {
                    if file!.lowercased() != c.name.lowercased() {
                        continue
                    }
                }

                if inputc {
                    output += " \(c.count)"
                }
                output += " "
                output += pstr
                output += "%"
                print(output)
            }
        }
    }
}


最后执行脚本程序:

FileCounter.main()

复制代码

我们编写完程序,可以直接运行Xcode run进行测试,当然也可以直接编译生成一个可执行文件。通过以下命令即可。

在项目根目录输入:

swift package -c release
复制代码

这会在./build/release/目录下生成我们需要的可执行文件。

cd .build/release
cp -f FileCounter /usr/local/bin/fcounter
复制代码

cp(copy file)命令主要用于复制文件或目录。cp -f: 覆盖已经存在的目标文件而不给出提示。

这样我们可以直接在我们任意项目下执行命令:fcounter 或者 fcounter swift,即可列出所有编程语言占比,或是我们想要的编程语言的占比。

最终实现效果:

/Users/zl/Developer/Project/你的项目根目录
Total 18027 files percentile:
Other 10053 55.8%
Objective-C 7252 40.2%
Swift 580 3.2%
C/C++ 136 0.8%
Shell 4 0.02%
Ruby 2 0.01%

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享