Skip to content

Commit

Permalink
[IMA-10467] Add convert command to export a JSON representation of Ap…
Browse files Browse the repository at this point in the history
…p Thinning Size Reports (#73)

* Add converter

* add converter

* update parser

* added documentation, update code

* [IMA-10467] Add method documentation.

* [IMA-10467] Update convert overview text.

* [IMA-10467] Fix error with setting the output name based on the report path. Also modified usage text.

Co-authored-by: Vido Shaweddy <vido.shaweddy@chargepoint.com>
  • Loading branch information
rsukumar-cpi and vshaweddy-cpi authored Feb 25, 2022
1 parent 40f3848 commit 3622c47
Show file tree
Hide file tree
Showing 24 changed files with 1,464 additions and 5 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/apple/swift-tools-support-core.git",
"state": {
"branch": null,
"revision": "3b6b97d612b56e25d80d0807f5bc38ea08b7bdf3",
"version": "0.2.3"
"revision": "f9bbd6b80d67408021576adf6247e17c2e957d92",
"version": "0.2.4"
}
}
]
Expand Down
18 changes: 16 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ let package = Package(
.library(
name: "XCParseCore",
targets: ["XCParseCore"]
),
.library(
name: "Converter",
targets: ["Converter"]
)
],
dependencies: [
Expand All @@ -25,13 +29,23 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "xcparse",
dependencies: [ "XCParseCore", "SwiftToolsSupport-auto" ]),
dependencies: [ "XCParseCore", "SwiftToolsSupport-auto", "Converter" ]),
.target(
name: "XCParseCore",
dependencies: [ "SwiftToolsSupport-auto" ]),
.target(
name: "Converter",
dependencies: ["XCParseCore"]),
.target(
name: "testUtility",
dependencies: [],
path: "Tests/Utility"),
.testTarget(
name: "xcparseTests",
dependencies: ["xcparse"]),
dependencies: ["xcparse", "testUtility"]),
.testTarget(
name: "appThinningConverterTests",
dependencies: ["Converter", "testUtility"]),
],
swiftLanguageVersions: [.v5]
)
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,26 @@ This will export logs & diagnostic files into a per-action folder structure simi

![Logs exported into folders](Docs/Images/screenshots_logs.png?raw=true)

### Convert

```
xcparse convert /path/to/AppThinningSizeReport.txt /path/to/outputDirectory --flagVariants sizeLimit
```

This will export a JSON representation of the given App Thinning Size Report to the output directory.

#### Flag Variants

The ```--flag-variants``` option can allow for the generation of an app size violations report with all the app variants that exceed the specified size limit.

| Examples | Description |
|-------------------------------------|--------------------------------|
| ```--flag-variants 10MB``` | Flags variants that exceed 10MB|
| ```--flag-variants 13``` | Flags variants that exceed 13MB|
| ```--flag-variants invalid``` | Flags variants that exceed 10MB|

If a size unit is not specified, the default unit is considered to be megabytes. Similarly, invalid arguments cause a default size limit of 10MB to be used for flagging app size violations.

### Help

