Repository: platers/obsidian-linter
Pull request #266 (2022-07-08 23:09:11)

Sort Yaml
Adds the ability to sort yaml keys.


Patch:
diff --git a/README.md b/README.md
index 5422b9e..12018f7 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,7 @@ Documentation for all rules can be found in the [rules docs](https://github.com/
 - [yaml-title](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#yaml-title)
 - [yaml-title-alias](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#yaml-title-alias)
 - [escape-yaml-special-characters](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#escape-yaml-special-characters)
+- [yaml-key-sort](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#yaml-key-sort)
 
 ### Heading rules
 
diff --git a/docs/rules.md b/docs/rules.md
index f4706ac..9b62235 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -401,6 +401,133 @@ nestedArray2: [["value: with colon in the middle"], "value with ' a single quote
 _Note that escaped commas in a YAML array will be treated as a separator._
 ``````
 
+### YAML Key Sort
+
+Alias: `yaml-key-sort`
+
+Sorts the YAML keys based on the order and priority specified. Note: removes blank lines as well.
+
+Options:
+- YAML Key Priority Sort Order: The order in which to sort keys with one on each line where it sorts in the order found in the list
+	- Default: ``
+- Priority Keys at Start of YAML: `YAML Key Priority Sort Order` is placed at the start of the YAML frontmatter
+	- Default: `true`
+- YAML Sort Order for Other Keys: The way in which to sort the keys that are not found in the `YAML Key Priority Sort Order` text area
+	- Default: `None`
+	- `None`: No sorting other than what is in the `YAML Key Priority Sort Order` text area
+	- `Ascending Alphabetical`: Sorts the keys based on key value from a to z
+	- `Descending Alphabetical`: Sorts the keys based on key value from z to a
+
+Example: Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language`
+
+Before:
+
+``````markdown
+---
+language: Typescript
+type: programming
+tags: computer
+keywords: []
+status: WIP
+date: 02/15/2022
+---
+``````
+
+After:
+
+``````markdown
+---
+date: 02/15/2022
+type: programming
+language: Typescript
+tags: computer
+keywords: []
+status: WIP
+---
+``````
+Example: Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language` with `'YAML Sort Order for Other Keys' = Ascending Alphabetical`
+
+Before:
+
+``````markdown
+---
+language: Typescript
+type: programming
+tags: computer
+keywords: []
+status: WIP
+date: 02/15/2022
+---
+``````
+
+After:
+
+``````markdown
+---
+date: 02/15/2022
+type: programming
+language: Typescript
+keywords: []
+status: WIP
+tags: computer
+---
+``````
+Example: Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language` with `'YAML Sort Order for Other Keys' = Descending Alphabetical`
+
+Before:
+
+``````markdown
+---
+language: Typescript
+type: programming
+tags: computer
+keywords: []
+status: WIP
+date: 02/15/2022
+---
+``````
+
+After:
+
+``````markdown
+---
+date: 02/15/2022
+type: programming
+language: Typescript
+tags: computer
+status: WIP
+keywords: []
+---
+``````
+Example: Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language` with `'YAML Sort Order for Other Keys' = Descending Alphabetical` and `'Priority Keys at Start of YAML' = false`
+
+Before:
+
+``````markdown
+---
+language: Typescript
+type: programming
+tags: computer
+keywords: []
+
+status: WIP
+date: 02/15/2022
+---
+``````
+
+After:
+
+``````markdown
+---
+tags: computer
+status: WIP
+keywords: []
+date: 02/15/2022
+type: programming
+language: Typescript
+---
+``````
+
 ## Heading
 ### Header Increment
 
diff --git a/src/rules.ts b/src/rules.ts
index 19cabbc..51da403 100644
--- a/src/rules.ts
+++ b/src/rules.ts
@@ -2161,6 +2161,203 @@ export const rules: Rule[] = [
       ],
   ),
 
+  new Rule(
+      'YAML Key Sort',
+      'Sorts the YAML keys based on the order and priority specified. Note: removes blank lines as well.',
+      RuleType.YAML,
+      (text: string, options = {}) => {
+        const yaml = text.match(yamlRegex);
+        if (!yaml) {
+          return text;
+        }
+
+        let yamlText = yaml[1];
+
+        const priorityAtStartOfYaml: boolean = options['Priority Keys at Start of YAML'];
+        const getNewYamlFrontmatter = function(priorityKeysSorted: string, remainingKeys: string, priorityAtStart: boolean): string {
+          if (priorityAtStart) {
+            return `---\n${priorityKeysSorted}${remainingKeys}---`;
+          }
+
+          return `---\n${remainingKeys}${priorityKeysSorted}---`;
+        };
+
+        const getYAMLKeysSorted = function(yaml: string, keys: string[]): {remainingYaml: string, sortedYamlKeyValues: string} {
+          let specifiedYamlKeysSorted = '';
+          for (const key of keys) {
+            const value = getYamlSectionValue(yaml, key);
+
+            if (value !== null) {
+              if (value.includes('\n')) {
+                specifiedYamlKeysSorted += `${key}:${value}\n`;
+              } else {
+                specifiedYamlKeysSorted += `${key}: ${value}\n`;
+              }
+
+              yaml = removeYamlSection(yaml, key);
+            }
+          }
+
+          return {
+            remainingYaml: yaml,
+            sortedYamlKeyValues: specifiedYamlKeysSorted,
+          };
+        };
+
+        const yamlKeys: string[] = options['YAML Key Priority Sort Order'].split('\n');
+        const sortKeysResult = getYAMLKeysSorted(yamlText, yamlKeys);
+        const priorityKeysSorted = sortKeysResult.sortedYamlKeyValues;
+        yamlText = sortKeysResult.remainingYaml;
+
+        const sortOrder = options['YAML Sort Order for Other Keys'];
+        const yamlObject = loadYAML(yamlText);
+        if (yamlObject == null) {
+          return getNewYamlFrontmatter(priorityKeysSorted, yamlText, priorityAtStartOfYaml);
+        }
+
+        const sortAlphabeticallyDesc = function(previousKey: string, currentKey: string): number {
+          previousKey = previousKey.toLowerCase();
+          currentKey = currentKey.toLowerCase();
+
+          return previousKey > currentKey ? -1 : currentKey > previousKey ? 1 : 0;
+        };
+        const sortAlphabeticallyAsc = function(previousKey: string, currentKey: string): number {
+          previousKey = previousKey.toLowerCase();
+          currentKey = currentKey.toLowerCase();
+
+          return previousKey < currentKey ? -1 : currentKey < previousKey ? 1 : 0;
+        };
+
+        let remainingKeys = Object.keys(yamlObject);
+        let sortMethod: (previousKey: string, currentKey: string) => number;
+        if (sortOrder === 'Ascending Alphabetical') {
+          sortMethod = sortAlphabeticallyAsc;
+        } else if (sortOrder === 'Descending Alphabetical') {
+          sortMethod = sortAlphabeticallyDesc;
+        } else {
+          return getNewYamlFrontmatter(priorityKeysSorted, yamlText, priorityAtStartOfYaml);
+        }
+
+        remainingKeys = remainingKeys.sort(sortMethod);
+        const remainingKeysSortResult = getYAMLKeysSorted(yamlText, remainingKeys);
+
+        return getNewYamlFrontmatter(priorityKeysSorted, remainingKeysSortResult.sortedYamlKeyValues, priorityAtStartOfYaml);
+      },
+      [
+        new Example(
+            'Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language`',
+            dedent`
+      ---
+      language: Typescript
+      type: programming
+      tags: computer
+      keywords: []
+      status: WIP
+      date: 02/15/2022
+      ---
+      `,
+            dedent`
+      ---
+      date: 02/15/2022
+      type: programming
+      language: Typescript
+      tags: computer
+      keywords: []
+      status: WIP
+      ---
+      `,
+            {'YAML Key Priority Sort Order': 'date\ntype\nlanguage', 'YAML Sort Order for Other Keys': 'None', 'Priority Keys at Start of YAML': true},
+        ),
+        new Example(
+            'Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language` with `\'YAML Sort Order for Other Keys\' = Ascending Alphabetical`',
+            dedent`
+      ---
+      language: Typescript
+      type: programming
+      tags: computer
+      keywords: []
+      status: WIP
+      date: 02/15/2022
+      ---
+      `,
+            dedent`
+      ---
+      date: 02/15/2022
+      type: programming
+      language: Typescript
+      keywords: []
+      status: WIP
+      tags: computer
+      ---
+      `,
+            {'YAML Key Priority Sort Order': 'date\ntype\nlanguage', 'YAML Sort Order for Other Keys': 'Ascending Alphabetical'},
+        ),
+        new Example(
+            'Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language` with `\'YAML Sort Order for Other Keys\' = Descending Alphabetical`',
+            dedent`
+      ---
+      language: Typescript
+      type: programming
+      tags: computer
+      keywords: []
+      status: WIP
+      date: 02/15/2022
+      ---
+      `,
+            dedent`
+      ---
+      date: 02/15/2022
+      type: programming
+      language: Typescript
+      tags: computer
+      status: WIP
+      keywords: []
+      ---
+      `,
+            {'YAML Key Priority Sort Order': 'date\ntype\nlanguage', 'YAML Sort Order for Other Keys': 'Descending Alphabetical', 'Priority Keys at Start of YAML': true},
+        ),
+        new Example(
+            'Sorts YAML keys in order specified by `YAML Key Priority Sort Order` has a sort order of `date type language` with `\'YAML Sort Order for Other Keys\' = Descending Alphabetical` and `\'Priority Keys at Start of YAML\' = false`',
+            dedent`
+  ---
+  language: Typescript
+  type: programming
+  tags: computer
+  keywords: []
+
+  status: WIP
+  date: 02/15/2022
+  ---
+  `,
+            dedent`
+  ---
+  tags: computer
+  status: WIP
+  keywords: []
+  date: 02/15/2022
+  type: programming
+  language: Typescript
+  ---
+  `,
+            {'YAML Key Priority Sort Order': 'date\ntype\nlanguage', 'YAML Sort Order for Other Keys': 'Descending Alphabetical', 'Priority Keys at Start of YAML': false},
+        ),
+      ],
+      [
+        new TextAreaOption('YAML Key Priority Sort Order', 'The order in which to sort keys with one on each line where it sorts in the order found in the list', ''),
+        new BooleanOption('Priority Keys at Start of YAML', '`YAML Key Priority Sort Order` is placed at the start of the YAML frontmatter', true),
+        new DropdownOption(
+            'YAML Sort Order for Other Keys',
+            'The way in which to sort the keys that are not found in the `YAML Key Priority Sort Order` text area',
+            'None',
+            [
+              new DropdownRecord('None', 'No sorting other than what is in the `YAML Key Priority Sort Order` text area'),
+              new DropdownRecord('Ascending Alphabetical', 'Sorts the keys based on key value from a to z'),
+              new DropdownRecord('Descending Alphabetical', 'Sorts the keys based on key value from z to a'),
+            ],
+        ),
+      ],
+  ),
+
   // Heading rules
 
   new Rule(
diff --git a/src/utils.ts b/src/utils.ts
index 746d4aa..ad1cb9a 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -503,7 +503,7 @@ export function toSingleLineArrayYamlString<T>(arr: T[]): string {
 }
 
 function getYamlSectionRegExp(rawKey: string): RegExp {
-  return new RegExp(`(?<=^|\\n)${rawKey}:[ \\t]*(\\S.*|(?:\\n {2}\\S.*)*)\\n(?=$|\\S)`);
+  return new RegExp(`(?<=^|\\n)${rawKey}:[ \\t]*(\\S.*|(?:\\n {2}\\S.*)*)\\n`);
 }
 
 export function setYamlSection(yaml: string, rawKey: string, rawValue: string): string {
diff --git a/versions.json b/versions.json
index d48d6f3..ceeed80 100644
--- a/versions.json
+++ b/versions.json
@@ -1,3 +1,26 @@
 {
-	"1.3.4": "0.9.7"
+	"1.3.4": "0.9.7",
+	"1.3.3": "0.9.7",
+	"1.3.2": "0.9.7",
+	"1.3.1": "0.9.7",
+	"1.3.0": "0.9.7",
+	"1.2.10": "0.9.7",
+	"1.2.9": "0.9.7",
+	"1.2.8": "0.9.7",
+	"1.2.7": "0.9.7",
+	"1.2.6": "0.9.7",
+	"1.2.5": "0.9.7",
+	"1.2.4": "0.9.7",
+	"1.2.3": "0.9.7",
+	"1.2.2": "0.9.7",
+	"1.2.1": "0.9.7",
+	"1.2.0": "0.9.7",
+	"1.1.5": "0.9.7",
+	"1.1.4": "0.9.7",
+	"1.1.3": "0.9.7",
+	"1.1.2": "0.9.7",
+	"1.1.1": "0.9.7",
+	"1.1.0": "0.9.7",
+	"1.0.1": "0.9.7",
+	"1.0.0": "0.9.7"
 }


Test patch:
diff --git a/src/test/yaml-key-sort.test.ts b/src/test/yaml-key-sort.test.ts
new file mode 100644
index 0000000..da10e8c
--- /dev/null
+++ b/src/test/yaml-key-sort.test.ts
@@ -0,0 +1,73 @@
+import dedent from 'ts-dedent';
+import {rulesDict} from '../rules';
+
+describe('YAML Key Sort', () => {
+  it('Sort with multiline array keeps values for multiline array', () => {
+    const before = dedent`
+        ---
+        language: Typescript
+        type: programming
+        tags: computer
+        keywords: []
+        status: WIP
+        date: 02/15/2022
+        aliases:
+          - alias1
+          - alias2
+          - alias3
+        ---
+        `;
+
+    const after = dedent`
+        ---
+        aliases:
+          - alias1
+          - alias2
+          - alias3
+        date: 02/15/2022
+        keywords: []
+        language: Typescript
+        status: WIP
+        tags: computer
+        type: programming
+        ---
+        `;
+
+    expect(rulesDict['yaml-key-sort'].apply(before, {'YAML Key Priority Sort Order': '', 'YAML Sort Order for Other Keys': 'Ascending Alphabetical', 'Priority Keys at Start of YAML': true})).toBe(after);
+  });
+
+  it('Sort with new line in yaml removes new line', () => {
+    const before = dedent`
+          ---
+          language: Typescript
+          type: programming
+          tags: computer
+          keywords: []
+
+          date: 02/15/2022
+          aliases:
+            - alias1
+            - alias2
+            - alias3
+          status: WIP
+          ---
+          `;
+
+    const after = dedent`
+          ---
+          aliases:
+            - alias1
+            - alias2
+            - alias3
+          date: 02/15/2022
+          keywords: []
+          language: Typescript
+          status: WIP
+          tags: computer
+          type: programming
+          ---
+          `;
+
+    expect(rulesDict['yaml-key-sort'].apply(before, {'YAML Key Priority Sort Order': '', 'YAML Sort Order for Other Keys': 'Ascending Alphabetical', 'Priority Keys at Start of YAML': true})).toBe(after);
+  });
+});