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

[ #60 , extract tasks ] #71

Merged
merged 6 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/five-dots-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"mddb": minor
---

[ #60 , extract tasks ]
Add tasks extraction from files. e.g `- [ ] task`
6 changes: 6 additions & 0 deletions __mocks__/content/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ tags: tag1, tag2, tag3
# Welcome

[link](blog0.mdx)

- [] uncompleted task 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely we need a pure list item ... which is not a task and check it is not showing up in our tasks list ...

We are in theory extracting tasks not just all list items

i.e. something like ...

- this is a list item
- this is another list item

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course, I tested for those before committing.

Actually, this exact line - [] uncompleted task 1 is a list item that's not being identified as a task because it's [] and not [ ], i.e., missing a space between the brackets, thus not a valid task (not valid in Obsidian, Github markdown, or even remark-parse).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mohamedsalem401 ok that wasn't obvious because of the name. 😄

- [ ] uncompleted task 2

- [x] completed task 1
- [X] completed task 2
8 changes: 1 addition & 7 deletions src/lib/databaseUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { Knex } from "knex";
import {
MddbFile,
MddbTag,
MddbLink,
MddbFileTag,
File,
} from "./schema.js";
import { MddbFile, MddbTag, MddbLink, MddbFileTag, File } from "./schema.js";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this "linting" stuff creates noise in the PRs. I strongly suggest we do this separately (it can just be a direct push to main if just linting).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing. The only thing that makes me lint on PR is our ci/cd complaining, but I will select the Github option to bypass this error.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mohamedsalem401 👍

For future: we can lint the whole repo once and then we don't get running into these.

import path from "path";
import { WikiLink } from "./parseFile.js";

Expand All @@ -18,12 +12,12 @@
}
}

export function mapFileToInsert(file: any) {

Check warning on line 15 in src/lib/databaseUtils.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
const { _id, file_path, extension, url_path, filetype, metadata } = file;
return { _id, file_path, extension, url_path, filetype, metadata };
}

