daily pastebin goal
61%
SHARE
TWEET

Untitled

a guest Jan 16th, 2019 75 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env xcrun swift
  2.  
  3. import Darwin
  4. import Foundation
  5.  
  6. struct ANSIColors {
  7.     static let clear = "\u{001B}[0m"
  8.     static let red = "\u{001B}[38;5;160m"
  9.     static let orange = "\u{001B}[38;5;202m"
  10.     static let yellow = "\u{001B}[38;5;220m"
  11.     static let green = "\u{001B}[0;32m"
  12.     static let blue = "\u{001B}[0;36m"
  13.     static let grey = "\u{001B}[38;5;237m"
  14. }
  15.  
  16. struct Config {
  17.     enum DiffMode: String {
  18.         case fastlane
  19.         case live
  20.     }
  21.     var scriptName = "time-diff"
  22.     var diffMode = DiffMode.live
  23.     var low = 1
  24.     var medium = 5
  25.     var high = 10
  26.     var summaryLimit = 20
  27.     var resetRegex: NSRegularExpression? = nil
  28.    
  29.     func colorCode(duration: TimeInterval) -> String {
  30.         if Int(duration) >= high {
  31.             return ANSIColors.red
  32.         } else if Int(duration) >= medium {
  33.             return ANSIColors.orange
  34.         } else if Int(duration) >= low {
  35.             return ANSIColors.yellow
  36.         } else {
  37.             return ANSIColors.grey
  38.         }
  39.     }
  40.    
  41.     func resetMatch(_ string: String) -> Bool {
  42.         if let resetRegex = config.resetRegex {
  43.             if let _ = resetRegex.firstMatch(in: string, range: NSMakeRange(0, (string as NSString).length)) {
  44.                 return true
  45.             }
  46.         }
  47.         return false
  48.     }
  49. }
  50.  
  51. extension String {
  52.    
  53. }
  54.  
  55. func usage(error: String) -> Never {
  56.     let scriptLocation = CommandLine.arguments.first ?? "time-diff.swift"
  57.     print(ANSIColors.red, "👉 ", error, ANSIColors.clear, separator: "")
  58.     print(ANSIColors.red, "Script failed ", scriptLocation, ANSIColors.clear, separator: "")
  59.     let defaultConfig = Config()
  60.     print("""
  61.  
  62.         Usage: \(scriptLocation) [-l low] [-m medium] [-h high] [-r reset-mark] [-d diff-mode] [-s summary-limit] [-f --fastlane]
  63.           -l, --low                   Threshold in seconds for low duration color formatting (default: \(defaultConfig.low))
  64.           -m, --medium                Threshold in seconds for medium duration color formatting (default: \(defaultConfig.medium))
  65.           -h, --high                  Threshold in seconds for high duration color formatting (default: \(defaultConfig.high))
  66.           -r, --reset-mark            String match to reset total counter (default: none)
  67.           -d, --diff-mode             Valid options is "live" or "fastlane (default: live)
  68.           -s, --summary-limit         Maximum number of lines in summary (default: \(defaultConfig.summaryLimit))
  69.        
  70.           -f, --fastlane              Shortcut for --diff-mode fastlane --reset-mark "Step :"
  71.        
  72.         Example: \(scriptLocation) --low \(defaultConfig.low) --medium \(defaultConfig.medium) --high \(defaultConfig.high) --reset-mark "Step: " --diff-mode \(defaultConfig.diffMode.rawValue) --summary-limit \(defaultConfig.summaryLimit)
  73.        
  74.         Example: fastlane build | \(scriptLocation) -f
  75.         """)
  76.     exit(1)
  77. }
  78.  
  79. func parseCLIArguments() -> Config {
  80.     var config = Config()
  81.     var arguments = CommandLine.arguments
  82.     arguments.removeFirst()
  83.     while arguments.isEmpty == false {
  84.         let argument = arguments.removeFirst()
  85.         switch argument {
  86.         case "-d", "--diff-mode":
  87.             guard !arguments.isEmpty else {
  88.                 usage(error: "Missing value on  option option")
  89.             }
  90.             guard let diffMode = Config.DiffMode(rawValue: arguments.removeFirst().lowercased()) else {
  91.                 usage(error: "Bad value sent to  option option")
  92.             }
  93.             config.diffMode = diffMode
  94.         case "-r", "--reset-mark":
  95.             guard !arguments.isEmpty else {
  96.                 usage(error: "Missing value on --reset mark")
  97.             }
  98.             do {
  99.                 config.resetRegex = try NSRegularExpression(pattern: arguments.removeFirst())
  100.             } catch {
  101.                 usage(error: "Bad regex pattern passed to \(argument) option. Error: \(error.localizedDescription))")
  102.             }
  103.         case "-l", "--low":
  104.             guard !arguments.isEmpty, let value = Int(arguments.removeFirst()) else {
  105.                 usage(error: "Bad value passed to \(argument) option")
  106.             }
  107.             config.low = value
  108.         case "-m", "--medium":
  109.             guard !arguments.isEmpty, let value = Int(arguments.removeFirst()) else {
  110.                 usage(error: "Bad value passed to \(argument) option")
  111.             }
  112.             config.medium = value
  113.         case "-h", "--high":
  114.             guard !arguments.isEmpty, let value = Int(arguments.removeFirst()) else {
  115.                 usage(error: "Bad value passed to \(argument) option")
  116.             }
  117.             config.high = value
  118.         case "-s", "--summary-limit":
  119.             guard !arguments.isEmpty, let value = Int(arguments.removeFirst()) else {
  120.                 usage(error: "Bad value passed to \(argument) option")
  121.             }
  122.             config.summaryLimit = value
  123.         case "-f", "--fastlane":
  124.             if config.resetRegex == nil {
  125.                 config.resetRegex = try! NSRegularExpression(pattern: "Step: ")
  126.             }
  127.             config.diffMode = .fastlane
  128.         default:
  129.             usage(error: "Unknown argument \"\(argument)\"")
  130.         }
  131.     }
  132.     return config
  133. }
  134.  
  135. extension String {
  136.     func leftPadding(toLength: Int, withPad character: Character) -> String {
  137.         if self.count < toLength {
  138.             return String(repeatElement(character, count: toLength - self.count)) + self
  139.         } else {
  140.             return self
  141.         }
  142.     }
  143. }
  144.  
  145.  
  146.  
  147. func parseFastlaneDate(string: String) -> TimeInterval? {
  148.     let scanner = Scanner(string: string)
  149.     var hours: Int = 0
  150.     var minutes: Int = 0
  151.     var seconds: Int = 0
  152.     if scanner.scanInt(&hours),
  153.         scanner.scanString(":", into: nil),
  154.         scanner.scanInt(&minutes),
  155.         scanner.scanString(":", into: nil),
  156.         scanner.scanInt(&seconds) {
  157.         return TimeInterval(seconds) + (TimeInterval(minutes) * 60) + (TimeInterval(hours) * 60 * 60)
  158.     }
  159.     return nil
  160. }
  161.  
  162. class Chapter {
  163.     struct Offender {
  164.         var duration: TimeInterval
  165.         var timestamp: TimeInterval
  166.         var line: String
  167.     }
  168.     var name: String
  169.     var offenders: [Offender] = []
  170.     var endTime: TimeInterval? = nil
  171.     var startTime: TimeInterval? = nil
  172.     var duration: TimeInterval? {
  173.         if let endTime = endTime, let startTime = startTime {
  174.             return endTime - startTime
  175.         }
  176.         return nil
  177.     }
  178.     var limit: Int {
  179.         didSet {
  180.             trim()
  181.         }
  182.     }
  183.    
  184.     init(name: String, limit: Int) {
  185.         self.name = name
  186.         self.limit = limit
  187.     }
  188.    
  189.     func addLineIfSlow(duration: TimeInterval, minimumLimit: TimeInterval, timestamp: TimeInterval, line: String) {
  190.         guard duration > minimumLimit else {
  191.             return
  192.         }
  193.         if duration > offenders.last?.duration ?? 0 || offenders.count < limit {
  194.             offenders.append(Offender(duration: duration, timestamp: timestamp, line: line))
  195.             sortOffendersByDuration()
  196.             trim()
  197.         }
  198.     }
  199.    
  200.     func sortOffendersByTimeStamp() {
  201.         offenders.sort { $0.timestamp < $1.timestamp }
  202.     }
  203.    
  204.     func sortOffendersByDuration() {
  205.         offenders.sort { $0.duration > $1.duration }
  206.     }
  207.    
  208.     func trim() {
  209.         while offenders.count > limit {
  210.             offenders.removeLast()
  211.         }
  212.     }
  213. }
  214.  
  215. let config = parseCLIArguments()
  216. var lastTime: Double? = nil
  217. var time: Double? = nil
  218. var chapter: Chapter = Chapter(name: "First chapter\n", limit: config.summaryLimit)
  219. var total: Chapter = Chapter(name: "Everything\n", limit: config.summaryLimit)
  220. var chapters: [Chapter] = [chapter]
  221. var lastLine: String? = nil
  222. while let line = readLine(strippingNewline: false) {
  223.     switch config.diffMode {
  224.     case .fastlane:
  225.         let dateString = String(line.prefix(9).suffix(8))
  226.         time = parseFastlaneDate(string: dateString) ?? time
  227.     case .live:
  228.         time = Date().timeIntervalSinceReferenceDate
  229.     }
  230.     if lastTime == nil {
  231.         lastTime = time
  232.     }
  233.     if chapter.startTime == nil {
  234.         chapter.startTime = time
  235.     }
  236.     if total.startTime == nil {
  237.         total.startTime = time
  238.     }
  239.     if config.resetMatch(line) {
  240.         print(ANSIColors.blue,
  241.               "Reseting timer ---------------- ",
  242.               ANSIColors.clear,
  243.               line, separator: "", terminator: "")
  244.         if let time = time {
  245.             chapter.endTime = time
  246.         }
  247.         chapter = Chapter(name: line, limit: config.summaryLimit)
  248.         chapters.append(chapter)
  249.         chapter.startTime = time
  250.     }
  251.     else if let time = time, let chapterTime = chapter.startTime {
  252.         let lastDiff = time - (lastTime ?? 0)
  253.         let chapterDiff = time - chapterTime
  254.         print(config.colorCode(duration: lastDiff),
  255.               String(format: "+ %.0f", lastDiff).leftPadding(toLength: 7, withPad: " "),
  256.               " seconds",
  257.               ANSIColors.grey,
  258.               " = ",
  259.               String(format: "%.0f", chapterDiff).leftPadding(toLength: 5, withPad: " "),
  260.               " seconds ",
  261.               ANSIColors.clear,
  262.               line, separator: "", terminator: "")
  263.         if let lastLine = lastLine {
  264.             chapter.addLineIfSlow(duration: lastDiff, minimumLimit: TimeInterval(config.low), timestamp: time, line: lastLine)
  265.             total.addLineIfSlow(duration: lastDiff, minimumLimit: TimeInterval(config.low), timestamp: time, line: lastLine)
  266.         }
  267.     }
  268.     else {
  269.         print("                                ", line, separator: "", terminator: "")
  270.     }
  271.     lastTime = time
  272.     lastLine = line
  273. }
  274.  
  275. chapter.endTime = time
  276. total.endTime = time
  277.  
  278. let onlyOneChapterBesidesTotal = chapters.count == 2
  279. if onlyOneChapterBesidesTotal == true {
  280.     chapters.removeLast()
  281. }
  282.  
  283. func printSummary(chapter: Chapter) {
  284.     print(ANSIColors.grey,
  285.           String(format: "%.0f", chapter.duration ?? 0).leftPadding(toLength: 6, withPad: " "),
  286.           " seconds in total ",
  287.           ANSIColors.blue,
  288.           "# ",
  289.           ANSIColors.clear,
  290.           chapter.name, separator: "", terminator: "")
  291.     for offender in chapter.offenders {
  292.         print(config.colorCode(duration: offender.duration),
  293.               String(format: "%.0f", offender.duration).leftPadding(toLength: 15, withPad: " "),
  294.               " seconds ",
  295.               ANSIColors.blue,
  296.               "  ",
  297.               ANSIColors.clear,
  298.               offender.line, separator: "", terminator: "")
  299.     }
  300.     if chapter.offenders.count == 0 {
  301.         print(ANSIColors.grey,
  302.               "".leftPadding(toLength: 15, withPad: " "),
  303.               "           (No significant events)\n",
  304.               ANSIColors.clear, separator: "", terminator: "")
  305.     }
  306.     print()
  307. }
  308.  
  309. if config.summaryLimit > 0 {
  310.     print("\n\n", ANSIColors.blue, "========================= Summary by timestamp =========================", ANSIColors.clear, "\n", separator: "")
  311.     for chapter in chapters {
  312.         chapter.sortOffendersByTimeStamp()
  313.         printSummary(chapter: chapter)
  314.     }
  315.    
  316.     print("\n", ANSIColors.blue, "========================= Summary by duration ==========================", ANSIColors.clear, "\n", separator: "")
  317.     for chapter in chapters {
  318.         chapter.sortOffendersByDuration()
  319.         printSummary(chapter: chapter)
  320.     }
  321. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top