// // RUtils.swift // RSwitch // // Created by hrbrmstr on 9/1/19. // Copyright © 2019 Bob Rudis. All rights reserved. // import Foundation import Cocoa struct versionInfo { var path : String var current: Bool } class RVersions { static let macos_r_framework = "/Library/Frameworks/R.framework/Versions" // Where the official R installs go static let replRegex = try!NSRegularExpression(pattern: "[[:alpha:][:space:]\"#_]+", options: NSRegularExpression.Options.caseInsensitive) static func currentVersionTarget() -> String { // get where Current points to let furl = NSURL(fileURLWithPath: (RVersions.macos_r_framework as NSString).appendingPathComponent("Current")) if (furl.fileReferenceURL() != nil) { do { let fdat = try NSURL(resolvingAliasFileAt: furl as URL, options: []) return(fdat.lastPathComponent!) } catch { return(furl.path!) } } else { return("") } } static func preciseVersion(versionPath : String) -> String { let actualPath = NSString.path(withComponents: [ versionPath.starts(with: "/") ? "" : RVersions.macos_r_framework, versionPath, "Headers", "Rversion.h" ]) var out = "" if (FileManager.default.fileExists(atPath:actualPath)) { do { let versionHeader = (try NSString(contentsOfFile: actualPath, encoding: String.Encoding.utf8.rawValue)) as String let majMin = versionHeader .split(separator: "\n") .filter{ $0.contains("R_MAJOR") || $0.contains("R_MINOR") } .map{ replRegex.stringByReplacingMatches(in: String($0), options: [], range: NSMakeRange(0, $0.count), withTemplate: "") } out = " (" + majMin[0] + "." + majMin[1] + ")" } catch { } } return(out) } static func hasRBinary(versionPath : String) -> Bool { let resourcesPath = NSString.path(withComponents: [ versionPath.starts(with: "/") ? "" : RVersions.macos_r_framework, versionPath, "Resources", "bin", "R" ]) return(FileManager.default.fileExists(atPath: resourcesPath)) } static func reloadVersions() throws -> [String] { // gets a directory listing let entries = try FileManager.default.contentsOfDirectory(atPath: RVersions.macos_r_framework) // retrieves all versions (excludes hidden files and the Current alias return(entries.sorted().filter { !($0.hasPrefix(".")) && !($0 == "Current") }) } static func populateRVersionsMenu(menu : NSMenu, handler : Selector) { do { let targetPath = RVersions.currentVersionTarget() let versions = try RVersions.reloadVersions() // populate menu items with all installed R versions, ensuring we // put a checkbox next to the one that is Current var i = 1 for version in versions { let complete = RVersions.hasRBinary(versionPath: version) let keynum = (i < 10) ? String(i) : "" let item = NSMenuItem( title: complete ? version + RVersions.preciseVersion(versionPath: version) : version + " (incomplete)", action: complete ? handler : nil, keyEquivalent: complete ? keynum : "" ) item.isEnabled = complete if (version == targetPath) { item.state = NSControl.StateValue.on } item.representedObject = version menu.addItem(item) i = complete ? i + 1 : i } } catch { AppAlerts.quitAlert("Failed to list contents of R framework directory. You either do not have R installed or have incorrect permissions set on " + RVersions.macos_r_framework) } } }