export function mapLinksToInsert(filesToInsert: File[], file: any) {

Check warning on line 20 in src/lib/databaseUtils.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
return file.links.map((link: WikiLink) => {
let to: string | undefined;
if (!link.internal) {
Expand Down Expand Up @@ -51,12 +45,12 @@
});
}

export function isLinkToDefined(link: any) {

Check warning on line 48 in src/lib/databaseUtils.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
return link.to !== undefined;
}

export function mapFileTagsToInsert(file: any) {

Check warning on line 52 in src/lib/databaseUtils.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
return file.tags.map((tag: any) => ({

Check warning on line 53 in src/lib/databaseUtils.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
file: file._id,
tag: tag as unknown as string,
}));
Expand Down
37 changes: 37 additions & 0 deletions src/lib/parseFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
// Links
const links = extractWikiLinks(ast, options);

const tasks = extractTasks(ast);
metadata.tasks = tasks;

return {
metadata,
links,
Expand Down Expand Up @@ -61,7 +64,7 @@

const nodes = selectAll("*", ast);
for (let index = 0; index < nodes.length; index++) {
const node: any = nodes[index];

Check warning on line 67 in src/lib/parseFile.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
if (node.value) {
const textTags = node.value.match(/(?:^|\s)(#(\w+|\/|-|_)+)/g);
if (textTags) {
Expand All @@ -74,7 +77,7 @@
};

export interface LinkExtractors {
[test: string]: (node: any) => WikiLink;

Check warning on line 80 in src/lib/parseFile.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
}

export interface WikiLink {
Expand All @@ -93,7 +96,7 @@
const directory = path.dirname(from);

const extractors: LinkExtractors = {
link: (node: any) => {

Check warning on line 99 in src/lib/parseFile.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
const to = !node.url.startsWith("http")
? path.posix.join(directory, node.url)
: node.url;
Expand All @@ -106,7 +109,7 @@
internal: !node.url.startsWith("http"),
};
},
image: (node: any) => ({

Check warning on line 112 in src/lib/parseFile.ts

View workflow job for this annotation

GitHub Actions / Lint & format check

Unexpected any. Specify a different type
from: from,
to: path.posix.join(directory, node.url),
toRaw: node.url,
Expand Down Expand Up @@ -155,6 +158,40 @@
return wikiLinks;
};

export interface Task {
description: string;
checked: boolean;
}

export const extractTasks = (ast: Root) => {
const nodes = selectAll("*", ast);
const tasks: Task[] = [];
nodes.map((node: any) => {
if (node.type === "listItem") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like we will extract all list items as tasks which is not correct ....

const description = recursivelyExtractText(node).trim();
const checked = node.checked;
if (checked !== null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rufuspollock
You are right; we need to exclude listItems that are not tasks. That's why I am checking if checked is null to exclude the non-tasks list items, as per the documentation for GFM.
image

tasks.push({
description,
checked,
});
}
}
});

return tasks;
};

function recursivelyExtractText(node: any) {
if (node.value) {
return node.value;
} else if (node.children) {
return node.children.map(recursivelyExtractText).join(" ");
} else {
return "";
}
}

// links = extractWikiLinks({
// source,
// // TODO pass slug instead of file path as hrefs/srcs are sluggified too
Expand Down
72 changes: 72 additions & 0 deletions src/tests/extractTasks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { extractTasks, processAST } from "../lib/parseFile";

const getTasksFromSource = (source: string) => {
const ast = processAST(source, {});
const tasks = extractTasks(ast);
return tasks;
};

describe("extractTasks", () => {
test("should extract uncompleted tasks from body", () => {
const tasks = getTasksFromSource(
"- [] uncompleted task 1\n- [ ] uncompleted task 2"
);
const expectedTasks = [
{ description: "uncompleted task 2", checked: false },
];
expect(tasks).toEqual(expectedTasks);
});

test("should extract completed tasks from body", () => {
const tasks = getTasksFromSource(
"- [x] completed task 1\n- [X] completed task 2"
);
const expectedTasks = [
{ description: "completed task 1", checked: true },
{ description: "completed task 2", checked: true },
];
expect(tasks).toEqual(expectedTasks);
});

test("should handle mixed completed and uncompleted tasks", () => {
const tasks = getTasksFromSource(
"- [x] completed task\n- [ ] uncompleted task"
);
const expectedTasks = [
{ description: "completed task", checked: true },
{ description: "uncompleted task", checked: false },
];
expect(tasks).toEqual(expectedTasks);
});

test("should handle tasks with leading and trailing spaces", () => {
const tasks = getTasksFromSource(
"- [x] completed task \n- [ ] uncompleted task "
);
const expectedTasks = [
{ description: "completed task", checked: true },
{ description: "uncompleted task", checked: false },
];
expect(tasks).toEqual(expectedTasks);
});

test("should handle tasks with different checkbox formats", () => {
const tasks = getTasksFromSource(
"- [x] task 1\n- [X] task 2\n- [ ] task 3"
);
const expectedTasks = [
{ description: "task 1", checked: true },
{ description: "task 2", checked: true },
{ description: "task 3", checked: false },
];
expect(tasks).toEqual(expectedTasks);
});

test("should handle tasks with special characters", () => {
const tasks = getTasksFromSource("- [x] task with $pecial character$");
const expectedTasks = [
{ description: "task with $pecial character$", checked: true },
];
expect(tasks).toEqual(expectedTasks);
});
});
10 changes: 10 additions & 0 deletions src/tests/parseFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ tags: a, b, c
[[blog/Some Other Link]]
[[blog/Some Other Link|Page Alias]]
![[Some Image.png]]
- [ ] uncompleted task
- [x] completed task
`;

describe("parseFile", () => {
Expand All @@ -18,6 +20,10 @@ describe("parseFile", () => {
title: "Hello World",
authors: ["John Doe", "Jane Doe"],
tags: ["a", "b", "c"],
tasks: [
{ description: "uncompleted task", checked: false },
{ description: "completed task", checked: true },
],
};
const expectedLinks = [
{
Expand Down Expand Up @@ -63,6 +69,10 @@ describe("parseFile", () => {
title: "Hello World",
authors: ["John Doe", "Jane Doe"],
tags: ["a", "b", "c"],
tasks: [
{ description: "uncompleted task", checked: false },
{ description: "completed task", checked: true },
],
};
const expectedLinks = [
{
Expand Down
14 changes: 14 additions & 0 deletions src/tests/process.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ describe("Can parse a file and get file info", () => {
expect(fileInfo.metadata).toEqual({
title: "Homepage",
tags: ["tag1", "tag2", "tag3"],
tasks: [
{
checked: false,
description: "uncompleted task 2",
},
{
checked: true,
description: "completed task 1",
},
{
checked: true,
description: "completed task 2",
},
],
});
expect(fileInfo.links).toEqual([
{
Expand Down
Loading