```
Expand Down
32 changes: 32 additions & 0 deletions Sources/Converter/Helper/FileController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// FileController.swift
// AppThinningConverter
//
// Created by Vido Shaweddy on 3/25/21.
//

import Foundation

public class FileController {
// load file with given url
public static func loadFileContents(url: String) -> String? {
var res: String?
let url = URL(fileURLWithPath: url)
if let fileContents = try? String(contentsOf: url) {
res = fileContents
}

return res
}

// write file to the url directory for the given data
public static func writeFile(data: String, url: String, outputName: String = "report", format: String = "json") {
let url = URL(fileURLWithPath: url)
let location = url.appendingPathComponent("\(outputName).\(format)")
do {
try data.write(to: location, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error.localizedDescription)
}
}
}
172 changes: 172 additions & 0 deletions Sources/Converter/Helper/MemorySize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//
// Units.swift
// AppThinningConverter
//
// Created by Vido Shaweddy on 3/30/21.
//

import Foundation

public struct MemorySize {
enum Unit: String, Codable {
case bytes = "B"
case kilobytes = "KB"
case megabytes = "MB"
case gigabytes = "GB"
}

public var bytes: Double {
get {
return Double(kilobytes) * 1_024
}
}

public var kilobytes: Double

public var megabytes: Double {
return kilobytes / 1_024
}

public var gigabytes: Double {
return megabytes / 1_024
}

public init(bytes: Int64) {
self.kilobytes = Double(bytes) / 1_024
}

public init(bytes: Double) {
self.kilobytes = bytes / 1_024
}

public init(kilobytes: Double) {
self.kilobytes = kilobytes
}

public init(megabytes: Double) {
self.kilobytes = megabytes * 1_024
}

public init(gigabytes: Double) {
self.kilobytes = gigabytes * 1_024 * 1_024
}

public init?(text: String) {
self.kilobytes = 0
if let value = parseFrom(text: text) {
self.kilobytes = value.kilobytes
} else {
return nil
}
}
}

extension MemorySize {
var displayString: String {
switch bytes {
case 0..<pow(1_024, 1):
return "\(bytes) bytes"
case pow(1_024, 1)..<pow(1_024, 2):
return "\(String(format: "%.2f", kilobytes)) KB"
case pow(1_024, 2)..<pow(1_024, 3):
return "\(String(format: "%.2f", megabytes)) MB"
case pow(1_024, 3)...:
return "\(String(format: "%.2f", gigabytes)) GB"
default:
return "\(bytes) bytes"
}
}

// parse memory size from text
func parseFrom(text: String) -> MemorySize? {
let textToMemoryUnit: [String: MemorySize.Unit] =
[
"b": .bytes,
"byte": .bytes,
"bytes": .bytes,
"kb": .kilobytes,
"kilobyte": .kilobytes,
"kilobytes": .kilobytes,
"mb": .megabytes,
"megabyte": .megabytes,
"megabytes": .megabytes,
"gb": .gigabytes,
"gigabyte": .gigabytes,
"gigabytes": .gigabytes,
]

let unit = textToMemoryUnit[parseUnits(text: text)] ?? .megabytes
guard let size = parseSize(text: text) else {
return nil
}

switch unit {
case .bytes:
return MemorySize(bytes: size)
case .kilobytes:
return MemorySize(kilobytes: size)
case .megabytes:
return MemorySize(megabytes: size)
case .gigabytes:
return MemorySize(gigabytes: size)
}
}

// parse the unit from text
func parseUnits(text: String) -> String {
// when it's 0 kb the report text is going to be "zero kb" so we need to handle it manually
if text.lowercased() == Self.zeroSize { return "kb" }
return parseUnits(text: Array(text))
}

// parse the unit from text (array of character)
func parseUnits(text: [Character]) -> String {
var unitString = ""

for character in text where character.isLetter {
if character != "." && character != "," {
unitString.append(character.lowercased())
}
}

return unitString
}

// parse the memory size from text
func parseSize(text: String) -> Double? {
// when it's 0 kb the report text is going to be "zero kb" so we need to handle it manually
if text.lowercased() == Self.zeroSize { return 0 }
return parseSize(text: Array(text))
}

// parse the memory size from text (array of character)
func parseSize(text: [Character]) -> Double? {
var sizeString = ""

for character in text {
if character.isNumber || character == "." || character == "," {
sizeString.append(character)
}
}

return Double(sizeString)
}
}

extension MemorySize: Comparable {
public static func < (lhs: MemorySize, rhs: MemorySize) -> Bool {
return lhs.kilobytes < rhs.kilobytes
}

public static func > (lhs: MemorySize, rhs: MemorySize) -> Bool {
return lhs.kilobytes > rhs.kilobytes
}

public static func == (lhs: MemorySize, rhs: MemorySize) -> Bool {
return lhs.kilobytes == rhs.kilobytes
}
}

public extension MemorySize {
static let zeroSize = "zero kb"
}
35 changes: 35 additions & 0 deletions Sources/Converter/Helper/ResultFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// ResultFactory.swift
// AppThinningConverter
//
// Created by Vido Shaweddy on 3/29/21.
//

import Foundation

final class ResultFactory {
/// A factory class for parsing text
///
/// Use this method to get the result for each parser.
/// - Warning: Returns optional any type. if the parser is unable to find the data it will returns nil.
/// - Parameter:
/// - text: the text you want to parse
/// - modelType: the
/// - Returns: The result for each parser in any data type.
func parse(from text: String, using parser: VariantModel.ParsingKeys) -> Any? {
let result: Any?
switch parser {
case .variant:
result = VariantParser(text: text).result
case .supportedVariantDescriptors:
result = VariantDescriptorParser(text: text).result
case .appOnDemandResourcesSize:
result = AppSizeParser(text: text).result
case .appSize:
result = AppSizeParser(text: text).result
case .onDemandResourcesSize:
result = AppSizeParser(text: text).result
}
return result
}
}
45 changes: 45 additions & 0 deletions Sources/Converter/Models/AppSizeModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// AppOnDemandModel.swift
// AppThinningConverter
//
// Created by Vido Shaweddy on 3/25/21.
//

import Foundation

protocol ModelSize: Codable, Equatable {
var compressed: SizeModel { get set }
var uncompressed: SizeModel { get set }
}

class AppSizeModel: ModelSize {
var compressed: SizeModel
var uncompressed: SizeModel

enum CodingKeys: String, CodingKey, CaseIterable {
case compressed, uncompressed
}

// MARK: - Constructor

init(compressed: SizeModel = SizeModel.placeholder(), uncompressed: SizeModel = SizeModel.placeholder()) {
self.compressed = compressed
self.uncompressed = uncompressed
}

static func == (lhs: AppSizeModel, rhs: AppSizeModel) -> Bool {
return lhs.compressed == rhs.compressed && lhs.uncompressed == rhs.uncompressed
}
}

struct SizeModel: Codable, Equatable {
let rawValue: String
let value: Double
let unit: MemorySize.Unit
}

private extension SizeModel {
static func placeholder() -> SizeModel {
return SizeModel(rawValue: "Unknown", value: 0, unit: .bytes)
}
}
22 changes: 22 additions & 0 deletions Sources/Converter/Models/DeviceModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// DeviceModel.swift
// AppThinningConverter
//
// Created by Vido Shaweddy on 3/25/21.
//

import Foundation

struct DeviceModel: Codable, Equatable {
let device: String
let osVersion: String

enum ParsingKeys: String, CaseIterable {
case device
case osVersion = "os-version"
}

enum CodingKeys: String, CodingKey {
case device, osVersion
}
}
Loading

0 comments on commit 3622c47

Please sign in to comment.