Skip to content

note

Note objects.

Note

A Markdown note.

Attributes:

Name Type Description
path Path

path to the note.

metadata NoteMetadata

NoteMetadata object.

content str

The note's textual content (including all types of metadata).

Source code in pyomd/note.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class Note:
    """A Markdown note.

    Attributes:
        path:
            path to the note.
        metadata:
            NoteMetadata object.
        content:
            The note's textual content (including all types of metadata).
    """

    def __init__(self, path: Union[Path, str]):
        """Initializes a Note object.

        Args:
            path: path to the markdown note.
        """
        self.path: Path = Path(path)
        try:
            with open(self.path, "r") as f:
                self.content: str = f.read()
        except Exception as e:
            raise NoteCreationError(path=path, exception=e) from e

        try:
            self.metadata: NoteMetadata = NoteMetadata(self.content)
        except Exception as e:
            raise ParsingNoteMetadataError(path=path, exception=e) from e

    def __repr__(self) -> str:
        return f'Note (path: "{self.path}")\n'

    def append(self, str_append: str, allow_repeat: bool = False):
        """Appends text to the note content.

        Args:
            str_append: string to append to the note content.
            allow_repeat: Add the string if it is already present in the note content.
        """
        if allow_repeat:
            self.content += f"\n{str_append}"
        else:
            if len(re.findall(re.escape(str_append), self.content)) == 0:
                self.content += f"\n{str_append}"

    def print(self):
        """Prints the note content to the screen."""
        print(self.content)

    def sub(self, pattern: str, replace: str, is_regex: bool = False):
        """Substitutes text within the note.

        Args:
            pattern:
                the pattern to replace (plain text or regular expression)
            replace:
                what to replace the pattern with
            is_regex:
                Whether the pattern is a regex pattern or plain text.
        """
        if not is_regex:
            pattern = re.escape(pattern)
        self.content = re.sub(pattern, replace, self.content)

    def update_content(
        self,
        inline_position: str = "bottom",
        inline_inplace: bool = True,
        inline_tml: Union[str, Callable] = "standard",  # type: ignore
        write: bool = False,
    ):
        """Updates the note's content.

        Args:
            inline_position:
                if "bottom" / "top", inline metadata is grouped at the bottom/top of the note.
                This is always the case for new inline metadata (that didn't exist in the
                previous note content).
            inline_inplace:
                By default it is True, which means the inline metadata position in the note
                is not modified. If False, the metadata is grouped according to `inline_how`
            inline_tml:
                Which template to use to update inline metadata content.
                Current possible values: ["standard", "callout"]
                Defaults to "standard": each metadata field is written on a newline.
                "callout": metadata fields are regrouped inside a callout:
                    > [!info]- metadata
                    > key1 :: values1
                    > key2 :: values2
                    ...
                NOTE: In later updates it will be possible to pass a function specifying how
                to display the metadata, for greater customization.
            write:
                Write changes to the file on disk after updating the content.
                If write = False, the user needs to call Note.write() subsequently to write
                changes to disk, otherwise only the self.content attribute is modified
                (in memory, but not on disk).
        """

        try:
            self.content = self.metadata._update_content(
                self.content,
                inline_position=inline_position,
                inline_inplace=inline_inplace,
                inline_tml=inline_tml,
            )
        except Exception as e:
            raise UpdateContentError(path=self.path, exception=e)
        if write:
            self.write()

    def write(self, path: Union[Path, None] = None):
        """Writes the note's content to disk.

        Args:
            path:
                path to the note. If None, overwrites the current note content.
        """
        p = self.path if path is None else path
        with open(p, "w") as f:
            f.write(self.content)

    @staticmethod
    def _is_md_file(path: Path):
        exist = path.exists()
        is_md = path.suffix == ".md"
        return exist and is_md

__init__(path)

Initializes a Note object.

Parameters:

Name Type Description Default
path Union[Path, str]

path to the markdown note.

required
Source code in pyomd/note.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def __init__(self, path: Union[Path, str]):
    """Initializes a Note object.

    Args:
        path: path to the markdown note.
    """
    self.path: Path = Path(path)
    try:
        with open(self.path, "r") as f:
            self.content: str = f.read()
    except Exception as e:
        raise NoteCreationError(path=path, exception=e) from e

    try:
        self.metadata: NoteMetadata = NoteMetadata(self.content)
    except Exception as e:
        raise ParsingNoteMetadataError(path=path, exception=e) from e

append(str_append, allow_repeat=False)

Appends text to the note content.

Parameters:

Name Type Description Default
str_append str

string to append to the note content.

