Repository: platers/obsidian-linter (ts)

Issue:
FR:  add a option that if YAML-title could be surrounded by " token or ' token
## Is Your Feature Request Related to a Problem? Please Describe.

Hugo static website generater needs frontmatter YAML to be like `title: "xxxx"`, but now linter only support add title section like `title: xxxx` and can not choose if add " or ' token .

now: 
``` markdown
---
title: README
---
```

supposed:
``` markdown
---
title: "README"
---
```

Pull request description:
Add Ability to Escape Yaml Title Key
Fixes #400 

Changes Made:
- Added tests to cover some common scenarios for title formats
- Added the options of `None`, `Single Quote`, and `Double Quote` to allow for the selection of the desired title key escape character

Patch:
diff --git a/docs/rules.md b/docs/rules.md
index 4ca1b6f..d7627a5 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -585,6 +585,11 @@ Inserts the title of the file into the YAML frontmatter. Gets the title from the
 Options:
 - Title Key: Which YAML key to use for title
 	- Default: `title`
+- Yaml Escape Character: Specifies what character to put around the Title Key Yaml value if it has not already been escaped
+	- Default: `None`
+	- `None`: title: Title Here
+	- `Single Quote`: title: 'Title Here'
+	- `Double Quote`: title: "Title Here"
 
 Example: Adds a header with the title from heading.
 
diff --git a/src/rules/yaml-title.ts b/src/rules/yaml-title.ts
index 7d6c970..cb2b193 100644
--- a/src/rules/yaml-title.ts
+++ b/src/rules/yaml-title.ts
@@ -1,16 +1,19 @@
 import {Options, RuleType} from '../rules';
-import RuleBuilder, {ExampleBuilder, OptionBuilderBase, TextOptionBuilder} from './rule-builder';
+import RuleBuilder, {DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase, TextOptionBuilder} from './rule-builder';
 import dedent from 'ts-dedent';
 import {formatYAML, initYAML, toYamlString} from '../utils/yaml';
 import {ignoreListOfTypes, IgnoreTypes} from '../utils/ignore-types';
 import {escapeDollarSigns} from '../utils/regex';
 import {insert} from '../utils/strings';
 
+type yamlTitleEscapeOptions = 'None' | 'Single Quote' | 'Double Quote';
+
 class YamlTitleOptions implements Options {
   @RuleBuilder.noSettingControl()
     fileName: string;
 
   titleKey?: string = 'title';
+  yamlEscapeCharacter?: yamlTitleEscapeOptions = 'None';
 }
 
 @RuleBuilder.register
@@ -36,10 +39,26 @@ export default class YamlTitle extends RuleBuilder<YamlTitleOptions> {
       }
       return '';
     });
+
     title = title || options.fileName;
 
     title = toYamlString(title);
 
+    const ensureSpecifiedCharacterStartsTitleIfNotAlreadyEscaped = function(title: string, characterToStartAndEndWith: string, characterToRemoveFromStartAndEnd: string) {
+      if ((title.startsWith(characterToRemoveFromStartAndEnd) && title.endsWith(characterToRemoveFromStartAndEnd)) ||
+      (title.startsWith(characterToStartAndEndWith) && title.endsWith(characterToStartAndEndWith))) {
+        return title;
+      }
+
+      return characterToStartAndEndWith + title + characterToStartAndEndWith;
+    };
+
+    if (options.yamlEscapeCharacter == 'Single Quote') {
+      title = ensureSpecifiedCharacterStartsTitleIfNotAlreadyEscaped(title, '\'', '"');
+    } else if (options.yamlEscapeCharacter == 'Double Quote') {
+      title = ensureSpecifiedCharacterStartsTitleIfNotAlreadyEscaped(title, '"', '\'');
+    }
+
     return formatYAML(text, (text) => {
       const title_match_str = `\n${options.titleKey}.*\n`;
       const title_match = new RegExp(title_match_str);
@@ -98,6 +117,26 @@ export default class YamlTitle extends RuleBuilder<YamlTitleOptions> {
         description: 'Which YAML key to use for title',
         optionsKey: 'titleKey',
       }),
+      new DropdownOptionBuilder({
+        OptionsClass: YamlTitleOptions,
+        name: 'Yaml Escape Character',
+        description: 'Specifies what character to put around the Title Key Yaml value if it has not already been escaped',
+        optionsKey: 'yamlEscapeCharacter',
+        records: [
+          {
+            value: 'None',
+            description: 'title: Title Here',
+          },
+          {
+            value: 'Single Quote',
+            description: 'title: \'Title Here\'',
+          },
+          {
+            value: 'Double Quote',
+            description: 'title: "Title Here"',
+          },
+        ],
+      }),
     ];
   }
 }


Test patch:
diff --git a/__tests__/yaml-title.test.ts b/__tests__/yaml-title.test.ts
index 608fc3a..57cfad5 100644
--- a/__tests__/yaml-title.test.ts
+++ b/__tests__/yaml-title.test.ts
@@ -65,5 +65,53 @@ ruleTest({
         # Very long title 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
       `,
     },
+    {
+      testName: 'Escaping with single quotes works',
+      before: dedent`
+        # This is a title
+      `,
+      after: dedent`
+        ---
+        title: 'This is a title'
+        ---
+        # This is a title
+      `,
+      options: {
+        fileName: 'testFile',
+        yamlEscapeCharacter: 'Single Quote',
+      },
+    },
+    {
+      testName: 'Escaping with double quotes works',
+      before: dedent`
+        # This is a title
+      `,
+      after: dedent`
+        ---
+        title: "This is a title"
+        ---
+        # This is a title
+      `,
+      options: {
+        fileName: 'testFile',
+        yamlEscapeCharacter: 'Double Quote',
+      },
+    },
+    {
+      testName: 'Escaping with double quotes does not come into affect when already escaped with single quotes',
+      before: dedent`
+        # "This is a title
+      `,
+      after: dedent`
+        ---
+        title: '"This is a title'
+        ---
+        # "This is a title
+      `,
+      options: {
+        fileName: 'testFile',
+        yamlEscapeCharacter: 'Double Quote',
+      },
+    },
   ],
 });