-
Notifications
You must be signed in to change notification settings - Fork 737
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
Images: dot syntax #206
Conversation
* 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]? |
There was a problem hiding this comment.
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])
}
}
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]]? { |
There was a problem hiding this comment.
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 ^^)
There was a problem hiding this comment.
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] { |
There was a problem hiding this comment.
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]] { |
There was a problem hiding this comment.
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]] { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 } |
There was a problem hiding this comment.
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"
}
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
}
}
There was a problem hiding this 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)) |
There was a problem hiding this comment.
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
)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
👍 |
There was a problem hiding this 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)) |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static let Orange: AssetType = "Round/Orange" | ||
|
||
struct Red { | ||
static let Apple: AssetType = "Round/Apple" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
BTW, I based the templates on the strings dot syntax. Any reason why we use nested |
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. |
Did this maybe depend on swift 3? |
Nah I don't think it did. enums as namespaces was already possible in Swift 2… |
Conflict to fix so we can merge 😉 |
Oh you want to do a minor release instead of patch? Sure, gimme a minute. |
Ah, didn't realize that this bumped to the minor version… why is that btw? |
New feature? (new template) |
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? |
No the old workflow still works, it still generates a flat enum with all cases. |
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. |
Yeah, I mean, I still have to check this works from command line 😅 |
yup! |
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. |
Definitely! 🎉 |
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.