required
allow_repeat bool

Add the string if it is already present in the note content.

False
Source code in pyomd/note.py
46
47
48
49
50
51
52
53
54
55
56
57
def append(self, str_append: str, allow_repeat: bool = False):
    """Appends text to the note content.

    Args:
        str_append: string to append to the note content.
        allow_repeat: Add the string if it is already present in the note content.
    """
    if allow_repeat:
        self.content += f"\n{str_append}"
    else:
        if len(re.findall(re.escape(str_append), self.content)) == 0:
            self.content += f"\n{str_append}"

print()

Prints the note content to the screen.

Source code in pyomd/note.py
59
60
61
def print(self):
    """Prints the note content to the screen."""
    print(self.content)

sub(pattern, replace, is_regex=False)

Substitutes text within the note.

Parameters:

Name Type Description Default
pattern str

the pattern to replace (plain text or regular expression)

required
replace str

what to replace the pattern with

required
is_regex bool

Whether the pattern is a regex pattern or plain text.

False
Source code in pyomd/note.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def sub(self, pattern: str, replace: str, is_regex: bool = False):
    """Substitutes text within the note.

    Args:
        pattern:
            the pattern to replace (plain text or regular expression)
        replace:
            what to replace the pattern with
        is_regex:
            Whether the pattern is a regex pattern or plain text.
    """
    if not is_regex:
        pattern = re.escape(pattern)
    self.content = re.sub(pattern, replace, self.content)

update_content(inline_position='bottom', inline_inplace=True, inline_tml='standard', write=False)

Updates the note's content.

Parameters:

Name Type Description Default
inline_position str

