Skip to content

Commit

Permalink
Merge pull request #256 from mnaoumov/issue-254
Browse files Browse the repository at this point in the history
Add rule YAML Title Alias
  • Loading branch information
pjkaufman authored Jul 7, 2022
2 parents b9491cd + dcee1ad commit 16fc01a
Show file tree
Hide file tree
Showing 5 changed files with 1,148 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Documentation for all rules can be found in the [rules docs](https://github.com/
- [insert-yaml-attributes](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#insert-yaml-attributes)
- [yaml-timestamp](https://github.com/platers/obsidian-linter/blob/master/docs/rules.md#yaml-timestamp)
- [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)

### Heading rules
Expand Down
55 changes: 55 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,61 @@ title: Filename

```

### YAML Title Alias

Alias: `yaml-title-alias`

Inserts the title of the file into the YAML frontmatter's aliases section. Gets the title from the first H1 or filename.

Options:
- YAML aliases section style: The style of the aliases YAML section
- Default: `Multi-line array`
- `Multi-line array`: ```aliases:\n - Title```
- `Single-line array`: ```aliases: [Title]```
- `Single string that expands to multi-line array if needed`: ```aliases: Title```
- `Single string that expands to single-line array if needed`: ```aliases: Title```
- Preserve existing aliases section style: If set, the `YAML aliases section style` setting applies only to the newly created sections
- Default: `true`
- Keep alias that matches the filename: Such aliases are usually redundant
- Default: `false`

Example: Adds a header with the title from heading.

Before:

```markdown
# Obsidian
```

After:

```markdown
---
aliases:
- Obsidian
linter-yaml-title-alias: Obsidian
---
# Obsidian
```
Example: Adds a header with the title.

Before:

```markdown

```

After:

```markdown
---
aliases:
- Filename
linter-yaml-title-alias: Filename
---

```

### Escape YAML Special Characters

Alias: `escape-yaml-special-characters`
Expand Down
294 changes: 292 additions & 2 deletions src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import {
loadYAML,
moveFootnotesToEnd,
yamlRegex,
escapeYamlString,
toYamlString,
makeEmphasisOrBoldConsistent,
addTwoSpacesAtEndOfLinesFollowedByAnotherLineOfTextContent,
makeSureThereIsOnlyOneBlankLineBeforeAndAfterParagraphs,
removeSpacesInLinkText,
toSingleLineArrayYamlString,
setYamlSection,
getYamlSectionValue,
removeYamlSection,
} from './utils';
import {
Option,
Expand Down Expand Up @@ -1606,7 +1610,7 @@ export const rules: Rule[] = [
});
title = title || options['metadata: file name'];

title = escapeYamlString(title);
title = toYamlString(title);

return formatYAML(text, (text) => {
const title_match_str = `\n${options['Title Key']}.*\n`;
Expand Down Expand Up @@ -1658,6 +1662,292 @@ export const rules: Rule[] = [
[new TextOption('Title Key', 'Which YAML key to use for title', 'title')],
),

new Rule(
'YAML Title Alias',
'Inserts the title of the file into the YAML frontmatter\'s aliases section. Gets the title from the first H1 or filename.',
RuleType.YAML,
(text: string, options = {}) => {
const ALIASES_YAML_SECTION_NAME = 'aliases';
const LINTER_ALIASES_HELPER_NAME = 'linter-yaml-title-alias';

const optionsObj = {
yamlAliasesSectionStyle: options['YAML aliases section style'] as string,
preserveExistingAliasesSectionStyle: options['Preserve existing aliases section style'] as boolean ?? true,
keepAliasThatMatchesTheFilename: options['Keep alias that matches the filename'] as boolean,
fileName: options['metadata: file name'] as string,
};

text = initYAML(text);
let title = ignoreCodeBlocksYAMLTagsAndLinks(text, (text) => {
const result = text.match(/^#\s+(.*)/m);
if (result) {
return result[1];
}
return '';
});
title = title || optionsObj.fileName;

const shouldRemoveTitleAlias = !optionsObj.keepAliasThatMatchesTheFilename && title === optionsObj.fileName;

let yaml = text.match(yamlRegex)[1];

let previousTitle = loadYAML(getYamlSectionValue(yaml, LINTER_ALIASES_HELPER_NAME));


let requiresChanges = true;

if (previousTitle === title && !shouldRemoveTitleAlias) {
requiresChanges = false;
}

if (previousTitle === null && shouldRemoveTitleAlias) {
requiresChanges = false;
}

if (!requiresChanges && optionsObj.preserveExistingAliasesSectionStyle) {
return text;
}

let aliasesValue = getYamlSectionValue(yaml, ALIASES_YAML_SECTION_NAME);

if (!aliasesValue) {
if (shouldRemoveTitleAlias) {
return text;
}

let emptyValue;
switch (optionsObj.yamlAliasesSectionStyle) {
case 'Multi-line array':
emptyValue = '\n - \'\'';
break;
case 'Single-line array':
emptyValue = ' [\'\']';
break;
case 'Single string that expands to multi-line array if needed':
case 'Single string that expands to single-line array if needed':
emptyValue = ' \'\'';
break;
default:
throw new Error(`Unsupported setting 'YAML aliases section style': ${optionsObj.yamlAliasesSectionStyle}`);
}

let newYaml = yaml;
newYaml = setYamlSection(newYaml, ALIASES_YAML_SECTION_NAME, emptyValue);
newYaml = setYamlSection(newYaml, LINTER_ALIASES_HELPER_NAME, ' \'\'');

text = text.replace(`---\n${yaml}---`, `---\n${newYaml}---`);
yaml = newYaml;
aliasesValue = getYamlSectionValue(yaml, ALIASES_YAML_SECTION_NAME);
previousTitle = '';
}

const isMultiline = aliasesValue.includes('\n');
const parsedAliases = loadYAML(aliasesValue);

const isSingleString = !isMultiline && aliasesValue.match(/^\[.*\]/) === null;

let resultAliasesArray = isSingleString ? [parsedAliases] : [...parsedAliases];

const previousTitleIndex = resultAliasesArray.indexOf(previousTitle);
if (previousTitleIndex !== -1) {
if (shouldRemoveTitleAlias) {
resultAliasesArray.splice(previousTitleIndex, 1);
} else {
resultAliasesArray[previousTitleIndex] = title;
}
} else if (!shouldRemoveTitleAlias) {
resultAliasesArray = [title, ...resultAliasesArray];
}

if (!requiresChanges) {
switch (optionsObj.yamlAliasesSectionStyle) {
case 'Multi-line array':
if (isMultiline) {
return text;
}
break;
case 'Single-line array':
if (!isMultiline && !isSingleString) {
return text;
}
break;
case 'Single string that expands to multi-line array if needed':
if (isSingleString) {
return text;
}
if (isMultiline && resultAliasesArray.length > 1) {
return text;
}
break;
case 'Single string that expands to single-line array if needed':
if (isSingleString) {
return text;
}
if (!isMultiline && resultAliasesArray.length > 1) {
return text;
}
break;
}
}

let resultStyle;

if (resultAliasesArray.length === 0) {
resultStyle = 'Remove';
} else if (!optionsObj.preserveExistingAliasesSectionStyle) {
switch (optionsObj.yamlAliasesSectionStyle) {
case 'Multi-line array':
resultStyle = 'Multi-line array';
break;
case 'Single-line array':
resultStyle = 'Single-line array';
break;
case 'Single string that expands to multi-line array if needed':
if (resultAliasesArray.length === 1) {
resultStyle = 'Single string';
} else {
resultStyle = 'Multi-line array';
}
break;
case 'Single string that expands to single-line array if needed':
if (resultAliasesArray.length === 1) {
resultStyle = 'Single string';
} else {
resultStyle = 'Single-line array';
}
break;
}
} else if (isSingleString) {
if (resultAliasesArray.length === 1) {
resultStyle = 'Single string';
} else {
switch (optionsObj.yamlAliasesSectionStyle) {
case 'Multi-line array':
case 'Single string that expands to multi-line array if needed':
resultStyle = 'Multi-line array';
break;
case 'Single-line array':
case 'Single string that expands to single-line array if needed':
resultStyle = 'Single-line array';
break;
}
}
} else if (isMultiline) {
resultStyle = 'Multi-line array';
} else {
resultStyle = 'Single-line array';
}

let newAliasesYaml;

switch (resultStyle) {
case 'Remove':
break;
case 'Multi-line array':
newAliasesYaml = `\n${toYamlString(resultAliasesArray)}`.replace(/\n-/g, '\n -');
break;
case 'Single-line array':
newAliasesYaml = ` ${toSingleLineArrayYamlString(resultAliasesArray)}`;
break;
case 'Single string':
newAliasesYaml = resultAliasesArray.length === 0 ? '' : ` ${toYamlString(resultAliasesArray[0])}`;
break;
default:
throw new Error(`Unsupported resultStyle: ${resultStyle}`);
}

let newYaml = yaml;

if (resultStyle === 'Remove') {
newYaml = removeYamlSection(newYaml, ALIASES_YAML_SECTION_NAME);
} else {
newYaml = setYamlSection(newYaml, ALIASES_YAML_SECTION_NAME, newAliasesYaml);
}

if (shouldRemoveTitleAlias) {
newYaml = removeYamlSection(newYaml, LINTER_ALIASES_HELPER_NAME);
} else {
newYaml = setYamlSection(newYaml, LINTER_ALIASES_HELPER_NAME, ` ${toYamlString(title)}`);
}

text = text.replace(yaml, newYaml);

return text;
},
[
new Example(
'Adds a header with the title from heading.',
dedent`
# Obsidian
`,
dedent`
---
aliases:
- Obsidian
linter-yaml-title-alias: Obsidian
---
# Obsidian
`,
{
'YAML aliases section style': 'Multi-line array',
},
),
new Example(
'Adds a header with the title.',
dedent`
`,
dedent`
---
aliases:
- Filename
linter-yaml-title-alias: Filename
---
`,
{
'metadata: file name': 'Filename',
'YAML aliases section style': 'Multi-line array',
'Keep alias that matches the filename': true,
},
),
],
[
new DropdownOption(
'YAML aliases section style',
'The style of the aliases YAML section',
'Multi-line array',
[
new DropdownRecord(
'Multi-line array',
'```aliases:\\n - Title```',
),
new DropdownRecord(
'Single-line array',
'```aliases: [Title]```',
),
new DropdownRecord(
'Single string that expands to multi-line array if needed',
'```aliases: Title```',
),
new DropdownRecord(
'Single string that expands to single-line array if needed',
'```aliases: Title```',
),
],
),
new BooleanOption(
'Preserve existing aliases section style',
'If set, the `YAML aliases section style` setting applies only to the newly created sections',
true,
),
new BooleanOption(
'Keep alias that matches the filename',
'Such aliases are usually redundant',
false,
),
],
),

new Rule(
'Escape YAML Special Characters',
'Escapes colons with a space after them (: ), single quotes (\'), and double quotes (") in YAML.',
Expand Down
Loading

0 comments on commit 16fc01a

Please sign in to comment.