Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Images: dot syntax #206

Merged
merged 19 commits into from
Dec 5, 2016
Merged

Images: dot syntax #206

merged 19 commits into from
Dec 5, 2016

Conversation

djbe
Copy link
Member

@djbe djbe commented Nov 21, 2016

Follow up of #199, implements the dot-syntax for asset catalogs (mentioned in #52).

For now only has the swift3 template, I want some input before I do the swift 2 version.

@djbe djbe added this to the Next minor (4.1.0) milestone Nov 21, 2016
* master:
  key sorting
  whitespace
  - cleaned up duped pod build phases
  - Playground tweaks - misc style tweaks - changelog tweak
  - Fileprivate audit
  - fixed strings for strict alphabetical ordering - fixed strings test
  - First pass feedback
  - change swiftIdentifier method signature
  - Bump xcode version and doing quick lint to avoid travis pod repo issue   : travis-ci/travis-ci#6473
  - Fixed genumkit swift version
  - Playgrounds to swift 3
  - Naming tweaks from feedback
  - Audit internal APIs for swift 3 compliance
  - Bump swift version and change log
  - Bump swift version and change log
  - Fixed font tests and made font parsing alphabetical for predictablity
  - fixed swift 3 index regression - fixed compiler errors - All tests passing, except font ordering issue
  - Swift 3 syntax to building
  - update pods to swit 3/latest
  - Ran Migrator to swift 3

# Conflicts:
#	GenumKit/Parsers/AssetsCatalogParser.swift
#	GenumKit/Stencil/Contexts.swift
#	SwiftGen.xcodeproj/project.pbxproj
struct Entry {
var name: String
var value: String
var items: [Entry]?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an Entry is either an image (with name and value) or a namespace (with name and sub-entries), shouldn't we use an enum with associated values for that instead?

Like either:

enum Entry {
  case image(name: String, value: String)
  indirect case namespace(name: String, entries: [Entry])
}

Or

struct Entry {
  var name: String
  enum Content {
    case image(value: String)
    case namespace(entries: [Entry])
  }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't used enums with associated values yet (although I've read about them). I'll look into it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the first solution (a simple enum with associated values) would be easier to use than a struct with enum inside.

Using a struct with inner enum is only useful if all cases of the enum share multiple properties, like enough to want to avoid repeating each property in each enum case's associated values.
But here we only have one common property so I think it's not worth it and integrating name directly in each enum case would be easier to manipulate.

And that way you won't need dedicate inits, you can just create Entry.image(name: "foo", value: "parent/foo") and Entry.namespace(name: "parent", entries: children).

PS: Note that I'm not sure if the indirect keyword is really needed. I think so, because the case references the enum being defined, but it's worth trying without to be sure we can't avoid it.

}
}

// MARK: - ACTool

extension AssetsCatalogParser {
fileprivate func loadAssetCatalog(at path: String) -> [[String: AnyObject]]? {
fileprivate func loadAssetCatalog(at path: String) -> [[String: Any]]? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add a doc-comment to this function to document the typical structure of the PLIST/JSON returned by actool, like the keys returned, how they look for image entries, for namespaced images, for namespaces themselves… Would help understand the rest of the code parsing it (especially parse(items😃)

(I know, I should have mentioned that in the other PR about actool migration ^^)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

)
}

func justValues(entries: [Entry]) -> [String] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be private (or fileprivate)?

return result
}

func structure(entries: [Entry], currentLevel: Int = 0, maxLevel: Int = 5) -> [[String: Any]] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be private (or fileprivate)?

}
}

func flatten(entries: [Entry]) -> [[String: Any]] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be private (or fileprivate)?

}
}

func testFileWithDotSyntax() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you don't have the same indentation settings as the ones defined in the project (2-spaces indentation)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, sometimes Xcode forgets them, or something else.

Personally I always use tabs so that's why it sometimes gets through, even though I try to be careful.


