//
// APIFetcher.swift
// C Code Develop
//
// Created by xcbosa on 2022/6/20.
// Copyright © 2022 xcbosa. All rights reserved.
//
import Foundation
public typealias ProvideRootMarkdownBlock = () -> [APIDocFileCollectionModel]
public protocol APIDocMarkdownGenerable: AnyObject {
func generateMarkdown() -> String
var navigationItemTitle: String { get }
var shareURL: URL? { get }
var collectionPath: String { get }
var linkPathPrefix: String { get set }
var language: ElementDefs.SupportedLanguage { get set }
var provideRootMarkdownBlock: ProvideRootMarkdownBlock? { get set }
}
extension APIDocMarkdownGenerable {
public func getCollectionName() -> String {
switch collectionPath {
case "stdc": return "APIDoc.StandardCHeaderName".apiDocLocalized(language)
case "cdenvc": return "APIDoc.ExtendedCHeaderName".apiDocLocalized(language)
default: return collectionPath
}
}
}
public struct FunctionParamDef {
public var name: String
public var type: String
public var description: String
}
public extension APIDocMarkdownGenerable {
var cKeyWordsExtended: [String] {
[
"break", "case", "char", "continue", "default", "do", "double", "else", "extern", "false", "FALSE", "float", "for", "goto", "if", "int", "long", "register", "const", "return", "short", "signed", "sizeof", "struct", "null", "static", "switch", "true", "TRUE", "typedef", "unsigned", "void", "while", "include", "define", "builtin", "unsigned int", "unsigned long", "long long", "unsigned char", "unsigned short", "__Macro", "const int", "const long", "const float", "const double", "Never", "const void", "const char", "const short", "const unsigned char", "const unsigned short", "const unsigned int", "const unsigned long", "union"
]
}
func buildLinkPath(withMemberName name: String?, inFile file: String, inCollection collection: String) -> String {
if let name = name {
return "\(linkPathPrefix)/\(collection)/\(file)/\(name)"
} else {
return "\(linkPathPrefix)/\(collection)/\(file)"
}
}
func color(token str: String, drawFunctionName: Bool, drawTypeName: Bool, drawStructName: Bool, drawVariableName: Bool) -> String {
var pstr = str
while pstr.first == "*" { pstr.removeFirst() }
while pstr.last == "*" { pstr.removeLast() }
let colorTable = CLangColorTable.getColorTable()
if cKeyWordsExtended.contains(pstr) {
return "\(str)"
}
var files = [APIDocFileCollectionModel]()
if let rootArray = provideRootMarkdownBlock?() {
files = rootArray
} else {
print("[APIDoc] provideRootMarkdownBlock = nil, Using \"Current\"")
if let self = self as? APIDocFileModel {
files.append(APIDocFileCollectionModel([self], withCollectionTitle: "Current", andCollectionStoragePath: "Current"))
}
}
for it in files {
for doc in it.files {
if doc.fileName.hasSuffix(".h") {
let items = doc.items
if drawFunctionName,
items.filter({ $0.type == .functionDefintion }).contains(where: { $0.name == pstr }) {
return "\(str)"
}
if drawTypeName {
if pstr.hasPrefix("struct ") {
if items.filter({ $0.type == .structDefinition }).contains(where: { $0.name == pstr.split(separator: " ").last?.description ?? "" }) {
return "\(str)"
}
}
if items.filter({ $0.type == .typeDefinition }).contains(where: { $0.name == pstr }) {
return "\(str)"
}
}
if drawStructName,
items.filter({ $0.type == .structDefinition }).contains(where: { $0.name == pstr }) {
return "\(str)"
}
if drawVariableName,
items.filter({ $0.type == .variableDefinition }).contains(where: { $0.name == pstr }) {
return "\(str)"
}
}
}
}
return str
}
func drawFunc(_ token: String) -> String { color(token: token, drawFunctionName: true, drawTypeName: false, drawStructName: false, drawVariableName: false) }
func drawType(_ token: String) -> String { color(token: token, drawFunctionName: false, drawTypeName: true, drawStructName: false, drawVariableName: false) }
func drawStruct(_ token: String) -> String { color(token: token, drawFunctionName: false, drawTypeName: false, drawStructName: true, drawVariableName: false) }
func drawVariable(_ token: String) -> String { color(token: token, drawFunctionName: false, drawTypeName: false, drawStructName: false, drawVariableName: true) }
func getFuncParamDef(_ def: ElementDefs) -> [FunctionParamDef] {
let paramArray = def.functionArgs.split(separator: ",").filter({ !$0.isEmpty })
var defs = [FunctionParamDef]()
var unnameValIndex = 0
let peekUnnameIndex = { () -> String in
unnameValIndex += 1
return "arg\(unnameValIndex)"
}
for it in paramArray {
let paramDef = it.trimmingCharacters(in: .whitespacesAndNewlines)
let paramDefArray = paramDef.split(separator: " ").filter({ !$0.isEmpty })
var paramType = ""
var paramName = ""
for id in 0.. String {
let paramArray = str.split(separator: ",").filter({ !$0.isEmpty })
var strbuf = ""
var unnameValIndex = 0
let peekUnnameIndex = { () -> String in
unnameValIndex += 1
return "arg\(unnameValIndex)"
}
for it in paramArray {
let paramDef = it.trimmingCharacters(in: .whitespacesAndNewlines)
let paramDefArray = paramDef.split(separator: " ").filter({ !$0.isEmpty })
var paramType = ""
var paramName = ""
for id in 0.. String {
let colorTable = CLangColorTable.getColorTable()
switch element.type {
case .functionDefintion:
return "\(drawType(element.functionReturn)) \(drawFunc(element.name))(\(drawParamTable(element.functionArgs)));".replacingOccurrences(of: "*", with: "\\*")
case .structDefinition:
return "struct { ... } \(drawStruct(element.name));".replacingOccurrences(of: "*", with: "\\*")
case .typeDefinition:
return "\(color(token: "typedef", drawFunctionName: false, drawTypeName: false, drawStructName: false, drawVariableName: false)) \(drawType(element.typeImplementation)) \(drawType(element.name));".replacingOccurrences(of: "*", with: "\\*")
case .variableDefinition:
return "\(drawType(element.variableType)) \(drawVariable(element.name));".replacingOccurrences(of: "*", with: "\\*")
case .anyToken:
return "\(element.name)".replacingOccurrences(of: "*", with: "\\*")
case .functionPointer:
return "\(drawType(element.functionReturn)) (\(element.functionPointerFlag) \(drawType(element.name)))(\(drawParamTable(element.functionArgs)));"
case .specialReserved:
return "\(element.name)".replacingOccurrences(of: "*", with: "\\*")
case .unionDefinition:
return "union { ... } \(drawStruct(element.name));".replacingOccurrences(of: "*", with: "\\*")
}
}
}
public class APIDocMemberModel: APIDocMarkdownGenerable {
public var language: ElementDefs.SupportedLanguage = .unspecified
public var provideRootMarkdownBlock: ProvideRootMarkdownBlock?
public var member: ElementDefs
public var collectionPath: String
public var fileName: String
public var linkPathPrefix: String = "https://docs.forgetive.org"
public var shareURL: URL? {
URL(string: "\(linkPathPrefix)/\(collectionPath)/\(fileName)/\(member.name)")
}
public var navigationItemTitle: String { member.name }
public init(_ item: ElementDefs, inFile file: String, inCollection collection: String) {
member = item
fileName = file
collectionPath = collection
}
public func generateMarkdown() -> String {
let colorTable = CLangColorTable.getColorTable()
let headerColorHex = colorTable.getTypeContentColor().hexString
var markdown = ""
markdown.append("#### \("APIDoc.\(member.type.rawValue)".apiDocLocalized(language)) (\("APIDoc.BelongsTo".apiDocLocalized(language)) \(getCollectionName()) / \(fileName)) \n")
markdown.append("# \(member.name) \n")
markdown.append("\(drawDefinition(forElement: member)) \n")
markdown.append("### \(member.getLocalizedComment(withKey: nil, language: language)) \n")
let filePath = Bundle.main.resourcePath! + "/CodeAnalyserFile/article/\(fileName).md"
if ["cdenvc", "stdc"].contains(collectionPath), FileManager.default.fileExists(atPath: filePath) {
markdown.append("### \("APIDoc.Article".apiDocLocalized(language)) \n")
}
var wantDrawBottomLine = true
if member.type == .functionDefintion || member.type == .functionPointer {
let paramDef = getFuncParamDef(member)
if !paramDef.isEmpty {
if wantDrawBottomLine {
markdown.append(" \n-------- \n")
wantDrawBottomLine = false
}
markdown.append("### \("APIDoc.ParamTable".apiDocLocalized(language)) \n")
for it in paramDef {
markdown.append(" \(drawType(it.type)) \(it.name) \n")
if !it.description.isEmpty {
markdown.append(" \(it.description) \n \n")
}
}
wantDrawBottomLine = true
}
}
if member.type == .typeDefinition || member.type == .structDefinition {
let currentObjectName = member.type == .typeDefinition ? member.name : "struct \(member.name)"
let collections = provideRootMarkdownBlock?() ?? []
var relatedTypes = [ElementDefs]()
var relatedFunctions = [ElementDefs]()
var relatedVariables = [ElementDefs]()
for collection in collections {
for file in collection.files {
if file.fileName.hasSuffix(".h") {
for member in file.items {
switch member.type {
case .typeDefinition:
if member.typeImplementation == currentObjectName {
relatedTypes.append(member)
}
break
case .functionDefintion:
if member.functionReturn == currentObjectName {
relatedFunctions.append(member)
break
}
let paramDefs = getFuncParamDef(member)
if paramDefs.contains(where: { $0.type == currentObjectName }) {
relatedFunctions.append(member)
}
break
case .variableDefinition:
if member.variableType == currentObjectName {
relatedVariables.append(member)
}
break
default:
break
}
}
}
}
}
if relatedTypes.count + relatedVariables.count + relatedFunctions.count > 0 {
if wantDrawBottomLine {
markdown.append(" \n-------- \n")
wantDrawBottomLine = false
}
markdown.append("### \("APIDoc.Related".apiDocLocalized(language)) \n")
for it in relatedTypes {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
for it in relatedFunctions {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
for it in relatedVariables {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
wantDrawBottomLine = true
}
}
return markdown
}
}
public class ArticleDocModel: APIDocMarkdownGenerable {
public var navigationItemTitle: String { "" }
public var shareURL: URL? { URL(string: "\(linkPathPrefix)/\(collectionPath)/\(filePath)/__article__") }
public var collectionPath: String
public var linkPathPrefix: String = "https://docs.forgetive.org"
public var language: ElementDefs.SupportedLanguage = .unspecified
public var provideRootMarkdownBlock: ProvideRootMarkdownBlock?
public var filePath: String
public init(collectionPath: String, filePath: String) {
self.filePath = filePath
self.collectionPath = collectionPath
}
public func generateMarkdown() -> String {
var markdown = ""
markdown.append("#### \("APIDoc.Article.Title".apiDocLocalized(language)) (\("APIDoc.BelongsTo".apiDocLocalized(language)) \(getCollectionName()) / \(filePath)) \n")
let pathToFile = Bundle.main.resourcePath! + "/CodeAnalyserFile/article/\(filePath).md"
markdown.append((try? String(contentsOfFile: pathToFile)) ?? "")
return markdown
}
}
public class APIDocFileModel: APIDocMarkdownGenerable {
public var language: ElementDefs.SupportedLanguage = .unspecified
public var fileName: String
public var collectionPath: String
public var firstComment: ElementDefs
public var items: [ElementDefs]
public var provideRootMarkdownBlock: ProvideRootMarkdownBlock?
public var linkPathPrefix: String = "https://docs.forgetive.org"
public var shareURL: URL? {
URL(string: "\(linkPathPrefix)/\(collectionPath)/\(fileName)")
}
public var navigationItemTitle: String { fileName }
public convenience init() {
self.init([], firstComment: ElementDefs(withAnyToken: "", comment: ""), withFileName: "", inCollection: "")
}
public init(_ items: [ElementDefs], firstComment: ElementDefs, withFileName file: String, inCollection collectionPath: String) {
self.items = items
self.fileName = file
self.firstComment = firstComment
self.collectionPath = collectionPath
}
public func generateMarkdown() -> String {
var markdown = ""
if fileName.hasSuffix(".h") {
markdown.append("#### \("APIDoc.Header".apiDocLocalized(language)) \n")
}
if fileName.hasSuffix(".c") {
markdown.append("#### \("APIDoc.Source".apiDocLocalized(language)) \n")
}
markdown.append("# \(fileName) \n")
if fileName.hasSuffix(".c") {
markdown.append("\("APIDoc.Source.Warn".apiDocLocalized(language)) \n")
}
markdown.append("### \(firstComment.getLocalizedComment(withKey: "filesummary", language: language)) \n")
let fileDescription = firstComment.getLocalizedComment(withKey: "filedescription", language: language)
if !fileDescription.isEmpty {
markdown.append("\(fileDescription) \n")
}
let filePath = Bundle.main.resourcePath! + "/CodeAnalyserFile/article/\(fileName).md"
if ["cdenvc", "stdc"].contains(collectionPath), FileManager.default.fileExists(atPath: filePath) {
markdown.append("### \("APIDoc.Article".apiDocLocalized(language)) \n")
}
markdown.append("-------- \n")
let typeItems = items.filter { $0.type == .typeDefinition }
let funcPtrItems = items.filter { $0.type == .functionPointer }
let structItems = items.filter { $0.type == .structDefinition }
let functionItems = items.filter { $0.type == .functionDefintion }
let variableItems = items.filter { $0.type == .variableDefinition }
var wantDrawBottomLine = false
if !typeItems.isEmpty {
markdown.append("### \("APIDoc.Type".apiDocLocalized(language)) \n")
for it in typeItems {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
wantDrawBottomLine = true
}
if !funcPtrItems.isEmpty {
markdown.append("### \("APIDoc.FunctionPointer".apiDocLocalized(language)) \n")
for it in funcPtrItems {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
wantDrawBottomLine = true
}
if !structItems.isEmpty {
if wantDrawBottomLine {
markdown.append(" \n-------- \n")
wantDrawBottomLine = false
}
markdown.append("### \("APIDoc.Struct".apiDocLocalized(language)) \n")
for it in structItems {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
wantDrawBottomLine = true
}
if !functionItems.isEmpty {
if wantDrawBottomLine {
markdown.append(" \n-------- \n")
wantDrawBottomLine = false
}
markdown.append("### \("APIDoc.Function".apiDocLocalized(language)) \n")
for it in functionItems {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
wantDrawBottomLine = true
}
if !variableItems.isEmpty {
if wantDrawBottomLine {
markdown.append(" \n-------- \n")
wantDrawBottomLine = false
}
markdown.append("### \("APIDoc.Variable".apiDocLocalized(language)) \n")
for it in variableItems {
markdown.append(" \(drawDefinition(forElement: it)) \n")
let comment = it.getLocalizedComment(withKey: nil, language: language)
if !comment.isEmpty {
markdown.append(" \(comment) \n\n")
} else {
markdown.append("\n")
}
}
wantDrawBottomLine = true
}
return markdown
}
}
public class APIDocFileCollectionModel {
public var collectionTitle: String
public var collectionStoragePath: String
public var files: [APIDocFileModel]
public convenience init() {
self.init([], withCollectionTitle: "", andCollectionStoragePath: "")
}
public init(_ files: [APIDocFileModel], withCollectionTitle name: String, andCollectionStoragePath path: String) {
self.collectionStoragePath = path
self.files = files
self.collectionTitle = name
}
}
public class APIDocFetcher {
public static let cdEnvCHeaders = ["autotree.h", "ccd.h", "debug.h", "http.h", "ccdui.h", "ccduicomp.h"]
public class func fetch(_ code: String, _ name: String, inCollection collectionPath: String) -> APIDocFileModel {
let doc = APIDocFileModel()
doc.fileName = name
doc.collectionPath = collectionPath
let engine = CodeAnalysisEngine()
var includeSet = [String]()
engine.writeElementList = true
engine.analysis(code, name, nil, &includeSet, false)
doc.items = engine.elementList
doc.firstComment = engine.getFirstComment(code)
return doc
}
public class func fetchAll(withGivenProjectContext project: CCDProject?) -> [APIDocFileCollectionModel] {
let standardCollection = APIDocFileCollectionModel([], withCollectionTitle: "APIDoc.StandardCHeaderName".apiDocLocalized(), andCollectionStoragePath: "stdc")
let cdenvcCollection = APIDocFileCollectionModel([], withCollectionTitle: "APIDoc.ExtendedCHeaderName".apiDocLocalized(), andCollectionStoragePath: "cdenvc")
var collections = [standardCollection, cdenvcCollection]
for it in (try? FileManager.default.contentsOfDirectory(atPath: (Bundle.main.resourcePath ?? "") + "/CodeAnalyserFile"))?.filter({ !$0.hasSuffix("_intrinsic.h") }) ?? [] {
let isFolder = UnsafeMutablePointer.allocate(capacity: 1)
FileManager.default.fileExists(atPath: (Bundle.main.resourcePath ?? "") + "/CodeAnalyserFile/" + it, isDirectory: isFolder)
if !isFolder.pointee.boolValue {
let fileName = it
let fileCode = (try? String(contentsOfFile: (Bundle.main.resourcePath ?? "") + "/CodeAnalyserFile/" + it)) ?? ""
let apiDoc = fetch(fileCode, fileName, inCollection: cdEnvCHeaders.contains(fileName) ? "cdenvc" : "stdc")
if cdEnvCHeaders.contains(fileName) {
cdenvcCollection.files.append(apiDoc)
} else {
standardCollection.files.append(apiDoc)
}
}
}
if let project = project {
let currentProjectCollection = APIDocFileCollectionModel([], withCollectionTitle: "\(project.projectName ) \("APIDoc.Current".apiDocLocalized())", andCollectionStoragePath: project.projectName )
for it in project.files.files {
let ifn = it.fileName ?? ""
let ifv = it.fileCode ?? ""
if ifn.hasSuffix(".h") || ifn.hasSuffix(".c") {
currentProjectCollection.files.append(fetch(ifv, ifn, inCollection: project.projectName ))
}
}
collections.append(currentProjectCollection)
}
for it in (try? FileManager.default.contentsOfDirectory(atPath: AppDelegate.documentsDirectory())) ?? [] {
if it == (project?.projectName ?? "") { continue }
let project = CCDProject(openWithName: it)
let currentProjectCollection = APIDocFileCollectionModel([], withCollectionTitle: it, andCollectionStoragePath: it)
let collectionPath = it
if let project = project {
for it in project.files.files {
let ifn = it.fileName ?? ""
let ifv = it.fileCode ?? ""
if ifn.hasSuffix(".h") || ifn.hasSuffix(".c") {
currentProjectCollection.files.append(fetch(ifv, ifn, inCollection: collectionPath))
}
}
}
collections.append(currentProjectCollection)
}
return collections
}
}