if "bottom" / "top", inline metadata is grouped at the bottom/top of the note. This is always the case for new inline metadata (that didn't exist in the previous note content).

'bottom'
inline_inplace bool

By default it is True, which means the inline metadata position in the note is not modified. If False, the metadata is grouped according to inline_how

True
inline_tml Union[str, Callable]

Which template to use to update inline metadata content. Current possible values: ["standard", "callout"] Defaults to "standard": each metadata field is written on a newline. "callout": metadata fields are regrouped inside a callout: > [!info]- metadata > key1 :: values1 > key2 :: values2 ... NOTE: In later updates it will be possible to pass a function specifying how to display the metadata, for greater customization.

'standard'
write bool

Write changes to the file on disk after updating the content. If write = False, the user needs to call Note.write() subsequently to write changes to disk, otherwise only the self.content attribute is modified (in memory, but not on disk).

False
Source code in pyomd/note.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def update_content(
    self,
    inline_position: str = "bottom",
    inline_inplace: bool = True,
    inline_tml: Union[str, Callable] = "standard",  # type: ignore
    write: bool = False,
):
    """Updates the note's content.

    Args:
        inline_position:
            if "bottom" / "top", inline metadata is grouped at the bottom/top of the note.
            This is always the case for new inline metadata (that didn't exist in the
            previous note content).
        inline_inplace:
            By default it is True, which means the inline metadata position in the note
            is not modified. If False, the metadata is grouped according to `inline_how`
        inline_tml:
            Which template to use to update inline metadata content.
            Current possible values: ["standard", "callout"]
            Defaults to "standard": each metadata field is written on a newline.
            "callout": metadata fields are regrouped inside a callout:
                > [!info]- metadata
                > key1 :: values1
                > key2 :: values2
                ...
            NOTE: In later updates it will be possible to pass a function specifying how
            to display the metadata, for greater customization.
        write:
            Write changes to the file on disk after updating the content.
            If write = False, the user needs to call Note.write() subsequently to write
            changes to disk, otherwise only the self.content attribute is modified
            (in memory, but not on disk).
    """

    try:
        self.content = self.metadata._update_content(
            self.content,
            inline_position=inline_position,
            inline_inplace=inline_inplace,
            inline_tml=inline_tml,
        )
    except Exception as e:
        raise UpdateContentError(path=self.path, exception=e)
    if write:
        self.write()

write(path=None)

Writes the note's content to disk.

Parameters:

Name Type Description Default
path Union[Path, None]

path to the note. If None, overwrites the current note content.

None
Source code in pyomd/note.py
125
126
127
128
129
130
131
132
133
134
def write(self, path: Union[Path, None] = None):
    """Writes the note's content to disk.

    Args:
        path:
            path to the note. If None, overwrites the current note content.
    """
    p = self.path if path is None else path
    with open(p, "w") as f:
        f.write(self.content)

Notes

A batch of notes.

Attributes:

Name Type Description
self.notes

list of Note objects

self.metadata

NoteMetadataBatch object

Source code in pyomd/note.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
class Notes:
    """A batch of notes.

    Attributes:
        self.notes:
            list of Note objects
        self.metadata:
            NoteMetadataBatch object
    """

    def __init__(self, paths: Union[Path, list[Path]], recursive: bool = True):
        """Initializes a Notes object.

        Add paths to individual notes or to directories containing multiple notes.

        Args:
            paths:
                list of paths pointing to markdown notes or folders
            recursive:
                When given a path to a directory, whether to add notes
                from sub-directories too
        """
        self.notes: list[Note] = []
        self.add(paths=paths, recursive=recursive)
        self.metadata = NoteMetadataBatch(self.notes)

    def __len__(self):
        return len(self.notes)

    def add(self, paths: Union[Path, list[Path]], recursive: bool = True):
        """Adds new notes to the Notes object.

        Args:
            paths:
                list of paths pointing to markdown notes or folders
            recursive:
                When given a path to a directory, whether to add notes
                from sub-directories too
        """
        if isinstance(paths, Path):
            paths = [paths]
        for pth in paths:
            assert pth.exists(), f"file or folder doesn't exist: '{pth}'"
            if pth.is_dir():
                for root, _, fls in os.walk(pth):  # type: ignore
                    for f_name in fls:  # type: ignore
                        pth_f: Path = Path(root) / f_name  # type: ignore
                        if Note._is_md_file(pth_f):
                            self.notes.append(Note(path=pth_f))
                    if not recursive:
                        break
            elif Note._is_md_file(pth):
                self.notes.append(Note(path=pth))

    def append(self, str_append: str, allow_repeat: bool = False):
        """Appends text to the note content.

        See `Note.append` for argument details."""
        for note in self.notes:
            note.append(str_append=str_append, allow_repeat=allow_repeat)

    def filter(
        self,
        starts_with: Optional[str] = None,
        ends_with: Optional[str] = None,
        pattern: Optional[str] = None,
        has_meta: Optional[
            list[tuple[str, Union[list[str], str, None], Optional[MetadataType]]]
        ] = None,
    ):
        """Keep only notes that have certain characteristics.

        Args:
            starts_with:
                Keep notes which file name starts with the string
            ends_with:
                Keep notes which file name ends with the string
            pattern:
                Keep notes which file name matches the regex pattern
            has_meta:
                keep notes which contains the specified metadata.
                has_meta is a list of tuples:
                (key_name, l_values, meta_type)
                that correspond to the arguments of NoteMetadata.has()
        """
        if starts_with is not None:
            self.notes = [
                n for n in self.notes if str(n.path.name).startswith(starts_with)
            ]
        if ends_with is not None:
            self.notes = [n for n in self.notes if str(n.path.name).endswith(ends_with)]
        if pattern is not None:
            self.notes = [n for n in self.notes if re.match(pattern, str(n.path.name))]
        if has_meta is not None:
            include: list[bool] = []
            for note in self.notes:
                inc = True
                for (k, vals, meta_type) in has_meta:
                    if not note.metadata.has(k=k, l=vals, meta_type=meta_type):
                        inc = False
                include.append(inc)
            self.notes = [n for (n, inc) in zip(self.notes, include) if inc]

    def update_content(
        self,
        inline_position: str = "bottom",
        inline_inplace: bool = True,
        inline_tml: Union[str, Callable] = "standard",  # type: ignore
        write: bool = False,
    ):
        """Updates the content of all notes.

        See `Note.update_content` for argument details.
        """
        for note in self.notes:
            note.update_content(
                inline_position=inline_position,
                inline_inplace=inline_inplace,
                inline_tml=inline_tml,
                write=write,
            )

    def write(self):
        """Writes the note's content to disk.

        See `Note.write` for argument details.
        """
        for note in self.notes:
            note.write()

__init__(paths, recursive=True)

Initializes a Notes object.

Add paths to individual notes or to directories containing multiple notes.

Parameters:

Name Type Description Default
paths Union[Path, list[Path]]

list of paths pointing to markdown notes or folders

required
recursive bool

When given a path to a directory, whether to add notes from sub-directories too

True
Source code in pyomd/note.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def __init__(self, paths: Union[Path, list[Path]], recursive: bool = True):
    """Initializes a Notes object.

    Add paths to individual notes or to directories containing multiple notes.

    Args:
        paths:
            list of paths pointing to markdown notes or folders
        recursive:
            When given a path to a directory, whether to add notes
            from sub-directories too
    """
    self.notes: list[Note] = []
    self.add(paths=paths, recursive=recursive)
    self.metadata = NoteMetadataBatch(self.notes)

add(paths, recursive=True)

Adds new notes to the Notes object.

Parameters:

Name Type Description Default
paths Union[Path, list[Path]]

list of paths pointing to markdown notes or folders

required
recursive bool

When given a path to a directory, whether to add notes from sub-directories too

True
Source code in pyomd/note.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def add(self, paths: Union[Path, list[Path]], recursive: bool = True):
    """Adds new notes to the Notes object.

    Args:
        paths:
            list of paths pointing to markdown notes or folders
        recursive:
            When given a path to a directory, whether to add notes
            from sub-directories too
    """
    if isinstance(paths, Path):
        paths = [paths]
    for pth in paths:
        assert pth.exists(), f"file or folder doesn't exist: '{pth}'"
        if pth.is_dir():
            for root, _, fls in os.walk(pth):  # type: ignore
                for f_name in fls:  # type: ignore
                    pth_f: Path = Path(root) / f_name  # type: ignore
                    if Note._is_md_file(pth_f):
                        self.notes.append(Note(path=pth_f))
                if not recursive:
                    break
        elif Note._is_md_file(pth):
            self.notes.append(Note(path=pth))

append(str_append, allow_repeat=False)

Appends text to the note content.

See Note.append for argument details.

Source code in pyomd/note.py
197
198
199
200
201
202
def append(self, str_append: str, allow_repeat: bool = False):
    """Appends text to the note content.

    See `Note.append` for argument details."""
    for note in self.notes:
        note.append(str_append=str_append, allow_repeat=allow_repeat)

filter(starts_with=None, ends_with=None, pattern=None, has_meta=None)

Keep only notes that have certain characteristics.

Parameters:

Name Type Description Default
starts_with Optional[str]

Keep notes which file name starts with the string

None
ends_with Optional[str]

Keep notes which file name ends with the string

None
pattern Optional[str]

Keep notes which file name matches the regex pattern

None
has_meta Optional[list[tuple[str, Union[list[str], str, None], Optional[MetadataType]]]]

keep notes which contains the specified metadata. has_meta is a list of tuples: (key_name, l_values, meta_type) that correspond to the arguments of NoteMetadata.has()

None
Source code in pyomd/note.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
def filter(
    self,
    starts_with: Optional[str] = None,
    ends_with: Optional[str] = None,
    pattern: Optional[str] = None,
    has_meta: Optional[
        list[tuple[str, Union[list[str], str, None], Optional[MetadataType]]]
    ] = None,
):
    """Keep only notes that have certain characteristics.

    Args:
        starts_with:
            Keep notes which file name starts with the string
        ends_with:
            Keep notes which file name ends with the string
        pattern:
            Keep notes which file name matches the regex pattern
        has_meta:
            keep notes which contains the specified metadata.
            has_meta is a list of tuples:
            (key_name, l_values, meta_type)
            that correspond to the arguments of NoteMetadata.has()
    """
    if starts_with is not None:
        self.notes = [
            n for n in self.notes if str(n.path.name).startswith(starts_with)
        ]
    if ends_with is not None:
        self.notes = [n for n in self.notes if str(n.path.name).endswith(ends_with)]
    if pattern is not None:
        self.notes = [n for n in self.notes if re.match(pattern, str(n.path.name))]
    if has_meta is not None:
        include: list[bool] = []
        for note in self.notes:
            inc = True
            for (k, vals, meta_type) in has_meta:
                if not note.metadata.has(k=k, l=vals, meta_type=meta_type):
                    inc = False
            include.append(inc)
        self.notes = [n for (n, inc) in zip(self.notes, include) if inc]

update_content(inline_position='bottom', inline_inplace=True, inline_tml='standard', write=False)

Updates the content of all notes.

See Note.update_content for argument details.

Source code in pyomd/note.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def update_content(
    self,
    inline_position: str = "bottom",
    inline_inplace: bool = True,
    inline_tml: Union[str, Callable] = "standard",  # type: ignore
    write: bool = False,
):
    """Updates the content of all notes.

    See `Note.update_content` for argument details.
    """
    for note in self.notes:
        note.update_content(
            inline_position=inline_position,
            inline_inplace=inline_inplace,
            inline_tml=inline_tml,
            write=write,
        )

write()

Writes the note's content to disk.

See Note.write for argument details.

Source code in pyomd/note.py
265
266
267
268
269
270
271
def write(self):
    """Writes the note's content to disk.

    See `Note.write` for argument details.
    """
    for note in self.notes:
        note.write()