if let providesNamespace = item[AssetCatalog.providesNamespace.rawValue] as? NSNumber,
providesNamespace.boolValue {
processCatalog(items: children, withPrefix: "\(prefix)\(filename)/")
providesNamespace.boolValue {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't Swift automatically bridge boolean NSNumber to Bool with as?? Especially since the AnyObject -> Any proposal got in?

I mean, we could probably do if let providesNamespace = item[AsstCatalog.providesNamespace.rawValue] as? Bool directly maybe? (At least it's worth a try)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was written before the swift3 merge. Don't know, but you're right, it should be bridged.

fileprivate func processCatalog(items: [[String: AnyObject]], withPrefix prefix: String = "") {
fileprivate func process(items: [[String: Any]], withPrefix prefix: String = "") -> [Entry] {
var result = [Entry]()

for item in items {
guard let filename = item[AssetCatalog.filename.rawValue] as? String else { continue }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally forgot to see that in the other PR about actool migration, but if you're only using the enum AssetCatalog as a way to group constants together (and never use the cases other than via their rawValue to get the String constant), I'd suggest you to use an enum with no cases, but with static let constants namespaced in the enum instead:

private enum AssetCatalogKeys {
  static let children = "children"
  static let filename = "filename"
  static let providesNamespace = "provides-namespace"
  static let root = "com.apple.actool.catalog-contents"
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we'd be using the enum just as some sort of way to namespace constants?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. That's how I namespace all my constants in my projects now, and have been loving it!

Like I very often have stuff like this in my projects:

struct SomeModel {
  enum JSONKeys {
    static let firstname = "firstName"
    static let lastname = "lastName"
    static let dob = "dateOfBirth"
  }

  let firstname: String
  let lastname: String
  let dateOfBirth: String

  init?(json: [String: Any]) {
    guard
      let fn = json[JSONKeys.firstname] as? String,
      let ln = json[JSONKeys.lastname] as? String,
      let dob = json[JSONKeys.dob] as? String
      else { return nil }
    self.firstname = fn
    self.lastname = ln
    self.dateOfBirth = dob
  }
}

Copy link
Collaborator

@AliSoftware AliSoftware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't hesitate to rebase on top of master, instead of merging master into your branch.

Both have pros and cons, but merging master into your branch means that you can get quite a 🍝 git tree sometimes. And it's then harder to rebase stuff like a PR that has been opened on top of another (like 204 is on top of 201) and make the latest PR only contain the diff commits (instead it adds more 🍝 )

Sure, rebasing instead of merging also have cons, like rewriting history meaning that other people who already have pulled your commit won't have valid commits anymore, but on a feature branch used for a PR, and on which typically only one people commits onto, it seems less a problem than the git tree 🍝 brought by merging master. So sure it's a matter of preferences but I prefer clean trees personally 😉 🌲

fileprivate func loadAssetCatalog(at path: String) -> [[String: Any]]? {
let command = Command("xcrun", arguments: "actool", "--print-contents", path)
fileprivate func loadAssetCatalog(at path: Path) -> [[String: Any]]? {
let command = Command("xcrun", arguments: "actool", "--print-contents", String(describing: path))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't PathKit's Path have a dedicated property to get the path string?
I mean, String(describing:) feels more intended for debug representation purposes to me, while something like path.path or path.string if it exists would feel more right (a bit like you'd use url.absoluteString)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd think that, wouldn't you? There isn't any public one, only internal methods unfortunately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does conform to CustomStringConvertible, but that's what I'm using.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not even path.description from some CustomStringConvertible conformance?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but what's better? String(describing: path) or path.description?

I'll create a PR in PathKit for something better, but I'll leave it up to you what we should use for now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think path.description feels more like a property on Path while String(describing: path), although doing the same thing, feels more like a conversion or builder to me.

@djbe
Copy link
Member Author

djbe commented Nov 26, 2016

What we can do (and kylef from Commander does this), is when merging the pull request to do "squash and merge", that way you'll always have a clean tree.

@AliSoftware
Copy link
Collaborator

👍

@djbe djbe mentioned this pull request Nov 26, 2016
3 tasks
Copy link
Collaborator

@AliSoftware AliSoftware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Fix indentation
  • Add CHANGELOG entry

}
}

guard !found else { return false }
entries.append(Entry.image(name: name, value: name))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation (from lines 17 to 26) seems wrong 😉

<key>com.apple.actool.catalog-contents</key>
<array>
<dict>
<key>children</key>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ tabs indentation

static let Orange: AssetType = "Round/Orange"

struct Red {
static let Apple: AssetType = "Round/Apple"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't the value of this generated constant "Round/Red/Apple" instead of just "Round/Apple"?
Is it because the group in the asset catalogs doesn't have provides-namespace checked, so that's normal and intentional?
In that case I'm wondering if we should create the intermediate structure struct Red if it doesn't provide namespace. I'm thinking YES (and so in this case this output is 👍 ), as otherwise the developer wouldn't have created a group in its asset catalog… but just wanted to start the discussion in case anyone had a different opinion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. Only namespaced folders modify the value (prefixing them with the folder's name).

I also think yes, because the structure in the catalog is there, and SwiftGen should provide an added value by offering this deeper structure. We could pass this extra structure information on to the template, although I'll have to think about how to best implement that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@djbe
Copy link
Member Author

djbe commented Nov 29, 2016

BTW, I based the templates on the strings dot syntax. Any reason why we use nested structs instead of nested enums for namespacing?

@AliSoftware
Copy link
Collaborator

I think it's just because I overlooked the PR implementing those templates at that time, would indeed seem better to use enums instead of structs here.

@djbe
Copy link
Member Author

djbe commented Nov 29, 2016

Did this maybe depend on swift 3?

@AliSoftware
Copy link
Collaborator

Nah I don't think it did. enums as namespaces was already possible in Swift 2…

@AliSoftware
Copy link
Collaborator

Conflict to fix so we can merge 😉

@djbe
Copy link
Member Author

djbe commented Dec 1, 2016

Oh you want to do a minor release instead of patch? Sure, gimme a minute.

@AliSoftware
Copy link
Collaborator

Ah, didn't realize that this bumped to the minor version… why is that btw?

@djbe
Copy link
Member Author

djbe commented Dec 1, 2016

New feature? (new template)

@AliSoftware
Copy link
Collaborator

I mean, before this PR, people using namespaces didn't have the dot namespace in the generated code, now they will… that means breaking, right?

@djbe
Copy link
Member Author

djbe commented Dec 1, 2016

No the old workflow still works, it still generates a flat enum with all cases.

@AliSoftware
Copy link
Collaborator

Oh, sorry, you're right, I thought we already had a (non-working) dot-syntax template for Images, (I got confused with the one we already have for Strings). If that's a new template then yeah, minor it is 😉

In that case yeah, maybe we should do a patch version real quick first, before merging this and starting a new minor, right.

@djbe
Copy link
Member Author

djbe commented Dec 1, 2016

Yeah, I mean, I still have to check this works from command line 😅
-t somethingsomething to switch templates right?

@AliSoftware
Copy link
Collaborator

yup!

@djbe
Copy link
Member Author

djbe commented Dec 1, 2016

So, I think I'll wait until 4.0.1 is released before I fix the conflict in this, otherwise I'll keep having to merge changes.

@AliSoftware
Copy link
Collaborator

Yup, probably a good idea indeed 😉

Can't wait for #204 to be able to test those command line invocations right from Xcode instead of having to launch the Terminal and rake install for that 😉

(Will review #204 rn btw, it's about time we move forward on this, would help a lot the dev process)

@djbe
Copy link
Member Author

djbe commented Dec 1, 2016

Definitely! 🎉

@djbe djbe merged commit a51e943 into SwiftGen:master Dec 5, 2016
@djbe djbe deleted the feature/structured-images branch December 5, 2016 19:06
# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants