-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include a generator #3
Comments
Hey @MarcelWaldvogel nice library. I had to parse vcard data in a JS/TS Frontend and it works very well. How would the interface of such a generator look like? How would you handle |
The function I started writing has the following prototype: export function stringifyVCard(vcard: VCard4): string; I planned to ignore the nags, but you certainly have a point. Generally, I found the escaping rules in the RFC to have some ambiguity (see e..g. the So, for the single value case, I was planning to not escape commas, as old parsers would not like it escaped and even new ones probably would accept the unescaped comma, for compatibility. I never thought of using the nags to (somewhat) faithfully reproduce the original context. Right now, I would say they are too fine-granular (single property or even a single field); if some compatibility options should be made available, I would prefer them to be more general (e.g., "do not escape any single values"), as I think the goal should be to create "good" output (e.g., maximize standards compliance and compatibility, which might not be the same thing every time), and not to faithfully replicate bad input. But as I do not have a concrete use case for the generation, I am absolutely open to comments. So, the prototype might end up to be more like: export function stringifyVCard(vcard: VCard4, encodingOptions?: …): string; or even export function stringifyVCard(vcard: Omit<VCard4, "nags" | "hasErrors">, encodingOptions?: …): string; |
@lenalebt: Would you have a preference? |
I'm by no means an expert on the topic :). I read a few things in the past weeks around VCard handling and how one should export VCards, but more from a backend perspective (where I am currently using https://github.com/mangstadt/ez-vcard). Maybe you already know that resource, I found it quite useful and it has a few hints about how one should or should not implement VCard exports: https://sabre.io/dav/building-a-carddav-client/#retain-full-vcards%21 They basically say "don't try to reproduce the VCard from an internal representation, most probably you will run into problems". Having this in mind: I do have some doubts about using the nags to reproduce what was there initially, as it basically is the approach they recommend not doing. Not using them probably also would be problematic though :D. But it is based on the above article, and not on personal experience so far as I was able to circumvent implementing that myself. Full disclouse: I'm working closely with @ztiromoritz and am more of a backend person :-). |
I did not know about Sabre pointer; thanks, it confirms my intuition. (Also reminds me of a discussion with the DAVx⁵ guys a few years ago about CardDAV synchronization issues; I guess they would take a similar stance.) Thanks for taking the time to respond! |
I read the article @lenalebt mentioned above more like: "don't use your own usecase specific datamodel to recreate the vcard. If you have to write But I understand the intention of this library here as exactly that: A vCard focused datamodel. For me this concludes to:
|
FYI READ:
WRITE:
And thanks in return, this discussion is very helpfull for my current use case. |
Ah, thanks for the description of the use case (and the hoops you have to be jumping through). So, right now, you do a "minimally invasive" approach, which cannot be easily generalized. GoalsLet me propose a more general approach:
RequirementsI envision three steps along the lines to reach this goal, which I would prefer instead of reconstructing unclear or bad formatting from the tags:
I think 1 would be a good thing to have anyway and easy to implement. 2 would be good to have for anyone to modify the file and might be reasonable to implement. However, I believe the cost-benefit factor of 3 to be bad (amount of code and complexity vs. actual need to preserve this level of detail; especially given the fact that any third party relying on this behaviour violates the standard and probably should fix their code. And yes, I know that this too often is outside of our control.) What do you think? Implementation optionsFor 2 (and also for 3, at a finer granularity) I see the following options:
I like the "user-driven compromise" (if you want lines to be preserved, specify |
I would agree with your goals. My gut feel says "requirements option 2 feels sufficient" and you could still go for option 3 in case it turns out to be necessary. Wrt implementation options, to me the user-driven simple solution does not have that much appeal - but I purely am looking at it from the user's perspective. Of course it would be the easiest to implement. Would the "setter" implementation really be that complicated? To me it sounds like a natural thing to do. I'm really not deep into TypeScript land, and risking to come across as "the emperor without clothes on"...: as far as I understand https://www.typescriptlang.org/docs/handbook/2/classes.html#getters--setters for a user it would still seem like "just a bunch of data"? Of course it would be more to do wrt implementation effort. |
Required when the vCard should be output again as faithfully as possible after some modifications. See discussion in #3
AFAIK, if you want to do anything on e.g. So, during parsing, you want the real objects (otherwise, you will have to fight your own setter trying to delete the I think I prefer the "copy" approach to that. |
Understand, thanks for clarification and sorry for my ignorance maybe :). I have a few ideas about how one could still simplify usage, but I'm not sure yet whether it would really make things simpler. It's basically structured around the "lens" idea from functional programming, basically having something that will do the modification for you. It would make it possible for you to go with the simple approach, but adding a way to have the "complex" modification made for you. I'll try it out on monday with @ztiromoritz. |
Optics seems to be topic I have been running into quite frequently in the past few months and I would like to get my hands dirty with them one of these days. Therefore, I would greatly appreciate any pointers in this direction. However, I do not think anymore that the "copy" approach is that expensive: remembering the The only downside the |
BTW: What kind of API would you prefer?
The latter could help reduce storage and (browser) code size to those users who just want to parse the values, with no intention to write it back almost-as-is, but is less elegant. (If |
It turns out, storing a |
Okay, so I guess you won't need the optics version then? I'll shortly present the optics idea anyways, although I did not have a chance yet to talk to @ztiromoritz about it, just for the sake of nerd-sniping you into it :D. I have not used the typescript libs here and seldomly code typescript (I'm more at home in Scala / Kotlin), trying to write it down - take it rather as pseudocode here. This is taken from https://github.com/gcanti/monocle-ts/blob/master/README.md where I was linked from your optics reference. import { Lens } from 'monocle-ts'
const name = Lens.fromPath<Employee>()(['company', 'address', 'street', 'name']);
const capitalizeName = name.modify(capitalize);
capitalize(employee) The return value of the last one would be a changed object. Now apply a slight modification (and yep, it's mutable over here, this is not what lenses are made for, just to get the idea across): import { Lens } from 'monocle-ts'
const companyLens = new Lens<Employee, Company>
employee => employee.company,
company => employee => {
employee.company = company;
employee.rawLine = null;
} )
);
const companyNameLens = Lens.fromPath<Company>()(['address', 'street', 'name']);
const nameLens = companyLens.compose(companyNameLens);
nameLens.set("Foostreet"); I seldomly code TypeScript, and just have written this down inline in this editor, so sorry if it does not compile. You could now just hide that lens API and make an own let vcard = //....
MyLens.fromPath<VCard>()(['address', 'street', 'name']).set("Foostreet")(vcard) This is how you'd use one of those lenses. My idea basically was to not use proxy objects everywhere, but just have a lens for the very first object level that would not only change the value, but also remove that |
Thank you very much for the inspirational writeup, which helped me understand Lenses better (e.g., I had forgotten about the immutable part). Do I understand it right lenses are essentially a comfortable way to achieve copy-on-write (CoW) semantics in structured data? So, if the code wanted to be notified about changes, we either would need to make the current data structure read-only ( The Lens approach in TS/JS has the (syntactical) disadvantage that you can no longer simply write ⌚ [Time passes] After brooding over the CoW concept, how about the following:
So, the fields can still be accessed (read-only) as @ztiromoritz, @lenalebt: What do you think? (And many, many thanks for the inspiration!) |
Would have been too good to be true. The creation of new/replacement properties would now be like Options:
|
A (failed) attempt at solving #3
Hi everyone, |
Maybe generate a
text/vcard
representation out of the structureThe text was updated successfully, but these errors were encountered: