Skip to content
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

Tree refactoring and basic text writing #90

Merged
merged 12 commits into from
Jan 15, 2022

Conversation

NightEule5
Copy link
Collaborator

No description provided.

Created the TomlTextWriter interface to handle low-level writing of Toml
files. One implementation writes to a StringBuilder, the other writes to
an Okio BufferedSink. KtomlConf has also been amended to include an
"indentation" property.
This interface roughly corresponds to the kotlinx-serialization.json
library's Composer interface in function, so the name was changed to
reflect that. This also clears up potential confusion with TomlWriter
and TomlFileWriter, which will have a different purpose.
Classes deriving from TomlValue now expose their content property in an
internal constructor, and parsing was moved to a secondary constructor.
Since member functions are inaccessible before the super constructor is
called, private functions used for parsing were moved to companion
objects.
@NightEule5 NightEule5 linked an issue Jan 10, 2022 that may be closed by this pull request
@NightEule5
Copy link
Collaborator Author

NightEule5 commented Jan 10, 2022

Hmm, not off to a good start with the workflows

I'll fix the formatting

@NightEule5
Copy link
Collaborator Author

Alright, fixed. Sorry about that

@orchestr7
Copy link
Owner

orchestr7 commented Jan 10, 2022

@NightEule5 that's fucking awesome! Thank you so much
Give us several days to review

@NightEule5
Copy link
Collaborator Author

Thanks! :)

Sure, no rush

@Peanuuutz
Copy link
Contributor

Peanuuutz commented Jan 10, 2022

Nice work. :D

A little suggestion, consider matching the type of content for each TomlValue.

BTW, I didn't find any actual class for tables. I may try to implement inline ones first. Shall I go with this thread or open a new PR? Also, should I defer inline ones and not inline ones? They behave quite differently. 🤔

@orchestr7
Copy link
Owner

BTW, I didn't find any actual class for tables. I may try to implement inline ones first. Shall I go with this thread or open a new PR? Also, should I defer inline ones and not inline ones? They behave quite differently. 🤔

Hey! You can open a new PR, but I suppose that we will need to merge this huge PR first…
Is it ok for you?

@Peanuuutz
Copy link
Contributor

Hey! You can open a new PR, but I suppose that we will need to merge this huge PR first...

I'm OK with that. I'll follow up when this is done. :D

@NightEule5
Copy link
Collaborator Author

Nice work. :D

Thanks :)

A little suggestion, consider matching the type of content for each TomlValue.

Yep, I left it how it was for now. Constraining the type on an overridden var property gives an error, and I didn't try much yet to make that work

@orchestr7
Copy link
Owner

Any chances that you will provide any tests that will cover this functionality? :)

Copy link
Owner

@orchestr7 orchestr7 left a comment

Choose a reason for hiding this comment

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

As it does not affect decoding logic - we can merge it, but let's discuss comments, that I have written. It also will be great if you will add more comments to the code and give a small overview on your plan :)

val escapedQuotesInLiteralStringsAllowed: Boolean = true
)
val escapedQuotesInLiteralStringsAllowed: Boolean = true,
val indentation: Indentation = FOUR_SPACES
Copy link
Owner

Choose a reason for hiding this comment

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

Could you please add a trailing comma here, so we will avoid 2-lines changes in the future?

@@ -24,6 +28,7 @@ public open class Toml(
) : StringFormat {
// parser is created once after the creation of the class, to reduce the number of created parsers for each toml
public val tomlParser: TomlParser = TomlParser(config)
public val tomlWriter: TomlWriter = TomlWriter(config)
Copy link
Owner

Choose a reason for hiding this comment

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

Could you please add some comment to this writer for newbies?

Copy link
Collaborator Author

@NightEule5 NightEule5 Jan 12, 2022

Choose a reason for hiding this comment

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

Sure. Perhaps editing the comment above tomlParser to apply to both the parser and the writer would suffice? Or do you mean description of its function?

/**
* @property string
*/
public enum class Indentation(public val string: String) {
Copy link
Owner

Choose a reason for hiding this comment

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

may be call it value? string looks a little bit confusing, usually Java/Kotlin developers expect toString() or value

/**
* Abstracts the specifics of writing TOML files into "emit" operations.
*/
public interface TomlComposer {
Copy link
Owner

Choose a reason for hiding this comment

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

Why do we need an interface if it is inherited only in one abstract class now? Any future plans?
I suggest to merge it with AbstractEmmiter if it is not used anywhere else

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No specific plans for it, I just thought this way was best practice. I can merge it if that's preferable

Copy link
Owner

Choose a reason for hiding this comment

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

Yes, it is a best practice when it has common sense. But here we interface it "just because" 😄
Better to merge, so the project won't be too huge to read for new developers

/**
* The based implementation of [TomlComposer].
*/
public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer {
Copy link
Owner

Choose a reason for hiding this comment

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

Are you sure that Composer is a right name? I have seen Emitter in projects like 'snakeyaml'. What do you think, does it sound better?

Or you are trying to make it similar to kotlinx.Json?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, that was the idea. Emitter is probably a more fitting and clear name though, so I'll change it

public fun write(file: TomlFile, composer: TomlComposer): Unit =
file.children.forEach { composer.writeChild(it) }

private fun TomlComposer.writeChild(node: TomlNode) = when (node) {
Copy link
Owner

Choose a reason for hiding this comment

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

You plan to insert Toml nodes to the Tree here? What is your plan on this code?
You plan to take an object -> create tree -> then emit string or file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, the plan here is to emit a tree that was created before to the Composer (Emitter), which then writes to a StringBuilder or BufferedSink depending on the implementation

protected abstract fun emit(fragment: Char)

protected abstract fun emit(fragment1: String, fragment2: String)
protected abstract fun emit(
Copy link
Owner

Choose a reason for hiding this comment

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

what is the user scenario for that bunch of emit methods? Do we really need all of them? Why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The use case was for parts of the file that would need multiple emit calls at once, like strings (a quote, the value, another quote), comments, and indentation. In hindsight it'd be better to simplify this into two abstract emits, char and string

val ktomlPath = tomlFile.toPath()
return getOsSpecificFileSystem().sink(ktomlPath).buffer()
} catch (e: FileNotFoundException) {
println("Not able to fine toml-file in the following path: $tomlFile")
Copy link
Owner

Choose a reason for hiding this comment

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

fine -> find


private fun TomlComposer.writeChild(node: TomlNode) = when (node) {
is TomlFile ->
error(
Copy link
Owner

Choose a reason for hiding this comment

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

We should have proper exception instead of error here...

val keys = key.keyParts

if (keys.isEmpty()) {
emitQuotedKey("")
Copy link
Owner

Choose a reason for hiding this comment

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

keys can have a single quote also...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, that's when the key is empty

@NightEule5
Copy link
Collaborator Author

Any chances that you will provide any tests that will cover this functionality? :)

Yes, that's the plan :)

I can do that in a later PR since this one got too large

It also will be great if you will add more comments to the code and give a small overview on your plan :)

Sure, I'll add more documentation

My plan is to encode to the AST in the Encoder and CompositeEncoder implementation(s), then emit the AST via the appropriate Emitter. TomlWriter would traverse the tree, calling the necessary emit functions instead of writing the file or string directly.

At some point we could make the AST @Serializable like Kotlinx Json does, but that's beyond the scope here I think

@Peanuuutz
Copy link
Contributor

Peanuuutz commented Jan 12, 2022

My plan is to encode to the AST in the Encoder and CompositeEncoder implementation(s), then emit the AST via the appropriate Emitter. TomlWriter would traverse the tree, calling the necessary emit functions instead of writing the file or string directly.

For further development, comments in TOML file should be considered. I personally think annotation(such as @Comment) on properties is a good practice(then via descriptor.getElementAnnotation(index) to emit comments), but if models are encoded to AST, these annotations may be dicarded. Any solution to this? 🤔

OK I'm just trying to copy what I have done in my implementation

@NightEule5
Copy link
Collaborator Author

NightEule5 commented Jan 13, 2022

For further development, comments in TOML file should be considered. I personally think annotation(such as @Comment) on properties is a good practice(then via descriptor.getElementAnnotation(index) to emit comments), but if models are encoded to AST, these annotations may be dicarded. Any solution to this?

Yes, I think this is a good idea. We'll have to add comments to the AST later

Other annotations would be good too, like @Multiline and @Literal for strings, @Integer(representation=...) for longs, and @Inline for classes and maps

@orchestr7
Copy link
Owner

orchestr7 commented Jan 14, 2022

@NightEule5

Other annotations would be good too, like @Multiline and @Literal for strings, @Integer(representation=...) for longs, and @Inline for classes and maps

could you please describe this Annotation logic here: #79

And let's merge this PR when you are ready. But please use Squash and merge.

image

@NightEule5 NightEule5 merged commit d953779 into orchestr7:main Jan 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support encoding of toml format
3 participants