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

Dedent multi-line strings #1838

Open
jmoore34 opened this issue Jan 23, 2025 · 1 comment
Open

Dedent multi-line strings #1838

jmoore34 opened this issue Jan 23, 2025 · 1 comment

Comments

@jmoore34
Copy link

Feature Request

Is your feature request related to a problem? Please describe:
Currently, multi-line strings are frustrating to use when the value is indented. Namely, in the following, notNested and nested have different values:

notNested = """\
import os

for filename in os.listdir("/path/to/dir/"):
    print filename
"""

a = {
  nested = """\
           import os

           for filename in os.listdir("/path/to/dir/"):
             print filename
           """
}

This creates some problems:

  • You can't easily move a multi-line value around to a different part of the config that has a different indentation without resulting in a different value
  • You can decide to forgo indentation altogether within the multi-line string, but this is quite an eyesore and is jarring to read in deeply nested values since you have to suddenly scroll all the way to the left.
some = {
       nested = {
            config = {
                           timeout = 5
                           interpreter = "python3"
                           script = """\
import os

for filename in os.listdir("/path/to/dir/"):
    print filename
"""
                           mode = 0640
                           }
                     }
                } 
       }

Describe the feature you'd like:

KDL I think takes a pretty good approach to solve this problem:

Both types of quoted string can be written across multiple lines by using triple quotes (""") followed immediately by a newline. Additionally, common indentation shared with the line containing the closing quotes will be stripped/dedented:

string """
  my
    multiline
  value
  """

Hence, the above example could be something like:

some = {
       nested = {
            config = {
                           timeout = 5
                           interpreter = "python3"
                           script = """\
                                import os

                                for filename in os.listdir("/path/to/dir/"):
                                    print filename
                                """
                           mode = 0640
                           }
                     }
                } 
       }

I think the behavior of using the indentation of the the closing """ is intuitive enough to be a default; however, to avoid a breaking change, it could
also work to reserve syntax for it, such as some prefix denoting the behavior (similar to "r" for raw), e.g.

                           script = i"""\
                                import os

                                for filename in os.listdir("/path/to/dir/"):
                                    print filename
                                """

Or alternatively with a different syntax entirely (e.g. YAML-inspired | syntax).

Another alternative is to take the approach Kotlin uses, i.e. to add a function like trimIndent which detects and removes a common minimal indent from all lines. For example, the following would be the same:

                           script = """\
                                import os

                                for filename in os.listdir("/path/to/dir/"):
                                    print filename""".trimIndent()
same as:

script = i"""\
import os

for filename in os.listdir("/path/to/dir/"):
  print filename""".trimIndent()"""

Describe alternatives you've considered:

  1. Manually dedent multiline strings. As mentioned before, this is an eyesore when the string's key is indented
  2. Define the string as a top-level variable and reference it in the nested key. Besides adding cognitive overhead, this is limited in that it won't work as well when you're interpolating values, within functions, etc.
  3. Use manual string concatenation, i.e.:
                       script = "import os\n" + \
                                "\n" + \
                                ...

This makes e.g. pasting in a script much more tedious than it needs to be.

Teachability, Documentation, Adoption, Migration Strategy:

The use case is any multiline string where an indentation artifact caused by the indentation of the config is not desired. Though an indentation artifact sometimes does not matter e.g. in shells that ignore leading indentation, it does cause issues in indentation-sensitive languages as well as anything user-facing (e.g., setting an environment variable to a multi-line message that is shown to the user).

Migration: Changing the semantics of """ in place would not be a breaking change so long as the ending """ is not indented.

However, migration altogether could be avoided if one of the other approaches is added, i.e. reserving syntax or adding a function.

@Peefy
Copy link
Contributor

Peefy commented Jan 24, 2025

Thank you for your grammar suggestion, it is a feature worth considering. Before implementing this feature, you can use the outdent module, https://github.com/kcl-lang/modules/tree/main/outdent

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

No branches or pull requests

2 participants