3

I am trying to create a VS code snippet that scaffolds the namespace based on the current folder in a project.

The current path provied by the TM_DIRECTORY variable could be something like this.

/Users/bernhardrichter/GitHub/heatkeeper2000/src/HeatKeeper.Server/Mapping

What I would like to end up with is namespace HeatKeeper.Server.Mapping based upon my root source folder being src

So I need to strip away everything before and including src so that we are left with HeatKeeper.Server/Mapping. And then I need to replace(transform) the / into . so that the final result is HeatKeeper.Server.Mapping.

Is it possible to do this in a single transform? If not is it possible to have multiple transforms?

This is what I have so far

"namespace ${TM_DIRECTORY/(.*src.)(.*).*$/$2/}"

This outputs namespace HeatKeeper.Server/Mapping which is almost what I want. I just need to replace all / with .

The problem is that I don't know where to put this transform.

The transform looks like this.

"${TM_DIRECTORY/[\\/]/./g}"

which gives me

.Users.bernhardrichter.GitHub.heatkeeper2000.src.HeatKeeper.Server.Mapping

I just don't know how to combine these two?

3 Answers 3

5

See after the edit for a more general (not just the last two directories) answer.


Original answer:

Yes, you can do them in one snippet, you just need to separately capture the two directories after src. Try:

  "namespace ${TM_DIRECTORY/.*src\\/(.*)\\/(.*)$/$1.$2/}",

Then put a period between the two capture groups. This regex assumes you always have a src directory preceding the two directories that you want. If that isn't the case, this will work capturing the last two directories:

  "namespace ${TM_DIRECTORY/.*\\/(.*)\\/(.*)$/$1.$2/}",

Note that the path separators "/" must be double-escaped.

EDIT ------------------------ see below -----------------------------------------------

For the more general case of some unknown number of directories after a specific directory, try this form:

"body": "${TM_DIRECTORY/.*src\\/(([^\\/]*)(\\/)?)|(\\/)([^\\/]*)/$2${3:+.}${5:+.}$5/g}",

// here for easier testing using the clipboard
"body": "${CLIPBOARD/.*src\\/(([^\\/]*)(\\/)?)|(\\/)([^\\/]*)/$2${3:+.}${5:+.}$5/g}",

Where src appears, put the last directory you do not want. This works for 1 or more directories after src (in this case).

interestingly, it was the one folder case that was the trickiest. That was solved by the extra conditional ${3:+.} which means if there is a capture group 3, insert a ..

For the regex explanation, see reg101 demo. At that link you can see that a simpler substitution works except for the one folder case. If you don't mind just backspacing over the that last . you could use the substitution $2.$5 instead of $2${3:+.}${5:+.}$5 I used above.

Finally, note that the above were designed to work with forward slash / path separators. To modify it to use either back/forward slash separators, look at this (with USERS as the last unwanted directory):

   "body": "${TM_DIRECTORY/.*Users[\\/\\\\](([^\\/\\\\]*)([\\/\\\\])?)|([\\/\\\\])([^\\/\\\\]*)/$2${3:+.}${5:+.}$5/g}",

All forward slashes \\/ get converted to the alternative forward and backslashes [\\/\\\\] where backslashes must appear as \\\\ if you want to test for a literal \. Yow.

[For the original question, just add namespace to the beginning.]

Sign up to request clarification or add additional context in comments.

4 Comments

What if the number of directories after src is not fixed (eg could be 1 to 10), is there a way to make this generic?
@Alex I think I made it for the general 1 to infinity case. Please test it and let me know.
That worked! Thank you very much! I'm using it to set the package correctly for new Java classes.
Adapted version for generating Magento 2 class namespace: ${TM_DIRECTORY/.*app\\/code\\/(([^\\/]*)(\\/)?)|(\\/)([^\\/]*)/$2${3:+\\\\}${5:+\\\\}$5/g}
0

Sadly @Marks solution didn't really work for me, it still contained the drive and all folders up to the filename or if i used the new ${RELATIVE_FILEPATH} it still contained the filename (i wanted the complete path without the workspace folder, so basically a project path inside a c#-project for example) luckily with the new relative filepath variable we can remove some of the complexity around the substitution and capture groups ($3, $5 and such) and the RegEx itself.

tinkering with the first answer and an other post from @Mark i came to the following solution:

"body": [
    "namespace ${RELATIVE_FILEPATH/^([^\\\\\\/]+)[\\\\\\/]|([^\\\\\\/]+)[\\\\\\/]|(?:[^\\\\\\/]+\\.[^\\\\\\/]+)/$1${2:+.}$2/g};",
    "",
    "internal ${1|enum,interface,sealed class,sealed record,record struct|} $TM_FILENAME_BASE",
    "{",
    "    $0",
    "}"
],

(for bit of testing see regex101.com not 100% accurate to how VSCode seems to do it, but oh well)

... that still looks kinda atrocious, i know, but let me simplify it a bit and break it down

first, for simplicity, we don't use \ and / as a separator, but # instead and remove the double escape the snippet needs. That lands us at:

  • the RegEx: ^([^#]+)#|([^#]+)#|(?:[^#]+\.[^#]+)
  • the snippet substitution part: $1${2:+.}$2

further we can then break the RegEx up in its 3 alternatives |:

  1. ^([^#]+)#
  2. ([^#]+)#
  3. (?:[^#]+\.[^#]+)

see how the first and second one are really similar ? thats because both, in essence, capture our folder parts, but the first one looks for the very first folder

  • ^([^#]+)# looks for the start of the string(^), then in a capture group (( )) for more then one non-# symbols ( [^#]+ ) followed by exactly one # => with this we get the very first folder in capture group $1 without any #'s

  • ([^#]+)# is exactly the same with out the start-of-string part => everything found here lands in capture group $2 with out the separator #

  • (?:[^#]+\.[^#]+) has a non-capturing capture-group ((?: ... )), that looks for more than one non-# symbol ([^#]+) followed by exactly one . (\.) followed again by more then one non-# symbol ([^#]+) => with this we capture the filename and its extension, even allowing extra . in the filename, and throw it away

lastly we use the format part of the transform (see here) to stitch our output together:

  • take the first capture group ($1), write it as is
  • if the second capture group is not empty, write a dot (${2:+.}) (gets repeated for each time we match the group)
  • add the second capture group ($2) (gets repeated for each time we match the group)

with this we don't have a dot (or multiple) at the end, removed the filename with extension (regardless of how many . it contained) and replaced all #(or in the original case any \ or /) with a .

i think i put >24 hours of work in my free time into this (man do i hate RegEx), could i have spend them writing code instead ? sure, but sometimes there is fun following your perfectionism and tackling a challenge ;)

Comments

0

You can use the following snippet :

"Guess Namespace": {
    "prefix": "namespace-auto",
    "body": [
        "namespace $WORKSPACE_NAME.${RELATIVE_FILEPATH/^([^\\\\\\/]+)[\\\\\\/]|([^\\\\\\/]+)[\\\\\\/]|(?:[^\\\\\\/]+\\.[^\\\\\\/]+)/$1${2:+.}$2/g}.$TM_FILENAME_BASE;",
    ],
    "description": "namespace root.<dir..>.filename_base;"
}

Example Outputs

  • File: ~Project\Controller\HomeController.cs
    Output:

    namespace Project.Controller.HomeController;
    
  • File: ~Project\src\Controller\HomeController.cs
    Output:

    namespace src.Controller.HomeController;
    
  • File: ~Project\src\Product\Controller\HomeController.cs
    Output:

    namespace src.Product.Controller.HomeController;
    

How it Works

  1. ${WORKSPACE_NAME}: Inserts the name of your project workspace.

  2. ${RELATIVE_FILEPATH}: Extracts the file path relative to the workspace folder.

  3. Regex Explanation:

    • ^([^\\\\\\/]+)[\\\\\\/]|([^\\\\\\/]+)[\\\\\\/]|(?:[^\\\\\\/]+\\.[^\\\\\\/]+):
      • Breaks the file path into hierarchical segments by recognizing slashes (/ or \).
      • The regex iteratively adds each segment as part of the namespace, excluding file extensions and workspace prefixes.
    • $1${2:+.}$2: Combines captured groups to build a valid namespace.
  4. ${TM_FILENAME_BASE}: Uses the file name without extension to finalize the namespace.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.