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
Add Document visitor #190
Comments
I'm starting to think that the preferential order of stringification types needs some more unifying option shape. Adding options one by one is going to produce even more of a mess than it is now. So, given that we've got a major release upcoming in any case, we can reconsider everything here. At least these controllable facets come to mind off the top of my head:
Suggestions are welcome for how to handle at least all of that, and for other aspects that should be controllable. My preference in these sorts of cases is usually for whatever option requires the least explanation. I don't think absolutely everything needs to be possible, just... most things. |
Thank you for your response. I'll describe my thoughts about this below. default type for keys & customizable escalation pathI think these cases are very specific and probably not that important for most users. I think, it would be okay to process it manually. I actually ended up doing this for my case: https://codesandbox.io/s/infallible-brook-3vxop?file=/src/index.ts .
yaml.stringify(data, {
replacer: (node, ctx) => {
if (node.key == 'key 1' && node instanceof Scalar) {
// change value and type based on key
node.value = 'specific value';
node.type = Type.BLOCK_LITERAL;
} else if (node instanceof Scalar && typeof node.value == 'string') {
// change type for every string
node.type = Type.BLOCK_LITERAL;
} else {
ctx.forEachDescendant(node);
}
}
}); It is probably too imperative, but it can achieve relatively easy overriding and/or implemeting any behavior. It is just impossible to add parameter for every possible case, but this will probably cover all of them. default type for multiline strings & preference between single/double quoted & preference between folded/literal blocksThese options can fit neatly as fields for options object. This looks perfectly fine: {
multilineStrings: {
//** Specifies what is "starting point" in escalation path. */
defaultType: Type,
//** use specified quote unless other one will require no escapes (or by using other, more complex policy) */
quotePreference: 'double' | 'single' |
{
type: 'double' | 'single',
//** if true, prefer specified quote even when escapes will be needed */
forceAlways: boolean
},
//** Whether or not use folded blocks*/
autoFolding: boolean
}
} which of the string types are even allowedI don't see way to add this option without it beign too complex both for API and implementation. And frankly, using value combinations from previous paragraph it should be possible to achieve quite a lot of endresults. |
This is a bit tricky, because the scalar options apply to tags, which are included in the schema, which is used by the document. Adding an override for them to document options (i.e. what's also give to
In v1, the function you're looking for is
PR #189 adds a JSON replacer function, but that only really applies to JS objects, while they're being turned into a document or a node. Something like a visitor helper function in addition to that does sound like a really rather good idea, though.
Could you clarify what you mean by
I think I'm looking for some very small set of options that could cover a lot of ground. Possibly with an alternative function form that'd have access to the stringification context object for determining the preferences; it already includes at least type StringType = 'BLOCK_FOLDED' | 'BLOCK_LITERAL' | 'PLAIN' | 'QUOTE_DOUBLE' | 'QUOTE_SINGLE'
interface StringifyOptions {
prefer: StringType[]
allow: { [key in StringType]: boolean }
}
interface StringifyContext {
implicitKey: boolean
inFlow: boolean
...
}
interface ScalarOptions {
stringifyScalar: StringifyOptions | ((ctx: StringifyContext, value: string) => StringifyOptions)
...
} There |
Am I understanding it correctly? You want document options to not have nested objects? Why so? For me it seems pretty convenient to group properties like that.
That's great! I had to read manual more closely before proposing changes, sorry...
I can implement this feature in PR if you interested.
In my mind this parameter would determine whether or not fold string when using folded block scalar ( Input: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum dapibus congue quam, eleifend consequat enim faucibus id. Ut luctus mi quis eleifend fermentum."
#autoFolding == true
Value: >-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum dapibus congue quam,
eleifend consequat enim faucibus id. Ut luctus mi quis eleifend fermentum.
#autoFolding == false
Value: >-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum dapibus congue quam, eleifend consequat enim faucibus id. Ut luctus mi quis eleifend fermentum.
I'm not sure about As for |
Yes. I've managed to keep the main options flat so far, and I'd prefer to do so in the future as well. That makes it significantly easier to grasp how the options are merged.
Sure. My first idea for this was something like
So specifically for folded block scalars? Because there's already
Just to clarify, YAML has two node types: collections and scalars. Those stringify very differently, hence the initial name
Mostly something like
Easiest fix for that is to not allow disabling the double-quoted style, as that's the only one that can represent all strings. |
I was thinking about something more akin to ts-morp traversal. Like this: interface TraversalContext {
path: Node[],
skip: () => void,
up: () => void,
stop: () => void
}
// will traverse recursively through all nodes (scallar and collections)
// - if returns Node, current node will be overriden
// - if returns void, same node will be used (changes from visitor will be retained)
// - unless traversal control function will be called, by default, library will iterate all
// child nodes of current node and then proceed to next sibling.
type VisitorArg = (node: Node, traversal: TraversalContext) => Node | void; I like having some control over traversal, so the visitor will not process every single node and can exit early.
Well, my initial idea was so it would prevent using
I thought that this function is a replacement for the visitor proposed earlier. And in this case, the user will not be able to change collection nodes by using
What I'm trying to say, that it isn't clear nor customizable from the code what rules are applied to "escalate" to the next type in a chain. And this also can be overridden by
Random idea: if an application is performing workflow akin to read-edit-(re)write, the library could store state type of scalars and use them by default, but still be able to "escalate" to the next type if necessary. |
Yeah, that makes sense. The ts-morph API is a good starting point, but I think the action triggered by returning something other than
That would allow for a pretty compact way of both using My gut feeling is that implementing 2. would cause more problems than it's worth. And that 4. would mean that the visitor needs access to the document instance. So the signature would need to be something like one of the following:
I think we do want both. They serve different use cases, though there is overlap: the config defines the general case, while the visitor does specific actions on individual nodes.
And that would be why having two different ways of achieving the same result would be good in this case.
That already happens: const doc = YAML.parseDocument("'foo'")
String(doc) === "'foo'\n"
doc.contents.value = 'foo\n bar'
String(doc) === '"foo\\n bar"\n' |
Sure, it will not do anything extra with node. But all mutations from visit will retain.
I don't think we really need it in this case. The goal of this visitor is to mutate nodes tree rather than to find and return some specific node as it was in ts-morph example. I think, if visitor returns node, library should replace current node with it, and then traverse through its children (unless visit: (node, traversal, doc) => {
if (node.key == 'keyToChange') {
// current node will be replaced and traversal ends
traversal.stop();
return doc.createNode('replaced value');
}
}
I think if visitor API will provide a reference to
Yep, adding visit to document itself is good idea. That will allow to easily switch multiple descending visitors. I would suggest supporting following usage: visit: (node, traversal, doc) => {
if (node.key == 'KeyToChange') {
doc.visit(node, nestedVisitor);
traversal.stop();
}
// traversal.stop() isn't called, continue traversalf through nodes
} So what do you think about this signature? // definition for Document.visit function
interface DocumentVisitFn {
// resolving root for visiting by path or node reference
(path: (string | number)[] | Node, visitor: VisitorFn) => Document;
// traverse starting from document root node
(visitor: VisitorFn) => Document;
}
Nice! Good job! |
BREAKING CHANGE: The public method is no longer available, as its internal use for it is being refactored to StreamDirectives. An alternative pattern will need to be documented for any current users, once the visitor API is available to use as a base for it. (#190)
Hi.
I need option to make all multiline strings as block literals (using
|-
instead of>-
). Would you think it will make sense? I can create PR for that.The text was updated successfully, but these errors were encountered: