Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1430d5215a | ||
|
|
c09c5ffa47 | ||
|
|
ed7e69c07e | ||
|
|
f286f92b1f | ||
|
|
e7031a3ae4 | ||
|
|
2f828735a8 | ||
|
|
78c62cfc95 | ||
|
|
ed93614ca3 | ||
|
|
fac26a6ca0 | ||
|
|
48761f62a2 | ||
|
|
dc03bff324 | ||
|
|
e9a52859f6 | ||
|
|
1a10cf2e5f | ||
|
|
1c2d82dc9b | ||
|
|
6ecfa9b954 | ||
|
|
c138f74460 | ||
|
|
8becc7dbc4 | ||
|
|
b29cd7b5f7 | ||
|
|
f21ef9250a | ||
|
|
fa2a92bf89 | ||
|
|
8341411be4 | ||
|
|
22d6c7991e | ||
|
|
795b7f0321 | ||
|
|
9e34e64449 | ||
|
|
ce4cfdd169 | ||
|
|
12b1f183f7 | ||
|
|
4212c7b9e0 | ||
|
|
7794846185 | ||
|
|
150e067039 | ||
|
|
f347fde0c8 | ||
|
|
ff3d5c4841 | ||
|
|
292362135d | ||
|
|
aae5434451 | ||
|
|
2390530d92 | ||
|
|
9ab3ff75b0 | ||
|
|
d25c4e8c81 | ||
|
|
4a7efa27c8 | ||
|
|
cec0b4b4f6 | ||
|
|
a1922c50c1 | ||
|
|
870120bc13 | ||
|
|
f38cf73485 |
57
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
57
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
@@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report (script bug or unexpected script behavior)
|
|
||||||
about: Create a bug report for generated scripts to help privacy.sexy improve
|
|
||||||
labels: bug
|
|
||||||
title: '[BUG]: '
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for reporting an issue with generated script(s).
|
|
||||||
Please fill in as much of the template below as you're able.
|
|
||||||
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### OS
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Which OS are you using? What version of OS you were using?
|
|
||||||
On Windows: Open "Start button" > "Settings" > "System" > "About".
|
|
||||||
On macOS: Open "Apple menu (top left corner)" > "About This Mac".
|
|
||||||
On Linux: Open terminal > type: lsb_release -a > copy paste the result.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Reproduction steps
|
|
||||||
|
|
||||||
<!--
|
|
||||||
How can the bug be recreated?
|
|
||||||
It's the most important information in the bug report. Bugs that cannot be reproduced cannot be fixed and verified.
|
|
||||||
E.g.
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Scripts
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, please attach the generated privacy.sexy file instead of copy pasting which becomes too long.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Additional information
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, add any other context about the problem here.
|
|
||||||
-->
|
|
||||||
114
.github/ISSUE_TEMPLATE/1-bug-report-scripts.yaml
vendored
Normal file
114
.github/ISSUE_TEMPLATE/1-bug-report-scripts.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
name: "Bug Report: Script Issues"
|
||||||
|
description: 🐛 Report issues with generated scripts to enhance privacy.sexy
|
||||||
|
labels: [ 'bug' ]
|
||||||
|
title: '[Bug]: '
|
||||||
|
body:
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
|
||||||
|
Please complete as much of the form below as possible.
|
||||||
|
Your feedback is valuable, even if you can't provide all details.
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "After running the cleanup script, music playback stopped functioning."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: How can the bug be recreated?
|
||||||
|
description: |-
|
||||||
|
This is the most important information in the bug report.
|
||||||
|
Bugs that cannot be reproduced cannot be fixed or verified.
|
||||||
|
placeholder: |-
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
description: |-
|
||||||
|
Please specify your operating system and its version.
|
||||||
|
|
||||||
|
- On Windows: Open "Start button" > "Settings" > "System" > "About".
|
||||||
|
- On macOS: Open "Apple menu (top left corner)" > "About This Mac".
|
||||||
|
- On Linux: Open terminal > type: lsb_release -a > copy paste the result.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Windows 11 Pro 22H3"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Script file
|
||||||
|
description: |-
|
||||||
|
If applicable, share the generated privacy.sexy file.
|
||||||
|
|
||||||
|
GitHub may restrict script file attachments.
|
||||||
|
Upload your script file to a service like [GitHub Gist](https://gist.github.com/) and share the link here.
|
||||||
|
|
||||||
|
If you used the desktop version to run the script, it is already stored on your system.
|
||||||
|
See the [documentation to locate it](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/desktop/desktop-vs-web-features.md#secure-script-executionstorage).
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach script files by dragging them into this area.
|
||||||
|
placeholder: |-
|
||||||
|
Attach the script, or post GitHub Gist link.
|
||||||
|
For example: https://gist.github.com/privacysexy-forks/6d85ad8ca27acc8c6a5417d4af28c9b6.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Screenshots
|
||||||
|
description: |-
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach screenshots by clicking this area to highlight it and then pasting them or dragging files in.
|
||||||
|
placeholder: Attach screenshots here or link to image hosting.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional information
|
||||||
|
description: |-
|
||||||
|
If applicable, add any other context about the problem here.
|
||||||
|
|
||||||
|
Helpful information includes:
|
||||||
|
|
||||||
|
- Application logs (desktop version only), see: [how to find application logs](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/desktop/desktop-vs-web-features.md#logging).
|
||||||
|
- Terminal output
|
||||||
|
- Proposed solutions
|
||||||
|
- Other related context such as related issues, software behavior, etc.
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach log files by dragging them into this area.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Here are the logs I get from the privacy.sexy 0.13.2 desktop application: ..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
---
|
||||||
|
|
||||||
|
**✉️ A friendly note from the maintainer:**
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> We are a small open-source project with a small community.
|
||||||
|
> It can sometimes take a long time for issues to be addressed, so please be patient.
|
||||||
|
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
|
||||||
|
> But your issue will eventually get attention regardless.
|
||||||
|
> <p align="right">@undergroundwires</p>
|
||||||
|
|
||||||
|
---
|
||||||
104
.github/ISSUE_TEMPLATE/2-bug-report-general.yaml
vendored
Normal file
104
.github/ISSUE_TEMPLATE/2-bug-report-general.yaml
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
name: "Bug Report: General"
|
||||||
|
description: 🐛 Report general issues to enhance privacy.sexy
|
||||||
|
labels: [ 'bug' ]
|
||||||
|
title: '[Bug]: '
|
||||||
|
body:
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
|
||||||
|
Please complete as much of the form below as possible.
|
||||||
|
Your feedback is valuable, even if you can't provide all details.
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Provide a clear and concise description of the issue.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "I cannot select any scripts."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Reproduction steps
|
||||||
|
description: |-
|
||||||
|
This is the most important information in the bug report.
|
||||||
|
Bugs that cannot be reproduced cannot be fixed or verified.
|
||||||
|
placeholder: |-
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: Describe what you expected to happen when the error occurred.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "I expected the settings menu to open smoothly without crashing.".
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Screenshots
|
||||||
|
description: |-
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach screenshots by clicking this area to highlight it and then pasting them or dragging files in.
|
||||||
|
placeholder: >-
|
||||||
|
Attach screenshots here or link to image hosting.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: privacy.sexy environment details
|
||||||
|
description: |-
|
||||||
|
If applicable, mention how you were using privacy.sexy when the bug occurred:
|
||||||
|
|
||||||
|
- Web (on which operating system and browser?)
|
||||||
|
- Or desktop (Windows, macOS, or Linux?)
|
||||||
|
placeholder: >-
|
||||||
|
For example: "The web version on Edge browser on Windows 11 23H2."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: |-
|
||||||
|
If applicable, add any other context about the problem here.
|
||||||
|
|
||||||
|
Helpful information includes:
|
||||||
|
|
||||||
|
- Application logs (desktop version only), see: [how to find application logs](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/desktop/desktop-vs-web-features.md#logging).
|
||||||
|
- Terminal output
|
||||||
|
- Proposed solutions
|
||||||
|
- Other related context such as related issues, software behavior, etc.
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach log files by dragging them into this area.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Here are the logs I get from the privacy.sexy 0.13.2 desktop application: ..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
---
|
||||||
|
|
||||||
|
**✉️ A friendly note from the maintainer:**
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> We are a small open-source project with a small community.
|
||||||
|
> It can sometimes take a long time for issues to be addressed, so please be patient.
|
||||||
|
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
|
||||||
|
> But your issue will eventually get attention regardless.
|
||||||
|
> <p align="right">@undergroundwires</p>
|
||||||
|
|
||||||
|
---
|
||||||
55
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
55
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report (unrelated to generated scripts)
|
|
||||||
about: Create a bug report that's not related to generated scripts to help privacy.sexy improve
|
|
||||||
labels: bug
|
|
||||||
title: '[BUG]: '
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for reporting an issue.
|
|
||||||
Please fill in as much of the template below as you're able.
|
|
||||||
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Reproduction steps
|
|
||||||
|
|
||||||
<!--
|
|
||||||
It's the most important information in the bug report. Bugs that cannot be reproduced cannot be fixed and verified.
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Expected behavior
|
|
||||||
|
|
||||||
<!--
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Distribution
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, mention how you were using privacy.sexy when the bug was encountered:
|
|
||||||
- Web (on Desktop or mobile?)
|
|
||||||
- Or desktop (Windows, macOS or Linux?)
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Additional context
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, add any other context about the problem here.
|
|
||||||
-->
|
|
||||||
36
.github/ISSUE_TEMPLATE/3-feature-request.md
vendored
36
.github/ISSUE_TEMPLATE/3-feature-request.md
vendored
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for privacy.sexy
|
|
||||||
labels: enhancement
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for suggesting an idea to improve privacy better 🤗.
|
|
||||||
Please fill in as much of the template below as you're able.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Problem description
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What are we trying to solve?
|
|
||||||
Please add a clear and concise description of the problem you are seeking to solve with this feature request.
|
|
||||||
E.g. I'm always frustrated when [...]
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Proposed solution
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe the solution you'd like in a clear and concise manner.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Alternatives considered
|
|
||||||
|
|
||||||
<!--
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Additional information
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If applicable, add any other context or screenshots about the feature request here.
|
|
||||||
-->
|
|
||||||
73
.github/ISSUE_TEMPLATE/3-suggestion-feature.yaml
vendored
Normal file
73
.github/ISSUE_TEMPLATE/3-suggestion-feature.yaml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
name: "Suggestion: Feature"
|
||||||
|
description: 💡 Suggest new ideas to enhance privacy.sexy
|
||||||
|
labels: [ 'enhancement' ]
|
||||||
|
title: '[Feature]: '
|
||||||
|
body:
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
|
||||||
|
Please complete as much of the form below as possible.
|
||||||
|
Your feedback is valuable, even if you can't provide all details.
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Problem statement
|
||||||
|
description: |-
|
||||||
|
What are we trying to solve?
|
||||||
|
|
||||||
|
Please add a clear and concise description of the problem you are seeking to solve with this feature request.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Every time I use the app, I struggle with..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Proposed solution
|
||||||
|
description: |-
|
||||||
|
Describe the solution you'd like in a clear and concise manner.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "It would be great if the app could..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Alternatives considered
|
||||||
|
description: |-
|
||||||
|
Have you considered any alternative solutions or features?
|
||||||
|
Different perspectives can inspire new ideas.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "We could also solve it by...".
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional information
|
||||||
|
description: |-
|
||||||
|
If applicable, add any other context or screenshots about the feature request here.
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach files or screenshots by dragging them into this area.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Challenges can be ..., but I'm unsure about ..., here is some documentation about it: ..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
---
|
||||||
|
|
||||||
|
**✉️ A friendly note from the maintainer:**
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> We are a small open-source project with a small community.
|
||||||
|
> It can sometimes take a long time for issues to be addressed, so please be patient.
|
||||||
|
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
|
||||||
|
> But your issue will eventually get attention regardless.
|
||||||
|
> <p align="right">@undergroundwires</p>
|
||||||
|
|
||||||
|
---
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
---
|
|
||||||
name: New script suggestion
|
|
||||||
about: Suggest a new script for privacy.sexy
|
|
||||||
labels: enhancement
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for contributing to privacy.sexy! 🌟
|
|
||||||
For guidance, see our script guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md.
|
|
||||||
Consider submitting a PR for faster implementation: https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md#extend-scripts.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Operating system
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Specify the OS: Windows, macOS, or Linux.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Name
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Suggest a name for the script.
|
|
||||||
Naming conventions: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#name.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Code
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Provide or explain the code to execute when the script runs.
|
|
||||||
Code guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#code.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Revert code
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Include code to revert changes to the default state.
|
|
||||||
Leave blank for non-reversible scripts.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Category
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Suggest a category for the script.
|
|
||||||
If unsure, leave blank for maintainers to decide.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Recommendation level
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Suggest a recommendation level: STANDARD (non-breaking), STRICT (limits functionality), or NONE (for advanced users).
|
|
||||||
If unsure, leave blank for maintainers to decide.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Documentation/References
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Provide any relevant documentation or references.
|
|
||||||
Prefer high-quality sources such as vendor documentation.
|
|
||||||
Documentation guidelines: https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#documentation.
|
|
||||||
-->
|
|
||||||
133
.github/ISSUE_TEMPLATE/4-suggestion-new-script.yaml
vendored
Normal file
133
.github/ISSUE_TEMPLATE/4-suggestion-new-script.yaml
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
name: "Suggestion: New Script"
|
||||||
|
description: 💡 Suggest new scripts to enhance privacy.sexy
|
||||||
|
labels: [ 'enhancement' ]
|
||||||
|
title: '[New script]: '
|
||||||
|
body:
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
Thank you for contributing to privacy.sexy and guiding our direction! 🌟
|
||||||
|
Please complete as much of the form below as possible.
|
||||||
|
Your feedback is valuable, even if you can't provide all details.
|
||||||
|
|
||||||
|
For guidance, see our [script guidelines](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md).
|
||||||
|
Consider submitting a PR to get your script added more quickly: (see [CONTRIBUTING.md](https://github.com/undergroundwires/privacy.sexy/blob/master/CONTRIBUTING.md#extend-scripts))
|
||||||
|
-
|
||||||
|
type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
description: Which operating system will the new script configure?
|
||||||
|
options:
|
||||||
|
- macOS
|
||||||
|
- Windows
|
||||||
|
- Linux
|
||||||
|
- All of them
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Name of the script
|
||||||
|
description: |-
|
||||||
|
Suggest a name for the script that clearly describes its function.
|
||||||
|
|
||||||
|
See [script naming conventions](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#name) for best practices.
|
||||||
|
placeholder: E.g, "Disable error data submission"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Documentation/References
|
||||||
|
description: |-
|
||||||
|
Provide any relevant documentation or references.
|
||||||
|
Prefer high-quality sources such as vendor documentation.
|
||||||
|
|
||||||
|
See [documentation guidelines](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#documentation) for best practices.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "This script will disable the error data submission, see https://microsoft.com/...".
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Code
|
||||||
|
description: |-
|
||||||
|
If possible, provide or explain the code that the script should execute.
|
||||||
|
|
||||||
|
See [script code guidelines](https://github.com/undergroundwires/privacy.sexy/blob/master/docs/script-guidelines.md#code).
|
||||||
|
placeholder: |-
|
||||||
|
For example: "Set registry key like this `reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" /v "AllowTelemetry" /t "REG_DWORD" /d "1"`".
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Revert code
|
||||||
|
description: |-
|
||||||
|
If applicable, provide revert code to restore the changes made by the script.
|
||||||
|
|
||||||
|
The revert code restores changes to their default state before script execution.
|
||||||
|
|
||||||
|
Leave blank for non-reversible scripts.
|
||||||
|
placeholder: |-
|
||||||
|
For example: "Revert to operating system default like this `reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" /v "AllowTelemetry" /t "REG_DWORD" /d "0"`".
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Suggested category
|
||||||
|
description: |-
|
||||||
|
Suggest a category for the script.
|
||||||
|
|
||||||
|
If unsure, leave blank for maintainers to decide.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Privacy Cleanup > Clear system logs"
|
||||||
|
-
|
||||||
|
type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Recommendation level
|
||||||
|
description: |-
|
||||||
|
Suggest a recommendation level for the script:
|
||||||
|
|
||||||
|
- **Standard**: Recommended for most users without side-effects.
|
||||||
|
- **Strict**: Provides improved privacy at the cost of some functionality.
|
||||||
|
- **None**: For advanced users or specific needs.
|
||||||
|
|
||||||
|
If unsure, leave blank for maintainers to decide.
|
||||||
|
options:
|
||||||
|
- Standard
|
||||||
|
- Strict
|
||||||
|
- None (do not recommend)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional information
|
||||||
|
description: |-
|
||||||
|
If applicable, add any other context or screenshots about the script request here.
|
||||||
|
|
||||||
|
> **💡 Tip:** You can attach additional documents or screenshots by dragging them into this area or pasting directly.
|
||||||
|
placeholder: >-
|
||||||
|
For example: "Challenges can be ..., but I am unsure about ..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
-
|
||||||
|
type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |-
|
||||||
|
---
|
||||||
|
|
||||||
|
**✉️ A friendly note from the maintainer:**
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> We are a small open-source project with a small community.
|
||||||
|
> It can sometimes take a long time for issues to be addressed, so please be patient.
|
||||||
|
> Consider [donating](https://undergroundwires.dev/donate) to keep privacy.sexy alive and improve support ❤️.
|
||||||
|
> But your issue will eventually get attention regardless.
|
||||||
|
> <p align="right">@undergroundwires</p>
|
||||||
|
|
||||||
|
---
|
||||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1 +1,7 @@
|
|||||||
|
# This file must be named `config.yml`. GitHub does not recognize the file if it is named `config.yaml`.
|
||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Donate
|
||||||
|
url: https://undergroundwires.dev/donate/
|
||||||
|
about: ❤️ Donate to support the free software you love to keep it alive.
|
||||||
|
# A separate link for reporting vulnerabilities is not included here because GitHub generates it automatically.
|
||||||
|
|||||||
15
.github/actions/upload-artifact/action.yaml
vendored
Normal file
15
.github/actions/upload-artifact/action.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
inputs:
|
||||||
|
name:
|
||||||
|
required: true
|
||||||
|
path:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.name }}
|
||||||
|
path: ${{ inputs.path }}
|
||||||
9
.github/workflows/checks.build.yaml
vendored
9
.github/workflows/checks.build.yaml
vendored
@@ -72,16 +72,19 @@ jobs:
|
|||||||
build-docker:
|
build-docker:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu ] # Windows runners do not support Linux containers
|
os:
|
||||||
|
- macos-13 # Downgraded due to lack of nested virtualization support in ARM-based runners (See: actions/runner-images#9460, actions/runner-images#9741, abiosoft/colima#1023)
|
||||||
|
- ubuntu-latest
|
||||||
|
# - windows-latest # Windows runners do not support Linux containers
|
||||||
fail-fast: false # Allows to see results from other combinations
|
fail-fast: false # Allows to see results from other combinations
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
-
|
-
|
||||||
name: Install Docker on macOS
|
name: Install Docker on macOS
|
||||||
if: matrix.os == 'macos' # macOS runner is missing Docker
|
if: contains(matrix.os, 'macos') # macOS runner is missing Docker
|
||||||
run: |-
|
run: |-
|
||||||
# Install Docker
|
# Install Docker
|
||||||
brew install docker
|
brew install docker
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ jobs:
|
|||||||
run-check:
|
run-check:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos, ubuntu, windows ]
|
os:
|
||||||
|
- macos-latest # Apple silicon (ARM64)
|
||||||
|
- macos-13 # Intel-based (x86-64)
|
||||||
|
- ubuntu-latest
|
||||||
|
- windows-latest
|
||||||
fail-fast: false # Allows to see results from other combinations
|
fail-fast: false # Allows to see results from other combinations
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
@@ -24,7 +28,7 @@ jobs:
|
|||||||
uses: ./.github/actions/npm-install-dependencies
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
-
|
||||||
name: Configure Ubuntu
|
name: Configure Ubuntu
|
||||||
if: matrix.os == 'ubuntu'
|
if: contains(matrix.os, 'ubuntu') # macOS runner is missing Docker
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update
|
sudo apt update
|
||||||
@@ -66,7 +70,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Upload screenshot
|
name: Upload screenshot
|
||||||
if: always() # Run even if previous step fails
|
if: always() # Run even if previous step fails
|
||||||
uses: actions/upload-artifact@v3
|
uses: ./.github/actions/upload-artifact
|
||||||
with:
|
with:
|
||||||
name: screenshot-${{ matrix.os }}
|
name: screenshot-${{ matrix.os }}
|
||||||
path: screenshot.png
|
path: screenshot.png
|
||||||
|
|||||||
27
.github/workflows/checks.quality.yaml
vendored
27
.github/workflows/checks.quality.yaml
vendored
@@ -4,7 +4,7 @@ on: [ push, pull_request ]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
lint-command:
|
lint-command:
|
||||||
@@ -74,3 +74,28 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Analyzing the code with pylint
|
name: Analyzing the code with pylint
|
||||||
run: npm run lint:pylint
|
run: npm run lint:pylint
|
||||||
|
|
||||||
|
validate-collection-files:
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ macos, ubuntu, windows ]
|
||||||
|
fail-fast: false # Still interested to see results from other combinations
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Setup node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
-
|
||||||
|
name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
-
|
||||||
|
name: Install dependencies
|
||||||
|
run: python3 -m pip install -r ./scripts/validate-collections-yaml/requirements.txt
|
||||||
|
-
|
||||||
|
name: Validate
|
||||||
|
run: python3 ./scripts/validate-collections-yaml
|
||||||
|
|||||||
4
.github/workflows/tests.e2e.yaml
vendored
4
.github/workflows/tests.e2e.yaml
vendored
@@ -51,14 +51,14 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Upload screenshots
|
name: Upload screenshots
|
||||||
if: failure() # Run only if previous steps fail because screenshots will be generated only if E2E test failed
|
if: failure() # Run only if previous steps fail because screenshots will be generated only if E2E test failed
|
||||||
uses: actions/upload-artifact@v3
|
uses: ./.github/actions/upload-artifact
|
||||||
with:
|
with:
|
||||||
name: e2e-screenshots-${{ matrix.os }}
|
name: e2e-screenshots-${{ matrix.os }}
|
||||||
path: ${{ steps.artifacts.outputs.SCREENSHOTS_DIR }}
|
path: ${{ steps.artifacts.outputs.SCREENSHOTS_DIR }}
|
||||||
-
|
-
|
||||||
name: Upload videos
|
name: Upload videos
|
||||||
if: always() # Run even if previous steps fail because test run video is always captured
|
if: always() # Run even if previous steps fail because test run video is always captured
|
||||||
uses: actions/upload-artifact@v3
|
uses: ./.github/actions/upload-artifact
|
||||||
with:
|
with:
|
||||||
name: e2e-videos-${{ matrix.os }}
|
name: e2e-videos-${{ matrix.os }}
|
||||||
path: ${{ steps.artifacts.outputs.VIDEOS_DIR }}
|
path: ${{ steps.artifacts.outputs.VIDEOS_DIR }}
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,3 +14,7 @@ node_modules
|
|||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
.venv
|
||||||
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -5,8 +5,10 @@
|
|||||||
"wengerk.highlight-bad-chars", // Highlights bad chars.
|
"wengerk.highlight-bad-chars", // Highlights bad chars.
|
||||||
"wayou.vscode-todo-highlight", // Highlights TODO.
|
"wayou.vscode-todo-highlight", // Highlights TODO.
|
||||||
"wix.vscode-import-cost", // Shows in KB how much a require include in code.
|
"wix.vscode-import-cost", // Shows in KB how much a require include in code.
|
||||||
// Documentation
|
// Markdown
|
||||||
"davidanson.vscode-markdownlint", // Lints markdown.
|
"davidanson.vscode-markdownlint", // Lints markdown.
|
||||||
|
// YAML
|
||||||
|
"redhat.vscode-yaml", // Lints YAML files, validates against schema.
|
||||||
// TypeScript / JavaScript
|
// TypeScript / JavaScript
|
||||||
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
|
"dbaeumer.vscode-eslint", // Lints JavaScript/TypeScript.
|
||||||
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.
|
"pmneo.tsimporter", // Provides better auto-complete for TypeScripts imports.
|
||||||
|
|||||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,5 +1,54 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.13.4 (2024-05-27)
|
||||||
|
|
||||||
|
* Add specific empty function name compiler error | [870120b](https://github.com/undergroundwires/privacy.sexy/commit/870120bc13909a3681e0f0a2351806849476342f)
|
||||||
|
* ci/cd: fix recent Docker build failures on macOS | [a1922c5](https://github.com/undergroundwires/privacy.sexy/commit/a1922c50c12b3b7806e9e681ace842194a178bda)
|
||||||
|
* win: standardize registry edit + delete on revert | [cec0b4b](https://github.com/undergroundwires/privacy.sexy/commit/cec0b4b4f63c3563a0e7923ce6324a38d71a3955)
|
||||||
|
* Fix e2e test failing on Windows | [4a7efa2](https://github.com/undergroundwires/privacy.sexy/commit/4a7efa27c8df73ef9b7960afed29f216b066cba2)
|
||||||
|
* Add support for macOS universal binary #348, #362 | [d25c4e8](https://github.com/undergroundwires/privacy.sexy/commit/d25c4e8c812b8d012010ba38070a2931dcd28908)
|
||||||
|
* Migrate to GitHub issue forms | [9ab3ff7](https://github.com/undergroundwires/privacy.sexy/commit/9ab3ff75b0a69ac2ba27dd02e82db9b5bd76ea0f)
|
||||||
|
* ci/cd: fix quality checks not running on all OSes | [2390530](https://github.com/undergroundwires/privacy.sexy/commit/2390530d929fb92c266558c52376569a0ecb90c1)
|
||||||
|
* Bump Vue to latest and fix universal selector CSS | [aae5434](https://github.com/undergroundwires/privacy.sexy/commit/aae54344511ec51d17ad0420a92cb5a064e0e7bb)
|
||||||
|
* Centralize and optimize `ResizeObserver` usage | [2923621](https://github.com/undergroundwires/privacy.sexy/commit/292362135db0519ec1050bab80ed373aad115731)
|
||||||
|
* win: improve app access disabling and docs #138 | [ff3d5c4](https://github.com/undergroundwires/privacy.sexy/commit/ff3d5c48419f663379f5aba8936636c22f2c5de8)
|
||||||
|
* win: document and discourage RSA key script #363 | [f347fde](https://github.com/undergroundwires/privacy.sexy/commit/f347fde0c85f8b51b0060fdea0a2724b042aaeed)
|
||||||
|
* win: improve printing removal /w Print Queue #279 | [150e067](https://github.com/undergroundwires/privacy.sexy/commit/150e0670392bb62348c20ec644a4ed8a6bbffe74)
|
||||||
|
* win: discourage blocking app access #121 #339 #350 | [7794846](https://github.com/undergroundwires/privacy.sexy/commit/77948461856e6837ddfbcbbef72a1bf9fc706b4e)
|
||||||
|
* Improve context for errors thrown by compiler | [4212c7b](https://github.com/undergroundwires/privacy.sexy/commit/4212c7b9e0b1500378a1e4e88efc2d59f39f3d29)
|
||||||
|
* win: document disabling firewall #115 #152 #364 | [12b1f18](https://github.com/undergroundwires/privacy.sexy/commit/12b1f183f7ce966d6ce090d98aeea7ec491f8c7c)
|
||||||
|
* win: add script to disable Recall feature | [ce4cfdd](https://github.com/undergroundwires/privacy.sexy/commit/ce4cfdd169b7da0edc3da61143c988ed5f3c976e)
|
||||||
|
* win, mac, linux: fix typos and dead URLs #367 | [9e34e64](https://github.com/undergroundwires/privacy.sexy/commit/9e34e644493674ca709b64a47206763d5d4bd60c)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.3...0.13.4)
|
||||||
|
|
||||||
|
## 0.13.3 (2024-05-11)
|
||||||
|
|
||||||
|
* win: organize and document network disablement | [2eed6f4](https://github.com/undergroundwires/privacy.sexy/commit/2eed6f4afb6cf85fdc1d6acb808f82405a35cafd)
|
||||||
|
* win: improve disabling SMBv1 protocol | [f584fab](https://github.com/undergroundwires/privacy.sexy/commit/f584fabb50c7de70ba43751d721af94d8fa2fa8a)
|
||||||
|
* win: improve disabling insecure renegotiations | [f261ab4](https://github.com/undergroundwires/privacy.sexy/commit/f261ab4cd9a53e31325e5c6da9129542971fe84b)
|
||||||
|
* win: doc, improve, encourage cipher disabling | [8b224ee](https://github.com/undergroundwires/privacy.sexy/commit/8b224eefe71be6a556a1085d8fe20dbd4b889430)
|
||||||
|
* ci/cd: add check for TODO comments | [4e21f05](https://github.com/undergroundwires/privacy.sexy/commit/4e21f05031d6cc90cda684bd598bec4735f8103b)
|
||||||
|
* win: improve 'Snipping Tool' removal #343 | [e18907c](https://github.com/undergroundwires/privacy.sexy/commit/e18907ca91e483255b44d14d7d923d7eef92afbd)
|
||||||
|
* ci/cd: lint Python scripts using `pylint` | [23bac0f](https://github.com/undergroundwires/privacy.sexy/commit/23bac0fc76ad697abb34f3fb327df5cdeb40286a)
|
||||||
|
* win: improve disabling insecure hashes #131 | [d19dde6](https://github.com/undergroundwires/privacy.sexy/commit/d19dde603ddac47022ee2e0ea865d53857560c26)
|
||||||
|
* Add system requirements documentation #134 | [0fc2ffc](https://github.com/undergroundwires/privacy.sexy/commit/0fc2ffc1ea36a9248c6a92da85a29f7b04b33796)
|
||||||
|
* win, linux, mac: fix various typos #349 | [694bf1a](https://github.com/undergroundwires/privacy.sexy/commit/694bf1a74d935531d7cd46891823af1fa58c3c8c)
|
||||||
|
* Fix script cancellation with new dialog on Linux | [8c17396](https://github.com/undergroundwires/privacy.sexy/commit/8c173962857a39dc0c9e5886cb2af4937e6618e7)
|
||||||
|
* win: improve disabling protocols | [4ef16ce](https://github.com/undergroundwires/privacy.sexy/commit/4ef16cea56789120cd041412d86b5577cccf0725)
|
||||||
|
* win: fix Copilot by excluding `r.bing.com` #329 | [66a5688](https://github.com/undergroundwires/privacy.sexy/commit/66a56888a4b3ead1a6bfef0feffa0218535701fe)
|
||||||
|
* Fix blank window on load on desktop version #348 | [813d820](https://github.com/undergroundwires/privacy.sexy/commit/813d820b85e1b623c50f8e0325ad372bf2f344f9)
|
||||||
|
* Improve desktop icon quality and generation | [ab25e0a](https://github.com/undergroundwires/privacy.sexy/commit/ab25e0a066be14ea979dafd0f80e1091bd5d33f8)
|
||||||
|
* win: improve enabling secure connections #175 | [c75df1c](https://github.com/undergroundwires/privacy.sexy/commit/c75df1c8c1151b64cbf014383dea0b748a8c78b3)
|
||||||
|
* Fix VSCode script issues with added CI/CD tests | [1d7cafc](https://github.com/undergroundwires/privacy.sexy/commit/1d7cafc831dcc339a10646794410dad7096bfe60)
|
||||||
|
* Fix win execution with whitespace in username #351 | [a334320](https://github.com/undergroundwires/privacy.sexy/commit/a3343205b1196d5a81fd3cee2ae661ce871a7bef)
|
||||||
|
* Fix misaligned tooltip positions in modal dialogs | [dd71536](https://github.com/undergroundwires/privacy.sexy/commit/dd71536316ec819caeb418b8635d544ac80e58ad)
|
||||||
|
* Fix Chromium scrollbar-induced layout shifts | [bc4879c](https://github.com/undergroundwires/privacy.sexy/commit/bc4879cfe97becac3c54f6b40780a89464d3b772)
|
||||||
|
* ci/cd: remove `check-latest` from `setup-node` | [52a4730](https://github.com/undergroundwires/privacy.sexy/commit/52a4730073b8ebfb2ce9d530b44e4a179f5849fe)
|
||||||
|
* win: categorize and rename network security #131 | [9fd193e](https://github.com/undergroundwires/privacy.sexy/commit/9fd193e676f1f0646898f5130fbfaaf25050b2e3)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.2...0.13.3)
|
||||||
|
|
||||||
## 0.13.2 (2024-04-15)
|
## 0.13.2 (2024-04-15)
|
||||||
|
|
||||||
* Update documentation for `logo-update.js` script | [4a9b430](https://github.com/undergroundwires/privacy.sexy/commit/4a9b430702bc6082426b50ecc3a06362b5720796)
|
* Update documentation for `logo-update.js` script | [4a9b430](https://github.com/undergroundwires/privacy.sexy/commit/4a9b430702bc6082426b50ecc3a06362b5720796)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
||||||
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.2/privacy.sexy-Setup-0.13.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.2/privacy.sexy-0.13.2.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.2/privacy.sexy-0.13.2.AppImage). For more options, see [here](#additional-install-options).
|
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.4/privacy.sexy-Setup-0.13.4.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.4/privacy.sexy-0.13.4.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.4/privacy.sexy-0.13.4.AppImage). For more options, see [here](#additional-install-options).
|
||||||
|
|
||||||
See also:
|
See also:
|
||||||
|
|
||||||
@@ -186,3 +186,7 @@ Check [architecture.md](./docs/architecture.md) for an overview of design and ho
|
|||||||
Security is a top priority at privacy.sexy.
|
Security is a top priority at privacy.sexy.
|
||||||
An extensive commitment to security verification ensures this priority.
|
An extensive commitment to security verification ensures this priority.
|
||||||
For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).
|
For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).
|
||||||
|
|
||||||
|
## Supporters
|
||||||
|
|
||||||
|
[](https://undergroundwires.dev/supporters)
|
||||||
|
|||||||
@@ -41,5 +41,5 @@ Application layer compiles templating syntax during parsing to create the end sc
|
|||||||
|
|
||||||
The steps to extend the templating syntax:
|
The steps to extend the templating syntax:
|
||||||
|
|
||||||
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
|
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Executable/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
|
||||||
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts).
|
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Executable/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts).
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Collection files
|
# Collection files
|
||||||
|
|
||||||
privacy.sexy is a data-driven application that reads YAML files.
|
privacy.sexy is a data-driven application that reads YAML files.
|
||||||
This document details the structure and syntax of the YAML files located in [`application/collections`](./../src/application/collections/), which form the backbone of the application's data model.
|
This document details the structure and syntax of the YAML files located in [`application/collections`](./../src/application/collections/), which form the backbone of the application's data model. The YAML schema [`.schema.yaml`](./../src/application/collections/.schema.yaml) is provided to provide better IDE support and be used in automated validations.
|
||||||
|
|
||||||
Related documentation:
|
Related documentation:
|
||||||
|
|
||||||
- 📖 [`collection.yaml.d.ts`](./../src/application/collections/collection.yaml.d.ts) outlines code types.
|
- 📖 [`Collections README`](./../src/application/collections/README.md) includes references to code as documentation.
|
||||||
- 📖 [Script Guidelines](./script-guidelines.md) provide guidance on script creation including best-practices.
|
- 📖 [Script Guidelines](./script-guidelines.md) provide guidance on script creation including best-practices.
|
||||||
|
|
||||||
## Objects
|
## Objects
|
||||||
@@ -28,11 +28,20 @@ Related documentation:
|
|||||||
- `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)**
|
- `scripting:` ***[`ScriptingDefinition`](#scriptingdefinition)*** **(required)**
|
||||||
- Sets the scripting language for all inline code used within the collection.
|
- Sets the scripting language for all inline code used within the collection.
|
||||||
|
|
||||||
### `Category`
|
### Executables
|
||||||
|
|
||||||
|
An Executable is a logical entity that can
|
||||||
|
|
||||||
|
- execute once compiled,
|
||||||
|
- include a `docs` property for documentation.
|
||||||
|
|
||||||
|
It's either [Category](#category) or a [Script](#script).
|
||||||
|
|
||||||
|
#### `Category`
|
||||||
|
|
||||||
Represents a logical group of scripts and subcategories.
|
Represents a logical group of scripts and subcategories.
|
||||||
|
|
||||||
#### `Category` syntax
|
##### `Category` syntax
|
||||||
|
|
||||||
- `category:` *`string`* **(required)**
|
- `category:` *`string`* **(required)**
|
||||||
- Name of the category.
|
- Name of the category.
|
||||||
@@ -43,7 +52,7 @@ Represents a logical group of scripts and subcategories.
|
|||||||
- `docs`: *`string`* | `[`*`string`*`, ... ]`
|
- `docs`: *`string`* | `[`*`string`*`, ... ]`
|
||||||
- Markdown-formatted documentation related to the category.
|
- Markdown-formatted documentation related to the category.
|
||||||
|
|
||||||
### `Script`
|
#### `Script`
|
||||||
|
|
||||||
Represents an individual tweak.
|
Represents an individual tweak.
|
||||||
|
|
||||||
@@ -58,7 +67,7 @@ Types (like [functions](#function)):
|
|||||||
|
|
||||||
📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md).
|
📖 For detailed guidelines, see [Script Guidelines](./script-guidelines.md).
|
||||||
|
|
||||||
#### `Script` syntax
|
##### `Script` syntax
|
||||||
|
|
||||||
- `name`: *`string`* **(required)**
|
- `name`: *`string`* **(required)**
|
||||||
- Script name.
|
- Script name.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ systems or configurations that haven't undergone official testing.
|
|||||||
|
|
||||||
- **Version:** Windows 10 and later.
|
- **Version:** Windows 10 and later.
|
||||||
- **Processor:** Intel Pentium 4 or later.
|
- **Processor:** Intel Pentium 4 or later.
|
||||||
- **Architecture:** 64-bit (x64), ARM.
|
- **Architecture:** 64-bit (x86-64), ARM (ARM64).
|
||||||
|
|
||||||
> **⚠️ Compatibility Note:**
|
> **⚠️ Compatibility Note:**
|
||||||
> ARM version is only compatible with Windows 11 and later.
|
> ARM version is only compatible with Windows 11 and later.
|
||||||
@@ -17,24 +17,20 @@ systems or configurations that haven't undergone official testing.
|
|||||||
## macOS
|
## macOS
|
||||||
|
|
||||||
- **Version:** macOS Catalina (10.15) and later.
|
- **Version:** macOS Catalina (10.15) and later.
|
||||||
- **Architecture:** Intel-based (64-bit), Apple Silicon (ARM).
|
- **Architecture:** Intel-based (x86-64), Apple silicon (ARM64).
|
||||||
|
|
||||||
> **⚠️ Compatibility Note:**
|
|
||||||
> Apple Silicon version runs non-natively, leading to slower performance due to emulation [2].
|
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
- **Version:** Ubuntu 18.04 and later, Fedora 32 and later, and Debian 10 and later.
|
- **Version:** Ubuntu 18.04 and later, Fedora 32 and later, and Debian 10 and later.
|
||||||
- **Processor:** Intel Pentium 4 or later.
|
- **Processor:** Intel Pentium 4 or later.
|
||||||
- **Architecture:** 64-bit (x64).
|
- **Architecture:** 64-bit (x86-64).
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
System requirements reflect Electron's platform capabilities [3] and Chromium's recommended configurations [4].
|
System requirements reflect Electron's platform capabilities [2] and Chromium's recommended configurations [3].
|
||||||
|
|
||||||
For details on the build process, see [electron-builder configuration file](./../../electron-builder.cjs).
|
For details on the build process, see [electron-builder configuration file](./../../electron-builder.cjs).
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20240428082726/https://learn.microsoft.com/en-us/windows/arm/add-arm-support#emulation-on-arm-based-devices-for-x86-or-x64-windows-apps "Add support Arm devices to your Windows app | Microsoft Learn | learn.microsoft.com"
|
[1]: https://web.archive.org/web/20240428082726/https://learn.microsoft.com/en-us/windows/arm/add-arm-support#emulation-on-arm-based-devices-for-x86-or-x64-windows-apps "Add support Arm devices to your Windows app | Microsoft Learn | learn.microsoft.com"
|
||||||
[2]: https://archive.today/2024.04.28-082901/https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary%23overview "Building a universal macOS binary | Apple Developer Documentation | developer.apple.com"
|
[2]: https://archive.ph/2024.04.28-082958/https://github.com/electron/electron/blob/main/README.md#platform-support "Platform Support | electron/README.md at main · electron/electron · GitHub | github.com"
|
||||||
[3]: https://archive.ph/2024.04.28-082958/https://github.com/electron/electron/blob/main/README.md#platform-support "Platform Support | electron/README.md at main · electron/electron · GitHub | github.com"
|
[3]: https://web.archive.org/web/20240428082945/https://support.google.com/chrome/a/answer/7100626?hl=en "Chrome browser system requirements - Chrome Enterprise and Education Help | support.google.com"
|
||||||
[4]: https://web.archive.org/web/20240428082945/https://support.google.com/chrome/a/answer/7100626?hl=en "Chrome browser system requirements - Chrome Enterprise and Education Help | support.google.com"
|
|
||||||
|
|||||||
@@ -80,8 +80,10 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
|||||||
- [**`npm run install-deps [-- <options>]`**](../scripts/npm-install.js):
|
- [**`npm run install-deps [-- <options>]`**](../scripts/npm-install.js):
|
||||||
- Manages NPM dependency installation, it offers capabilities like doing a fresh install, retries on network errors, and other features.
|
- Manages NPM dependency installation, it offers capabilities like doing a fresh install, retries on network errors, and other features.
|
||||||
- For example, you can run `npm run install-deps -- --fresh` to do clean installation of dependencies.
|
- For example, you can run `npm run install-deps -- --fresh` to do clean installation of dependencies.
|
||||||
- [**`python ./scripts/configure_vscode.py`**](../scripts/configure_vscode.py):
|
- [**`python3 ./scripts/configure_vscode.py`**](../scripts/configure_vscode.py):
|
||||||
- Optimizes Visual Studio Code settings and installs essential extensions, enhancing the development environment.
|
- Optimizes Visual Studio Code settings and installs essential extensions, enhancing the development environment.
|
||||||
|
- [**`python3 ./scripts/validate-collections-yaml`**](../scripts/validate-collections-yaml/README.md):
|
||||||
|
- Validates the syntax and structure of collection YAML files.
|
||||||
|
|
||||||
#### Automation scripts
|
#### Automation scripts
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ module.exports = {
|
|||||||
|
|
||||||
// macOS
|
// macOS
|
||||||
mac: {
|
mac: {
|
||||||
target: 'dmg',
|
target: {
|
||||||
|
target: 'dmg',
|
||||||
|
arch: 'universal',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dmg: {
|
dmg: {
|
||||||
artifactName: '${name}-${version}.${ext}',
|
artifactName: '${name}-${version}.${ext}',
|
||||||
|
|||||||
278
package-lock.json
generated
278
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/vue": "^1.0.6",
|
"@floating-ui/vue": "^1.0.6",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"electron-updater": "^6.1.9",
|
"electron-updater": "^6.1.9",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"vue": "^3.4.21"
|
"vue": "^3.4.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"@vue/test-utils": "^2.4.5",
|
"@vue/test-utils": "^2.4.5",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"cypress": "^13.7.3",
|
"cypress": "^13.7.3",
|
||||||
"electron": "^29.3.0",
|
"electron": "^31.0.2",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.13.3",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-vite": "^2.1.0",
|
"electron-vite": "^2.1.0",
|
||||||
@@ -522,9 +522,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.24.0",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||||
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
|
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
},
|
},
|
||||||
@@ -3947,49 +3947,49 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz",
|
||||||
"integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
|
"integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.24.4",
|
||||||
"@vue/shared": "3.4.21",
|
"@vue/shared": "3.4.27",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-dom": {
|
"node_modules/@vue/compiler-dom": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz",
|
||||||
"integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
|
"integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.4.21",
|
"@vue/compiler-core": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-sfc": {
|
"node_modules/@vue/compiler-sfc": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz",
|
||||||
"integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
|
"integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.24.4",
|
||||||
"@vue/compiler-core": "3.4.21",
|
"@vue/compiler-core": "3.4.27",
|
||||||
"@vue/compiler-dom": "3.4.21",
|
"@vue/compiler-dom": "3.4.27",
|
||||||
"@vue/compiler-ssr": "3.4.21",
|
"@vue/compiler-ssr": "3.4.27",
|
||||||
"@vue/shared": "3.4.21",
|
"@vue/shared": "3.4.27",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.7",
|
"magic-string": "^0.30.10",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.38",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-ssr": {
|
"node_modules/@vue/compiler-ssr": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz",
|
||||||
"integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
|
"integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.21",
|
"@vue/compiler-dom": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/eslint-config-airbnb": {
|
"node_modules/@vue/eslint-config-airbnb": {
|
||||||
@@ -4099,48 +4099,48 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz",
|
||||||
"integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
|
"integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-core": {
|
"node_modules/@vue/runtime-core": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz",
|
||||||
"integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
|
"integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.4.21",
|
"@vue/reactivity": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-dom": {
|
"node_modules/@vue/runtime-dom": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz",
|
||||||
"integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
|
"integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/runtime-core": "3.4.21",
|
"@vue/runtime-core": "3.4.27",
|
||||||
"@vue/shared": "3.4.21",
|
"@vue/shared": "3.4.27",
|
||||||
"csstype": "^3.1.3"
|
"csstype": "^3.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/server-renderer": {
|
"node_modules/@vue/server-renderer": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz",
|
||||||
"integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
|
"integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-ssr": "3.4.21",
|
"@vue/compiler-ssr": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "3.4.21"
|
"vue": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/shared": {
|
"node_modules/@vue/shared": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz",
|
||||||
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
|
"integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/test-utils": {
|
"node_modules/@vue/test-utils": {
|
||||||
"version": "2.4.5",
|
"version": "2.4.5",
|
||||||
@@ -6645,11 +6645,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron": {
|
"node_modules/electron": {
|
||||||
"version": "29.3.0",
|
"version": "31.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-29.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-31.0.2.tgz",
|
||||||
"integrity": "sha512-ZxFKm0/v48GSoBuO3DdnMlCYXefEUKUHLMsKxyXY4nZGgzbBKpF/X8haZa2paNj23CLfsCKBOtfc2vsEQiOOsA==",
|
"integrity": "sha512-55efQ5yfLN+AQHcFC00AXQqtxC3iAGaxX2GQ3EDbFJ0ca9GHNOdSXkcrdBElLleiDrR2hpXNkQxN1bDn0oxe6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/get": "^2.0.0",
|
"@electron/get": "^2.0.0",
|
||||||
"@types/node": "^20.9.0",
|
"@types/node": "^20.9.0",
|
||||||
@@ -10777,14 +10778,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.8",
|
"version": "0.30.10",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
|
||||||
"integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
|
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/map-age-cleaner": {
|
"node_modules/map-age-cleaner": {
|
||||||
@@ -16982,15 +16980,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz",
|
||||||
"integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
|
"integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.21",
|
"@vue/compiler-dom": "3.4.27",
|
||||||
"@vue/compiler-sfc": "3.4.21",
|
"@vue/compiler-sfc": "3.4.27",
|
||||||
"@vue/runtime-dom": "3.4.21",
|
"@vue/runtime-dom": "3.4.27",
|
||||||
"@vue/server-renderer": "3.4.21",
|
"@vue/server-renderer": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
@@ -17918,9 +17916,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.24.0",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||||
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg=="
|
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg=="
|
||||||
},
|
},
|
||||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||||
"version": "7.23.3",
|
"version": "7.23.3",
|
||||||
@@ -20277,49 +20275,49 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-core": {
|
"@vue/compiler-core": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz",
|
||||||
"integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
|
"integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.24.4",
|
||||||
"@vue/shared": "3.4.21",
|
"@vue/shared": "3.4.27",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-dom": {
|
"@vue/compiler-dom": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz",
|
||||||
"integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
|
"integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-core": "3.4.21",
|
"@vue/compiler-core": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-sfc": {
|
"@vue/compiler-sfc": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz",
|
||||||
"integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
|
"integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.24.4",
|
||||||
"@vue/compiler-core": "3.4.21",
|
"@vue/compiler-core": "3.4.27",
|
||||||
"@vue/compiler-dom": "3.4.21",
|
"@vue/compiler-dom": "3.4.27",
|
||||||
"@vue/compiler-ssr": "3.4.21",
|
"@vue/compiler-ssr": "3.4.27",
|
||||||
"@vue/shared": "3.4.21",
|
"@vue/shared": "3.4.27",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.7",
|
"magic-string": "^0.30.10",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.38",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-ssr": {
|
"@vue/compiler-ssr": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz",
|
||||||
"integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
|
"integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-dom": "3.4.21",
|
"@vue/compiler-dom": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/eslint-config-airbnb": {
|
"@vue/eslint-config-airbnb": {
|
||||||
@@ -20395,45 +20393,45 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/reactivity": {
|
"@vue/reactivity": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz",
|
||||||
"integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
|
"integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/runtime-core": {
|
"@vue/runtime-core": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz",
|
||||||
"integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
|
"integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/reactivity": "3.4.21",
|
"@vue/reactivity": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/runtime-dom": {
|
"@vue/runtime-dom": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz",
|
||||||
"integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
|
"integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/runtime-core": "3.4.21",
|
"@vue/runtime-core": "3.4.27",
|
||||||
"@vue/shared": "3.4.21",
|
"@vue/shared": "3.4.27",
|
||||||
"csstype": "^3.1.3"
|
"csstype": "^3.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/server-renderer": {
|
"@vue/server-renderer": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz",
|
||||||
"integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
|
"integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-ssr": "3.4.21",
|
"@vue/compiler-ssr": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/shared": {
|
"@vue/shared": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz",
|
||||||
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
|
"integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA=="
|
||||||
},
|
},
|
||||||
"@vue/test-utils": {
|
"@vue/test-utils": {
|
||||||
"version": "2.4.5",
|
"version": "2.4.5",
|
||||||
@@ -22341,9 +22339,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "29.3.0",
|
"version": "31.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-29.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-31.0.2.tgz",
|
||||||
"integrity": "sha512-ZxFKm0/v48GSoBuO3DdnMlCYXefEUKUHLMsKxyXY4nZGgzbBKpF/X8haZa2paNj23CLfsCKBOtfc2vsEQiOOsA==",
|
"integrity": "sha512-55efQ5yfLN+AQHcFC00AXQqtxC3iAGaxX2GQ3EDbFJ0ca9GHNOdSXkcrdBElLleiDrR2hpXNkQxN1bDn0oxe6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@electron/get": "^2.0.0",
|
"@electron/get": "^2.0.0",
|
||||||
@@ -25486,9 +25484,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"magic-string": {
|
"magic-string": {
|
||||||
"version": "0.30.8",
|
"version": "0.30.10",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
|
||||||
"integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
|
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
}
|
}
|
||||||
@@ -29953,15 +29951,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue": {
|
"vue": {
|
||||||
"version": "3.4.21",
|
"version": "3.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz",
|
||||||
"integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
|
"integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-dom": "3.4.21",
|
"@vue/compiler-dom": "3.4.27",
|
||||||
"@vue/compiler-sfc": "3.4.21",
|
"@vue/compiler-sfc": "3.4.27",
|
||||||
"@vue/runtime-dom": "3.4.21",
|
"@vue/runtime-dom": "3.4.27",
|
||||||
"@vue/server-renderer": "3.4.21",
|
"@vue/server-renderer": "3.4.27",
|
||||||
"@vue/shared": "3.4.21"
|
"@vue/shared": "3.4.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-component-type-helpers": {
|
"vue-component-type-helpers": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.13.2",
|
"version": "0.13.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"slogan": "Privacy is sexy",
|
"slogan": "Privacy is sexy",
|
||||||
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
|
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"electron-updater": "^6.1.9",
|
"electron-updater": "^6.1.9",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"vue": "^3.4.21"
|
"vue": "^3.4.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"@vue/test-utils": "^2.4.5",
|
"@vue/test-utils": "^2.4.5",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"cypress": "^13.7.3",
|
"cypress": "^13.7.3",
|
||||||
"electron": "^29.3.0",
|
"electron": "^31.0.2",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.13.3",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-vite": "^2.1.0",
|
"electron-vite": "^2.1.0",
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ def add_or_update_settings() -> None:
|
|||||||
# Details: # pylint: disable-next=line-too-long
|
# Details: # pylint: disable-next=line-too-long
|
||||||
# - https://archive.ph/2024.01.06-003914/https://github.com/microsoft/vscode/issues/179274, https://web.archive.org/web/20240106003915/https://github.com/microsoft/vscode/issues/179274
|
# - https://archive.ph/2024.01.06-003914/https://github.com/microsoft/vscode/issues/179274, https://web.archive.org/web/20240106003915/https://github.com/microsoft/vscode/issues/179274
|
||||||
|
|
||||||
|
# Disable telemetry
|
||||||
|
configure_setting_key('redhat.telemetry.enabled', False)
|
||||||
|
configure_setting_key('gitlens.telemetry.enabled', False)
|
||||||
|
|
||||||
def configure_setting_key(configuration_key: str, desired_value: Any) -> None:
|
def configure_setting_key(configuration_key: str, desired_value: Any) -> None:
|
||||||
try:
|
try:
|
||||||
with open(VSCODE_SETTINGS_JSON_FILE, 'r+', encoding='utf-8') as file:
|
with open(VSCODE_SETTINGS_JSON_FILE, 'r+', encoding='utf-8') as file:
|
||||||
|
|||||||
51
scripts/validate-collections-yaml/README.md
Normal file
51
scripts/validate-collections-yaml/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# validate-collections-yaml
|
||||||
|
|
||||||
|
This script validates YAML collection files against a predefined schema to ensure their integrity.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Python 3.x installed on your system.
|
||||||
|
|
||||||
|
## Running in a Virtual Environment (Recommended)
|
||||||
|
|
||||||
|
Using a virtual environment isolates dependencies and prevents conflicts.
|
||||||
|
|
||||||
|
1. **Create a virtual environment:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv ./scripts/validate-collections-yaml/.venv
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Activate the virtual environment:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ./scripts/validate-collections-yaml/.venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install dependencies:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pip install -r ./scripts/validate-collections-yaml/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run the script:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ./scripts/validate-collections-yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Globally
|
||||||
|
|
||||||
|
Running the script globally is less recommended due to potential dependency conflicts.
|
||||||
|
|
||||||
|
1. **Install dependencies:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pip install -r ./scripts/validate-collections-yaml/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run the script:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ./scripts/validate-collections-yaml
|
||||||
|
```
|
||||||
62
scripts/validate-collections-yaml/__main__.py
Normal file
62
scripts/validate-collections-yaml/__main__.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""
|
||||||
|
Description:
|
||||||
|
This script validates collection YAML files against the expected schema.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 ./scripts/validate-collections-yaml
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This script requires the `jsonschema` and `pyyaml` packages (see requirements.txt).
|
||||||
|
"""
|
||||||
|
# pylint: disable=missing-function-docstring
|
||||||
|
from os import path
|
||||||
|
import sys
|
||||||
|
from glob import glob
|
||||||
|
from typing import List
|
||||||
|
from jsonschema import exceptions, validate # pylint: disable=import-error
|
||||||
|
import yaml # pylint: disable=import-error
|
||||||
|
|
||||||
|
SCHEMA_FILE_PATH = './src/application/collections/.schema.yaml'
|
||||||
|
COLLECTIONS_GLOB_PATTERN = './src/application/collections/*.yaml'
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
schema_yaml = read_file(SCHEMA_FILE_PATH)
|
||||||
|
schema_json = convert_yaml_to_json(schema_yaml)
|
||||||
|
collection_file_paths = find_collection_files(COLLECTIONS_GLOB_PATTERN)
|
||||||
|
print(f'Found {len(collection_file_paths)} YAML files to validate.')
|
||||||
|
|
||||||
|
total_invalid_files = 0
|
||||||
|
for collection_file_path in collection_file_paths:
|
||||||
|
file_name = path.basename(collection_file_path)
|
||||||
|
print(f'Validating {file_name}...')
|
||||||
|
collection_yaml = read_file(collection_file_path)
|
||||||
|
collection_json = convert_yaml_to_json(collection_yaml)
|
||||||
|
try:
|
||||||
|
validate(instance=collection_json, schema=schema_json)
|
||||||
|
print(f'Success: {file_name} is valid.')
|
||||||
|
except exceptions.ValidationError as err:
|
||||||
|
print(f'Error: Validation failed for {file_name}.', file=sys.stderr)
|
||||||
|
print(str(err), file=sys.stderr)
|
||||||
|
total_invalid_files += 1
|
||||||
|
|
||||||
|
if total_invalid_files > 0:
|
||||||
|
print(f'Validation complete with {total_invalid_files} invalid files.', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print('Validation complete. All files are valid.')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def read_file(file_path: str) -> str:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
def find_collection_files(glob_pattern: str) -> List[str]:
|
||||||
|
files = glob(glob_pattern)
|
||||||
|
filtered_files = [f for f in files if not path.basename(f).startswith('.')]
|
||||||
|
return filtered_files
|
||||||
|
|
||||||
|
def convert_yaml_to_json(yaml_content: str) -> dict:
|
||||||
|
return yaml.safe_load(yaml_content)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
6
scripts/validate-collections-yaml/requirements.txt
Normal file
6
scripts/validate-collections-yaml/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
attrs==23.2.0
|
||||||
|
jsonschema==4.22.0
|
||||||
|
jsonschema-specifications==2023.12.1
|
||||||
|
PyYAML==6.0.1
|
||||||
|
referencing==0.35.1
|
||||||
|
rpds-py==0.18.1
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isFunction } from '@/TypeHelpers';
|
import { isFunction, type ConstructorArguments } from '@/TypeHelpers';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Provides a unified and resilient way to extend errors across platforms.
|
Provides a unified and resilient way to extend errors across platforms.
|
||||||
@@ -12,8 +12,8 @@ import { isFunction } from '@/TypeHelpers';
|
|||||||
> https://web.archive.org/web/20230810014143/https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
> https://web.archive.org/web/20230810014143/https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||||
*/
|
*/
|
||||||
export abstract class CustomError extends Error {
|
export abstract class CustomError extends Error {
|
||||||
constructor(message?: string, options?: ErrorOptions) {
|
constructor(...args: ConstructorArguments<typeof Error>) {
|
||||||
super(message, options);
|
super(...args);
|
||||||
|
|
||||||
fixPrototype(this, new.target.prototype);
|
fixPrototype(this, new.target.prototype);
|
||||||
ensureStackTrace(this);
|
ensureStackTrace(this);
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ export type EnumType = number | string;
|
|||||||
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType>
|
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType>
|
||||||
= { [key in T]: TEnumValue };
|
= { [key in T]: TEnumValue };
|
||||||
|
|
||||||
export interface IEnumParser<TEnum> {
|
export interface EnumParser<TEnum> {
|
||||||
parseEnum(value: string, propertyName: string): TEnum;
|
parseEnum(value: string, propertyName: string): TEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEnumParser<T extends EnumType, TEnumValue extends EnumType>(
|
export function createEnumParser<T extends EnumType, TEnumValue extends EnumType>(
|
||||||
enumVariable: EnumVariable<T, TEnumValue>,
|
enumVariable: EnumVariable<T, TEnumValue>,
|
||||||
): IEnumParser<TEnumValue> {
|
): EnumParser<TEnumValue> {
|
||||||
return {
|
return {
|
||||||
parseEnum: (value, propertyName) => parseEnumValue(value, propertyName, enumVariable),
|
parseEnum: (value, propertyName) => parseEnumValue(value, propertyName, enumVariable),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,18 +15,26 @@ const DefaultOptions: ThrottleOptions = {
|
|||||||
timer: PlatformTimer,
|
timer: PlatformTimer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function throttle(
|
export interface ThrottleFunction {
|
||||||
|
(
|
||||||
|
callback: CallbackType,
|
||||||
|
waitInMs: number,
|
||||||
|
options?: Partial<ThrottleOptions>,
|
||||||
|
): CallbackType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const throttle: ThrottleFunction = (
|
||||||
callback: CallbackType,
|
callback: CallbackType,
|
||||||
waitInMs: number,
|
waitInMs: number,
|
||||||
options: Partial<ThrottleOptions> = DefaultOptions,
|
options: Partial<ThrottleOptions> = DefaultOptions,
|
||||||
): CallbackType {
|
): CallbackType => {
|
||||||
const defaultedOptions: ThrottleOptions = {
|
const defaultedOptions: ThrottleOptions = {
|
||||||
...DefaultOptions,
|
...DefaultOptions,
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
const throttler = new Throttler(waitInMs, callback, defaultedOptions);
|
const throttler = new Throttler(waitInMs, callback, defaultedOptions);
|
||||||
return (...args: unknown[]) => throttler.invoke(...args);
|
return (...args: unknown[]) => throttler.invoke(...args);
|
||||||
}
|
};
|
||||||
|
|
||||||
class Throttler {
|
class Throttler {
|
||||||
private lastExecutionTime: number | null = null;
|
private lastExecutionTime: number | null = null;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
|
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
|
||||||
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
|
import type { SelectedScript } from '@/application/Context/State/Selection/Script/SelectedScript';
|
||||||
import type { ICodeChangedEvent } from './ICodeChangedEvent';
|
import type { ICodeChangedEvent } from './ICodeChangedEvent';
|
||||||
@@ -6,13 +6,13 @@ import type { ICodeChangedEvent } from './ICodeChangedEvent';
|
|||||||
export class CodeChangedEvent implements ICodeChangedEvent {
|
export class CodeChangedEvent implements ICodeChangedEvent {
|
||||||
public readonly code: string;
|
public readonly code: string;
|
||||||
|
|
||||||
public readonly addedScripts: ReadonlyArray<IScript>;
|
public readonly addedScripts: ReadonlyArray<Script>;
|
||||||
|
|
||||||
public readonly removedScripts: ReadonlyArray<IScript>;
|
public readonly removedScripts: ReadonlyArray<Script>;
|
||||||
|
|
||||||
public readonly changedScripts: ReadonlyArray<IScript>;
|
public readonly changedScripts: ReadonlyArray<Script>;
|
||||||
|
|
||||||
private readonly scripts: Map<IScript, ICodePosition>;
|
private readonly scripts: Map<Script, ICodePosition>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
code: string,
|
code: string,
|
||||||
@@ -25,7 +25,7 @@ export class CodeChangedEvent implements ICodeChangedEvent {
|
|||||||
this.addedScripts = selectIfNotExists(newScripts, oldScripts);
|
this.addedScripts = selectIfNotExists(newScripts, oldScripts);
|
||||||
this.removedScripts = selectIfNotExists(oldScripts, newScripts);
|
this.removedScripts = selectIfNotExists(oldScripts, newScripts);
|
||||||
this.changedScripts = getChangedScripts(oldScripts, newScripts);
|
this.changedScripts = getChangedScripts(oldScripts, newScripts);
|
||||||
this.scripts = new Map<IScript, ICodePosition>();
|
this.scripts = new Map<Script, ICodePosition>();
|
||||||
scripts.forEach((position, selection) => {
|
scripts.forEach((position, selection) => {
|
||||||
this.scripts.set(selection.script, position);
|
this.scripts.set(selection.script, position);
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,7 @@ export class CodeChangedEvent implements ICodeChangedEvent {
|
|||||||
return this.scripts.size === 0;
|
return this.scripts.size === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getScriptPositionInCode(script: IScript): ICodePosition {
|
public getScriptPositionInCode(script: Script): ICodePosition {
|
||||||
return this.getPositionById(script.id);
|
return this.getPositionById(script.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ function ensureAllPositionsExist(script: string, positions: ReadonlyArray<ICodeP
|
|||||||
function getChangedScripts(
|
function getChangedScripts(
|
||||||
oldScripts: ReadonlyArray<SelectedScript>,
|
oldScripts: ReadonlyArray<SelectedScript>,
|
||||||
newScripts: ReadonlyArray<SelectedScript>,
|
newScripts: ReadonlyArray<SelectedScript>,
|
||||||
): ReadonlyArray<IScript> {
|
): ReadonlyArray<Script> {
|
||||||
return newScripts
|
return newScripts
|
||||||
.filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id
|
.filter((newScript) => oldScripts.find((oldScript) => oldScript.id === newScript.id
|
||||||
&& oldScript.revert !== newScript.revert))
|
&& oldScript.revert !== newScript.revert))
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
|
import type { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
|
||||||
|
|
||||||
export interface ICodeChangedEvent {
|
export interface ICodeChangedEvent {
|
||||||
readonly code: string;
|
readonly code: string;
|
||||||
readonly addedScripts: ReadonlyArray<IScript>;
|
readonly addedScripts: ReadonlyArray<Script>;
|
||||||
readonly removedScripts: ReadonlyArray<IScript>;
|
readonly removedScripts: ReadonlyArray<Script>;
|
||||||
readonly changedScripts: ReadonlyArray<IScript>;
|
readonly changedScripts: ReadonlyArray<Script>;
|
||||||
isEmpty(): boolean;
|
isEmpty(): boolean;
|
||||||
getScriptPositionInCode(script: IScript): ICodePosition;
|
getScriptPositionInCode(script: Script): ICodePosition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import type { ICategory } from '@/domain/ICategory';
|
import type { Category } from '@/domain/Executables/Category/Category';
|
||||||
import type { FilterResult } from './FilterResult';
|
import type { FilterResult } from './FilterResult';
|
||||||
|
|
||||||
export class AppliedFilterResult implements FilterResult {
|
export class AppliedFilterResult implements FilterResult {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly scriptMatches: ReadonlyArray<IScript>,
|
public readonly scriptMatches: ReadonlyArray<Script>,
|
||||||
public readonly categoryMatches: ReadonlyArray<ICategory>,
|
public readonly categoryMatches: ReadonlyArray<Category>,
|
||||||
public readonly query: string,
|
public readonly query: string,
|
||||||
) {
|
) {
|
||||||
if (!query) { throw new Error('Query is empty or undefined'); }
|
if (!query) { throw new Error('Query is empty or undefined'); }
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { IScript, ICategory } from '@/domain/ICategory';
|
import type { Category } from '@/domain/Executables/Category/Category';
|
||||||
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
|
|
||||||
export interface FilterResult {
|
export interface FilterResult {
|
||||||
readonly categoryMatches: ReadonlyArray<ICategory>;
|
readonly categoryMatches: ReadonlyArray<Category>;
|
||||||
readonly scriptMatches: ReadonlyArray<IScript>;
|
readonly scriptMatches: ReadonlyArray<Script>;
|
||||||
readonly query: string;
|
readonly query: string;
|
||||||
hasAnyMatches(): boolean;
|
hasAnyMatches(): boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { ICategory, IScript } from '@/domain/ICategory';
|
import type { Category } from '@/domain/Executables/Category/Category';
|
||||||
import type { IScriptCode } from '@/domain/IScriptCode';
|
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
|
||||||
import type { IDocumentable } from '@/domain/IDocumentable';
|
import type { Documentable } from '@/domain/Executables/Documentable';
|
||||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import { AppliedFilterResult } from '../Result/AppliedFilterResult';
|
import { AppliedFilterResult } from '../Result/AppliedFilterResult';
|
||||||
import type { FilterStrategy } from './FilterStrategy';
|
import type { FilterStrategy } from './FilterStrategy';
|
||||||
import type { FilterResult } from '../Result/FilterResult';
|
import type { FilterResult } from '../Result/FilterResult';
|
||||||
@@ -24,7 +25,7 @@ export class LinearFilterStrategy implements FilterStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchesCategory(
|
function matchesCategory(
|
||||||
category: ICategory,
|
category: Category,
|
||||||
filterLowercase: string,
|
filterLowercase: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
return matchesAny(
|
return matchesAny(
|
||||||
@@ -34,7 +35,7 @@ function matchesCategory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchesScript(
|
function matchesScript(
|
||||||
script: IScript,
|
script: Script,
|
||||||
filterLowercase: string,
|
filterLowercase: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
return matchesAny(
|
return matchesAny(
|
||||||
@@ -58,7 +59,7 @@ function matchName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchCode(
|
function matchCode(
|
||||||
code: IScriptCode,
|
code: ScriptCode,
|
||||||
filterLowercase: string,
|
filterLowercase: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (code.execute.toLowerCase().includes(filterLowercase)) {
|
if (code.execute.toLowerCase().includes(filterLowercase)) {
|
||||||
@@ -71,7 +72,7 @@ function matchCode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchDocumentation(
|
function matchDocumentation(
|
||||||
documentable: IDocumentable,
|
documentable: Documentable,
|
||||||
filterLowercase: string,
|
filterLowercase: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
return documentable.docs.some(
|
return documentable.docs.some(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ICategory } from '@/domain/ICategory';
|
import type { Category } from '@/domain/Executables/Category/Category';
|
||||||
import type { CategorySelectionChangeCommand } from './CategorySelectionChange';
|
import type { CategorySelectionChangeCommand } from './CategorySelectionChange';
|
||||||
|
|
||||||
export interface ReadonlyCategorySelection {
|
export interface ReadonlyCategorySelection {
|
||||||
areAllScriptsSelected(category: ICategory): boolean;
|
areAllScriptsSelected(category: Category): boolean;
|
||||||
isAnyScriptSelected(category: ICategory): boolean;
|
isAnyScriptSelected(category: Category): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategorySelection extends ReadonlyCategorySelection {
|
export interface CategorySelection extends ReadonlyCategorySelection {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ICategory } from '@/domain/ICategory';
|
import type { Category } from '@/domain/Executables/Category/Category';
|
||||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import type { CategorySelectionChange, CategorySelectionChangeCommand } from './CategorySelectionChange';
|
import type { CategorySelectionChange, CategorySelectionChangeCommand } from './CategorySelectionChange';
|
||||||
import type { CategorySelection } from './CategorySelection';
|
import type { CategorySelection } from './CategorySelection';
|
||||||
@@ -13,7 +13,7 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public areAllScriptsSelected(category: ICategory): boolean {
|
public areAllScriptsSelected(category: Category): boolean {
|
||||||
const { selectedScripts } = this.scriptSelection;
|
const { selectedScripts } = this.scriptSelection;
|
||||||
if (selectedScripts.length === 0) {
|
if (selectedScripts.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -27,7 +27,7 @@ export class ScriptToCategorySelectionMapper implements CategorySelection {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isAnyScriptSelected(category: ICategory): boolean {
|
public isAnyScriptSelected(category: Category): boolean {
|
||||||
const { selectedScripts } = this.scriptSelection;
|
const { selectedScripts } = this.scriptSelection;
|
||||||
if (selectedScripts.length === 0) {
|
if (selectedScripts.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
|
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
|
||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import { EventSource } from '@/infrastructure/Events/EventSource';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
import type { ReadonlyRepository, Repository } from '@/application/Repository/Repository';
|
import type { ReadonlyRepository, Repository } from '@/application/Repository/Repository';
|
||||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
@@ -80,7 +80,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectOnly(scripts: readonly IScript[]): void {
|
public selectOnly(scripts: readonly Script[]): void {
|
||||||
assertNonEmptyScriptSelection(scripts);
|
assertNonEmptyScriptSelection(scripts);
|
||||||
this.processChanges({
|
this.processChanges({
|
||||||
changes: [
|
changes: [
|
||||||
@@ -145,7 +145,7 @@ export class DebouncedScriptSelection implements ScriptSelection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertNonEmptyScriptSelection(selectedItems: readonly IScript[]) {
|
function assertNonEmptyScriptSelection(selectedItems: readonly Script[]) {
|
||||||
if (selectedItems.length === 0) {
|
if (selectedItems.length === 0) {
|
||||||
throw new Error('Provided script array is empty. To deselect all scripts, please use the deselectAll() method instead.');
|
throw new Error('Provided script array is empty. To deselect all scripts, please use the deselectAll() method instead.');
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ function assertNonEmptyScriptSelection(selectedItems: readonly IScript[]) {
|
|||||||
|
|
||||||
function getScriptIdsToBeSelected(
|
function getScriptIdsToBeSelected(
|
||||||
existingItems: ReadonlyRepository<string, SelectedScript>,
|
existingItems: ReadonlyRepository<string, SelectedScript>,
|
||||||
desiredScripts: readonly IScript[],
|
desiredScripts: readonly Script[],
|
||||||
): string[] {
|
): string[] {
|
||||||
return desiredScripts
|
return desiredScripts
|
||||||
.filter((script) => !existingItems.exists(script.id))
|
.filter((script) => !existingItems.exists(script.id))
|
||||||
@@ -162,7 +162,7 @@ function getScriptIdsToBeSelected(
|
|||||||
|
|
||||||
function getScriptIdsToBeDeselected(
|
function getScriptIdsToBeDeselected(
|
||||||
existingItems: ReadonlyRepository<string, SelectedScript>,
|
existingItems: ReadonlyRepository<string, SelectedScript>,
|
||||||
desiredScripts: readonly IScript[],
|
desiredScripts: readonly Script[],
|
||||||
): string[] {
|
): string[] {
|
||||||
return existingItems
|
return existingItems
|
||||||
.getItems()
|
.getItems()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { IEventSource } from '@/infrastructure/Events/IEventSource';
|
import type { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import type { SelectedScript } from './SelectedScript';
|
import type { SelectedScript } from './SelectedScript';
|
||||||
import type { ScriptSelectionChangeCommand } from './ScriptSelectionChange';
|
import type { ScriptSelectionChangeCommand } from './ScriptSelectionChange';
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ export interface ReadonlyScriptSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ScriptSelection extends ReadonlyScriptSelection {
|
export interface ScriptSelection extends ReadonlyScriptSelection {
|
||||||
selectOnly(scripts: readonly IScript[]): void;
|
selectOnly(scripts: readonly Script[]): void;
|
||||||
selectAll(): void;
|
selectAll(): void;
|
||||||
deselectAll(): void;
|
deselectAll(): void;
|
||||||
processChanges(action: ScriptSelectionChangeCommand): void;
|
processChanges(action: ScriptSelectionChangeCommand): void;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { IEntity } from '@/infrastructure/Entity/IEntity';
|
import type { IEntity } from '@/infrastructure/Entity/IEntity';
|
||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
|
|
||||||
type ScriptId = IScript['id'];
|
type ScriptId = Script['id'];
|
||||||
|
|
||||||
export interface SelectedScript extends IEntity<ScriptId> {
|
export interface SelectedScript extends IEntity<ScriptId> {
|
||||||
readonly script: IScript;
|
readonly script: Script;
|
||||||
readonly revert: boolean;
|
readonly revert: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
||||||
import type { IScript } from '@/domain/IScript';
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
import type { SelectedScript } from './SelectedScript';
|
import type { SelectedScript } from './SelectedScript';
|
||||||
|
|
||||||
type SelectedScriptId = SelectedScript['id'];
|
type SelectedScriptId = SelectedScript['id'];
|
||||||
|
|
||||||
export class UserSelectedScript extends BaseEntity<SelectedScriptId> {
|
export class UserSelectedScript extends BaseEntity<SelectedScriptId> {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly script: IScript,
|
public readonly script: Script,
|
||||||
public readonly revert: boolean,
|
public readonly revert: boolean,
|
||||||
) {
|
) {
|
||||||
super(script.id);
|
super(script.id);
|
||||||
|
|||||||
@@ -1,40 +1,48 @@
|
|||||||
import type { CollectionData } from '@/application/collections/';
|
import type { CollectionData } from '@/application/collections/';
|
||||||
import type { IApplication } from '@/domain/IApplication';
|
import type { IApplication } from '@/domain/IApplication';
|
||||||
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
|
||||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
|
||||||
import WindowsData from '@/application/collections/windows.yaml';
|
import WindowsData from '@/application/collections/windows.yaml';
|
||||||
import MacOsData from '@/application/collections/macos.yaml';
|
import MacOsData from '@/application/collections/macos.yaml';
|
||||||
import LinuxData from '@/application/collections/linux.yaml';
|
import LinuxData from '@/application/collections/linux.yaml';
|
||||||
import { parseProjectDetails } from '@/application/Parser/ProjectDetailsParser';
|
import { parseProjectDetails, type ProjectDetailsParser } from '@/application/Parser/ProjectDetailsParser';
|
||||||
import { Application } from '@/domain/Application';
|
import { Application } from '@/domain/Application';
|
||||||
import type { IAppMetadata } from '@/infrastructure/EnvironmentVariables/IAppMetadata';
|
import { parseCategoryCollection, type CategoryCollectionParser } from './CategoryCollectionParser';
|
||||||
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
|
import { createTypeValidator, type TypeValidator } from './Common/TypeValidator';
|
||||||
import { parseCategoryCollection } from './CategoryCollectionParser';
|
|
||||||
|
|
||||||
export function parseApplication(
|
export function parseApplication(
|
||||||
categoryParser = parseCategoryCollection,
|
collectionsData: readonly CollectionData[] = PreParsedCollections,
|
||||||
projectDetailsParser = parseProjectDetails,
|
utilities: ApplicationParserUtilities = DefaultUtilities,
|
||||||
metadata: IAppMetadata = EnvironmentVariablesFactory.Current.instance,
|
|
||||||
collectionsData = PreParsedCollections,
|
|
||||||
): IApplication {
|
): IApplication {
|
||||||
validateCollectionsData(collectionsData);
|
validateCollectionsData(collectionsData, utilities.validator);
|
||||||
const projectDetails = projectDetailsParser(metadata);
|
const projectDetails = utilities.parseProjectDetails();
|
||||||
const collections = collectionsData.map(
|
const collections = collectionsData.map(
|
||||||
(collection) => categoryParser(collection, projectDetails),
|
(collection) => utilities.parseCategoryCollection(collection, projectDetails),
|
||||||
);
|
);
|
||||||
const app = new Application(projectDetails, collections);
|
const app = new Application(projectDetails, collections);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CategoryCollectionParserType
|
const PreParsedCollections: readonly CollectionData[] = [
|
||||||
= (file: CollectionData, projectDetails: ProjectDetails) => ICategoryCollection;
|
|
||||||
|
|
||||||
const PreParsedCollections: readonly CollectionData [] = [
|
|
||||||
WindowsData, MacOsData, LinuxData,
|
WindowsData, MacOsData, LinuxData,
|
||||||
];
|
];
|
||||||
|
|
||||||
function validateCollectionsData(collections: readonly CollectionData[]) {
|
function validateCollectionsData(
|
||||||
if (!collections.length) {
|
collections: readonly CollectionData[],
|
||||||
throw new Error('missing collections');
|
validator: TypeValidator,
|
||||||
}
|
) {
|
||||||
|
validator.assertNonEmptyCollection({
|
||||||
|
value: collections,
|
||||||
|
valueName: 'collections',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ApplicationParserUtilities {
|
||||||
|
readonly parseCategoryCollection: CategoryCollectionParser;
|
||||||
|
readonly validator: TypeValidator;
|
||||||
|
readonly parseProjectDetails: ProjectDetailsParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultUtilities: ApplicationParserUtilities = {
|
||||||
|
parseCategoryCollection,
|
||||||
|
parseProjectDetails,
|
||||||
|
validator: createTypeValidator(),
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,32 +3,73 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
|
|||||||
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import type { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import { CategoryCollection } from '@/domain/CategoryCollection';
|
import { CategoryCollection } from '@/domain/CategoryCollection';
|
||||||
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
||||||
import { createEnumParser } from '../Common/Enum';
|
import { createEnumParser, type EnumParser } from '../Common/Enum';
|
||||||
import { parseCategory } from './CategoryParser';
|
import { parseCategory, type CategoryParser } from './Executable/CategoryParser';
|
||||||
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
|
import { parseScriptingDefinition, type ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
|
||||||
import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
|
import { createTypeValidator, type TypeValidator } from './Common/TypeValidator';
|
||||||
|
import { createCollectionUtilities, type CategoryCollectionSpecificUtilitiesFactory } from './Executable/CategoryCollectionSpecificUtilities';
|
||||||
|
|
||||||
export function parseCategoryCollection(
|
export const parseCategoryCollection: CategoryCollectionParser = (
|
||||||
content: CollectionData,
|
content,
|
||||||
projectDetails: ProjectDetails,
|
projectDetails,
|
||||||
osParser = createEnumParser(OperatingSystem),
|
utilities: CategoryCollectionParserUtilities = DefaultUtilities,
|
||||||
): ICategoryCollection {
|
) => {
|
||||||
validate(content);
|
validateCollection(content, utilities.validator);
|
||||||
const scripting = new ScriptingDefinitionParser()
|
const scripting = utilities.parseScriptingDefinition(content.scripting, projectDetails);
|
||||||
.parse(content.scripting, projectDetails);
|
const collectionUtilities = utilities.createUtilities(content.functions, scripting);
|
||||||
const context = new CategoryCollectionParseContext(content.functions, scripting);
|
const categories = content.actions.map(
|
||||||
const categories = content.actions.map((action) => parseCategory(action, context));
|
(action) => utilities.parseCategory(action, collectionUtilities),
|
||||||
const os = osParser.parseEnum(content.os, 'os');
|
|
||||||
const collection = new CategoryCollection(
|
|
||||||
os,
|
|
||||||
categories,
|
|
||||||
scripting,
|
|
||||||
);
|
);
|
||||||
|
const os = utilities.osParser.parseEnum(content.os, 'os');
|
||||||
|
const collection = utilities.createCategoryCollection({
|
||||||
|
os, actions: categories, scripting,
|
||||||
|
});
|
||||||
return collection;
|
return collection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CategoryCollectionFactory = (
|
||||||
|
...parameters: ConstructorParameters<typeof CategoryCollection>
|
||||||
|
) => ICategoryCollection;
|
||||||
|
|
||||||
|
export interface CategoryCollectionParser {
|
||||||
|
(
|
||||||
|
content: CollectionData,
|
||||||
|
projectDetails: ProjectDetails,
|
||||||
|
utilities?: CategoryCollectionParserUtilities,
|
||||||
|
): ICategoryCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate(content: CollectionData): void {
|
function validateCollection(
|
||||||
if (!content.actions.length) {
|
content: CollectionData,
|
||||||
throw new Error('content does not define any action');
|
validator: TypeValidator,
|
||||||
}
|
): void {
|
||||||
|
validator.assertObject({
|
||||||
|
value: content,
|
||||||
|
valueName: 'collection',
|
||||||
|
allowedProperties: [
|
||||||
|
'os', 'scripting', 'actions', 'functions',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
validator.assertNonEmptyCollection({
|
||||||
|
value: content.actions,
|
||||||
|
valueName: '"actions" in collection',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CategoryCollectionParserUtilities {
|
||||||
|
readonly osParser: EnumParser<OperatingSystem>;
|
||||||
|
readonly validator: TypeValidator;
|
||||||
|
readonly parseScriptingDefinition: ScriptingDefinitionParser;
|
||||||
|
readonly createUtilities: CategoryCollectionSpecificUtilitiesFactory;
|
||||||
|
readonly parseCategory: CategoryParser;
|
||||||
|
readonly createCategoryCollection: CategoryCollectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultUtilities: CategoryCollectionParserUtilities = {
|
||||||
|
osParser: createEnumParser(OperatingSystem),
|
||||||
|
validator: createTypeValidator(),
|
||||||
|
parseScriptingDefinition,
|
||||||
|
createUtilities: createCollectionUtilities,
|
||||||
|
parseCategory,
|
||||||
|
createCategoryCollection: (...args) => new CategoryCollection(...args),
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
import type {
|
|
||||||
CategoryData, ScriptData, CategoryOrScriptData,
|
|
||||||
} from '@/application/collections/';
|
|
||||||
import { Script } from '@/domain/Script';
|
|
||||||
import { Category } from '@/domain/Category';
|
|
||||||
import { NodeValidator } from '@/application/Parser/NodeValidation/NodeValidator';
|
|
||||||
import { NodeType } from '@/application/Parser/NodeValidation/NodeType';
|
|
||||||
import { parseDocs } from './DocumentationParser';
|
|
||||||
import { parseScript } from './Script/ScriptParser';
|
|
||||||
import type { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
|
|
||||||
|
|
||||||
let categoryIdCounter = 0;
|
|
||||||
|
|
||||||
export function parseCategory(
|
|
||||||
category: CategoryData,
|
|
||||||
context: ICategoryCollectionParseContext,
|
|
||||||
factory: CategoryFactoryType = CategoryFactory,
|
|
||||||
): Category {
|
|
||||||
return parseCategoryRecursively({
|
|
||||||
categoryData: category,
|
|
||||||
context,
|
|
||||||
factory,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICategoryParseContext {
|
|
||||||
readonly categoryData: CategoryData,
|
|
||||||
readonly context: ICategoryCollectionParseContext,
|
|
||||||
readonly factory: CategoryFactoryType,
|
|
||||||
readonly parentCategory?: CategoryData,
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseCategoryRecursively(context: ICategoryParseContext): Category | never {
|
|
||||||
ensureValidCategory(context.categoryData, context.parentCategory);
|
|
||||||
const children: ICategoryChildren = {
|
|
||||||
subCategories: new Array<Category>(),
|
|
||||||
subScripts: new Array<Script>(),
|
|
||||||
};
|
|
||||||
for (const data of context.categoryData.children) {
|
|
||||||
parseNode({
|
|
||||||
nodeData: data,
|
|
||||||
children,
|
|
||||||
parent: context.categoryData,
|
|
||||||
factory: context.factory,
|
|
||||||
context: context.context,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return context.factory(
|
|
||||||
/* id: */ categoryIdCounter++,
|
|
||||||
/* name: */ context.categoryData.category,
|
|
||||||
/* docs: */ parseDocs(context.categoryData),
|
|
||||||
/* categories: */ children.subCategories,
|
|
||||||
/* scripts: */ children.subScripts,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return new NodeValidator({
|
|
||||||
type: NodeType.Category,
|
|
||||||
selfNode: context.categoryData,
|
|
||||||
parentNode: context.parentCategory,
|
|
||||||
}).throw(err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureValidCategory(category: CategoryData, parentCategory?: CategoryData) {
|
|
||||||
new NodeValidator({
|
|
||||||
type: NodeType.Category,
|
|
||||||
selfNode: category,
|
|
||||||
parentNode: parentCategory,
|
|
||||||
})
|
|
||||||
.assertDefined(category)
|
|
||||||
.assertValidName(category.category)
|
|
||||||
.assert(
|
|
||||||
() => category.children.length > 0,
|
|
||||||
`"${category.category}" has no children.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICategoryChildren {
|
|
||||||
subCategories: Category[];
|
|
||||||
subScripts: Script[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INodeParseContext {
|
|
||||||
readonly nodeData: CategoryOrScriptData;
|
|
||||||
readonly children: ICategoryChildren;
|
|
||||||
readonly parent: CategoryData;
|
|
||||||
readonly factory: CategoryFactoryType;
|
|
||||||
readonly context: ICategoryCollectionParseContext;
|
|
||||||
}
|
|
||||||
function parseNode(context: INodeParseContext) {
|
|
||||||
const validator = new NodeValidator({ selfNode: context.nodeData, parentNode: context.parent });
|
|
||||||
validator.assertDefined(context.nodeData);
|
|
||||||
if (isCategory(context.nodeData)) {
|
|
||||||
const subCategory = parseCategoryRecursively({
|
|
||||||
categoryData: context.nodeData,
|
|
||||||
context: context.context,
|
|
||||||
factory: context.factory,
|
|
||||||
parentCategory: context.parent,
|
|
||||||
});
|
|
||||||
context.children.subCategories.push(subCategory);
|
|
||||||
} else if (isScript(context.nodeData)) {
|
|
||||||
const script = parseScript(context.nodeData, context.context);
|
|
||||||
context.children.subScripts.push(script);
|
|
||||||
} else {
|
|
||||||
validator.throw('Node is neither a category or a script.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isScript(data: CategoryOrScriptData): data is ScriptData {
|
|
||||||
return hasCode(data) || hasCall(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCategory(data: CategoryOrScriptData): data is CategoryData {
|
|
||||||
return hasProperty(data, 'category');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasCode(data: unknown): boolean {
|
|
||||||
return hasProperty(data, 'code');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasCall(data: unknown) {
|
|
||||||
return hasProperty(data, 'call');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasProperty(object: unknown, propertyName: string) {
|
|
||||||
return Object.prototype.hasOwnProperty.call(object, propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CategoryFactoryType = (
|
|
||||||
...parameters: ConstructorParameters<typeof Category>) => Category;
|
|
||||||
|
|
||||||
const CategoryFactory: CategoryFactoryType = (...parameters) => new Category(...parameters);
|
|
||||||
42
src/application/Parser/Common/ContextualError.ts
Normal file
42
src/application/Parser/Common/ContextualError.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { CustomError } from '@/application/Common/CustomError';
|
||||||
|
|
||||||
|
export interface ErrorWithContextWrapper {
|
||||||
|
(
|
||||||
|
error: Error,
|
||||||
|
additionalContext: string,
|
||||||
|
): Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wrapErrorWithAdditionalContext: ErrorWithContextWrapper = (
|
||||||
|
error: Error,
|
||||||
|
additionalContext: string,
|
||||||
|
) => {
|
||||||
|
return (error instanceof ContextualError ? error : new ContextualError(error))
|
||||||
|
.withAdditionalContext(additionalContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* AggregateError is similar but isn't well-serialized or displayed by browsers */
|
||||||
|
class ContextualError extends CustomError {
|
||||||
|
private readonly additionalContext = new Array<string>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly innerError: Error,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public withAdditionalContext(additionalContext: string): this {
|
||||||
|
this.additionalContext.push(additionalContext);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get message(): string { // toString() is not used when Chromium logs it on console
|
||||||
|
return [
|
||||||
|
'\n',
|
||||||
|
this.innerError.message,
|
||||||
|
'\n',
|
||||||
|
'Additional context:',
|
||||||
|
...this.additionalContext.map((context, index) => `${index + 1}: ${context}`),
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
131
src/application/Parser/Common/TypeValidator.ts
Normal file
131
src/application/Parser/Common/TypeValidator.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import type { PropertyKeys } from '@/TypeHelpers';
|
||||||
|
import {
|
||||||
|
isNullOrUndefined, isArray, isPlainObject, isString,
|
||||||
|
} from '@/TypeHelpers';
|
||||||
|
|
||||||
|
export interface TypeValidator {
|
||||||
|
assertObject<T>(assertion: ObjectAssertion<T>): void;
|
||||||
|
assertNonEmptyCollection(assertion: NonEmptyCollectionAssertion): void;
|
||||||
|
assertNonEmptyString(assertion: NonEmptyStringAssertion): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NonEmptyCollectionAssertion {
|
||||||
|
readonly value: unknown;
|
||||||
|
readonly valueName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegexValidationRule {
|
||||||
|
readonly expectedMatch: RegExp;
|
||||||
|
readonly errorMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NonEmptyStringAssertion {
|
||||||
|
readonly value: unknown;
|
||||||
|
readonly valueName: string;
|
||||||
|
readonly rule?: RegexValidationRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObjectAssertion<T> {
|
||||||
|
readonly value: T | unknown;
|
||||||
|
readonly valueName: string;
|
||||||
|
readonly allowedProperties?: readonly PropertyKeys<T>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTypeValidator(): TypeValidator {
|
||||||
|
return {
|
||||||
|
assertObject: (assertion) => {
|
||||||
|
assertDefined(assertion.value, assertion.valueName);
|
||||||
|
assertPlainObject(assertion.value, assertion.valueName);
|
||||||
|
assertNoEmptyProperties(assertion.value, assertion.valueName);
|
||||||
|
if (assertion.allowedProperties !== undefined) {
|
||||||
|
const allowedProperties = assertion.allowedProperties.map((p) => p as string);
|
||||||
|
assertAllowedProperties(assertion.value, assertion.valueName, allowedProperties);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertNonEmptyCollection: (assertion) => {
|
||||||
|
assertDefined(assertion.value, assertion.valueName);
|
||||||
|
assertArray(assertion.value, assertion.valueName);
|
||||||
|
assertNonEmpty(assertion.value, assertion.valueName);
|
||||||
|
},
|
||||||
|
assertNonEmptyString: (assertion) => {
|
||||||
|
assertDefined(assertion.value, assertion.valueName);
|
||||||
|
assertString(assertion.value, assertion.valueName);
|
||||||
|
if (assertion.value.length === 0) {
|
||||||
|
throw new Error(`'${assertion.valueName}' is missing.`);
|
||||||
|
}
|
||||||
|
if (assertion.rule) {
|
||||||
|
if (!assertion.value.match(assertion.rule.expectedMatch)) {
|
||||||
|
throw new Error(assertion.rule.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDefined<T>(
|
||||||
|
value: T,
|
||||||
|
valueName: string,
|
||||||
|
): asserts value is NonNullable<T> {
|
||||||
|
if (isNullOrUndefined(value)) {
|
||||||
|
throw new Error(`'${valueName}' is missing.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertPlainObject(
|
||||||
|
value: unknown,
|
||||||
|
valueName: string,
|
||||||
|
): asserts value is object {
|
||||||
|
if (!isPlainObject(value)) {
|
||||||
|
throw new Error(`'${valueName}' is not an object.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNoEmptyProperties(
|
||||||
|
value: object,
|
||||||
|
valueName: string,
|
||||||
|
): void {
|
||||||
|
if (Object.keys(value).length === 0) {
|
||||||
|
throw new Error(`'${valueName}' is an empty object without properties.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertAllowedProperties(
|
||||||
|
value: object,
|
||||||
|
valueName: string,
|
||||||
|
allowedProperties: readonly string[],
|
||||||
|
): void {
|
||||||
|
const properties = Object.keys(value).map((p) => p as string);
|
||||||
|
const disallowedProperties = properties.filter(
|
||||||
|
(prop) => !allowedProperties.map((p) => p as string).includes(prop),
|
||||||
|
);
|
||||||
|
if (disallowedProperties.length > 0) {
|
||||||
|
throw new Error(`'${valueName}' has disallowed properties: ${disallowedProperties.join(', ')}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertArray(
|
||||||
|
value: unknown,
|
||||||
|
valueName: string,
|
||||||
|
): asserts value is Array<unknown> {
|
||||||
|
if (!isArray(value)) {
|
||||||
|
throw new Error(`'${valueName}' should be of type 'array', but is of type '${typeof value}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertString(
|
||||||
|
value: unknown,
|
||||||
|
valueName: string,
|
||||||
|
): asserts value is string {
|
||||||
|
if (!isString(value)) {
|
||||||
|
throw new Error(`'${valueName}' should be of type 'string', but is of type '${typeof value}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNonEmpty(
|
||||||
|
value: Array<unknown>,
|
||||||
|
valueName: string,
|
||||||
|
): void {
|
||||||
|
if (value.length === 0) {
|
||||||
|
throw new Error(`'${valueName}' cannot be an empty array.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||||
|
import type { FunctionData } from '@/application/collections/';
|
||||||
|
import { ScriptCompiler } from './Script/Compiler/ScriptCompiler';
|
||||||
|
import { SyntaxFactory } from './Script/Validation/Syntax/SyntaxFactory';
|
||||||
|
import type { IScriptCompiler } from './Script/Compiler/IScriptCompiler';
|
||||||
|
import type { ILanguageSyntax } from './Script/Validation/Syntax/ILanguageSyntax';
|
||||||
|
import type { ISyntaxFactory } from './Script/Validation/Syntax/ISyntaxFactory';
|
||||||
|
|
||||||
|
export interface CategoryCollectionSpecificUtilities {
|
||||||
|
readonly compiler: IScriptCompiler;
|
||||||
|
readonly syntax: ILanguageSyntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createCollectionUtilities: CategoryCollectionSpecificUtilitiesFactory = (
|
||||||
|
functionsData: ReadonlyArray<FunctionData> | undefined,
|
||||||
|
scripting: IScriptingDefinition,
|
||||||
|
syntaxFactory: ISyntaxFactory = new SyntaxFactory(),
|
||||||
|
) => {
|
||||||
|
const syntax = syntaxFactory.create(scripting.language);
|
||||||
|
return {
|
||||||
|
compiler: new ScriptCompiler({
|
||||||
|
functions: functionsData ?? [],
|
||||||
|
syntax,
|
||||||
|
}),
|
||||||
|
syntax,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CategoryCollectionSpecificUtilitiesFactory {
|
||||||
|
(
|
||||||
|
functionsData: ReadonlyArray<FunctionData> | undefined,
|
||||||
|
scripting: IScriptingDefinition,
|
||||||
|
syntaxFactory?: ISyntaxFactory,
|
||||||
|
): CategoryCollectionSpecificUtilities;
|
||||||
|
}
|
||||||
187
src/application/Parser/Executable/CategoryParser.ts
Normal file
187
src/application/Parser/Executable/CategoryParser.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import type {
|
||||||
|
CategoryData, ScriptData, ExecutableData,
|
||||||
|
} from '@/application/collections/';
|
||||||
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||||
|
import type { Category } from '@/domain/Executables/Category/Category';
|
||||||
|
import { CollectionCategory } from '@/domain/Executables/Category/CollectionCategory';
|
||||||
|
import type { Script } from '@/domain/Executables/Script/Script';
|
||||||
|
import { parseDocs, type DocsParser } from './DocumentationParser';
|
||||||
|
import { parseScript, type ScriptParser } from './Script/ScriptParser';
|
||||||
|
import { createExecutableDataValidator, type ExecutableValidator, type ExecutableValidatorFactory } from './Validation/ExecutableValidator';
|
||||||
|
import { ExecutableType } from './Validation/ExecutableType';
|
||||||
|
import type { CategoryCollectionSpecificUtilities } from './CategoryCollectionSpecificUtilities';
|
||||||
|
|
||||||
|
let categoryIdCounter = 0;
|
||||||
|
|
||||||
|
export const parseCategory: CategoryParser = (
|
||||||
|
category: CategoryData,
|
||||||
|
collectionUtilities: CategoryCollectionSpecificUtilities,
|
||||||
|
categoryUtilities: CategoryParserUtilities = DefaultCategoryParserUtilities,
|
||||||
|
) => {
|
||||||
|
return parseCategoryRecursively({
|
||||||
|
categoryData: category,
|
||||||
|
collectionUtilities,
|
||||||
|
categoryUtilities,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CategoryParser {
|
||||||
|
(
|
||||||
|
category: CategoryData,
|
||||||
|
collectionUtilities: CategoryCollectionSpecificUtilities,
|
||||||
|
categoryUtilities?: CategoryParserUtilities,
|
||||||
|
): Category;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryParseContext {
|
||||||
|
readonly categoryData: CategoryData;
|
||||||
|
readonly collectionUtilities: CategoryCollectionSpecificUtilities;
|
||||||
|
readonly parentCategory?: CategoryData;
|
||||||
|
readonly categoryUtilities: CategoryParserUtilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCategoryRecursively(
|
||||||
|
context: CategoryParseContext,
|
||||||
|
): Category | never {
|
||||||
|
const validator = ensureValidCategory(context);
|
||||||
|
const children: CategoryChildren = {
|
||||||
|
subcategories: new Array<Category>(),
|
||||||
|
subscripts: new Array<Script>(),
|
||||||
|
};
|
||||||
|
for (const data of context.categoryData.children) {
|
||||||
|
parseUnknownExecutable({
|
||||||
|
data,
|
||||||
|
children,
|
||||||
|
parent: context.categoryData,
|
||||||
|
categoryUtilities: context.categoryUtilities,
|
||||||
|
collectionUtilities: context.collectionUtilities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return context.categoryUtilities.createCategory({
|
||||||
|
id: categoryIdCounter++,
|
||||||
|
name: context.categoryData.category,
|
||||||
|
docs: context.categoryUtilities.parseDocs(context.categoryData),
|
||||||
|
subcategories: children.subcategories,
|
||||||
|
scripts: children.subscripts,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw context.categoryUtilities.wrapError(
|
||||||
|
error,
|
||||||
|
validator.createContextualErrorMessage('Failed to parse category.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureValidCategory(
|
||||||
|
context: CategoryParseContext,
|
||||||
|
): ExecutableValidator {
|
||||||
|
const category = context.categoryData;
|
||||||
|
const validator: ExecutableValidator = context.categoryUtilities.createValidator({
|
||||||
|
type: ExecutableType.Category,
|
||||||
|
self: context.categoryData,
|
||||||
|
parentCategory: context.parentCategory,
|
||||||
|
});
|
||||||
|
validator.assertType((v) => v.assertObject({
|
||||||
|
value: category,
|
||||||
|
valueName: category.category ?? 'category',
|
||||||
|
allowedProperties: [
|
||||||
|
'docs', 'children', 'category',
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
validator.assertValidName(category.category);
|
||||||
|
validator.assertType((v) => v.assertNonEmptyCollection({
|
||||||
|
value: category.children,
|
||||||
|
valueName: category.category,
|
||||||
|
}));
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryChildren {
|
||||||
|
readonly subcategories: Category[];
|
||||||
|
readonly subscripts: Script[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExecutableParseContext {
|
||||||
|
readonly data: ExecutableData;
|
||||||
|
readonly children: CategoryChildren;
|
||||||
|
readonly parent: CategoryData;
|
||||||
|
readonly collectionUtilities: CategoryCollectionSpecificUtilities;
|
||||||
|
readonly categoryUtilities: CategoryParserUtilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseUnknownExecutable(context: ExecutableParseContext) {
|
||||||
|
const validator: ExecutableValidator = context.categoryUtilities.createValidator({
|
||||||
|
self: context.data,
|
||||||
|
parentCategory: context.parent,
|
||||||
|
});
|
||||||
|
validator.assertType((v) => v.assertObject({
|
||||||
|
value: context.data,
|
||||||
|
valueName: 'Executable',
|
||||||
|
}));
|
||||||
|
validator.assert(
|
||||||
|
() => isCategory(context.data) || isScript(context.data),
|
||||||
|
'Executable is neither a category or a script.',
|
||||||
|
);
|
||||||
|
if (isCategory(context.data)) {
|
||||||
|
const subCategory = parseCategoryRecursively({
|
||||||
|
categoryData: context.data,
|
||||||
|
collectionUtilities: context.collectionUtilities,
|
||||||
|
parentCategory: context.parent,
|
||||||
|
categoryUtilities: context.categoryUtilities,
|
||||||
|
});
|
||||||
|
context.children.subcategories.push(subCategory);
|
||||||
|
} else { // A script
|
||||||
|
const script = context.categoryUtilities.parseScript(context.data, context.collectionUtilities);
|
||||||
|
context.children.subscripts.push(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isScript(data: ExecutableData): data is ScriptData {
|
||||||
|
return hasCode(data) || hasCall(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCategory(data: ExecutableData): data is CategoryData {
|
||||||
|
return hasProperty(data, 'category');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCode(data: unknown): boolean {
|
||||||
|
return hasProperty(data, 'code');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCall(data: unknown) {
|
||||||
|
return hasProperty(data, 'call');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasProperty(
|
||||||
|
object: unknown,
|
||||||
|
propertyName: string,
|
||||||
|
): object is NonNullable<object> {
|
||||||
|
if (typeof object !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (object === null) { // `typeof object` is `null`
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Object.prototype.hasOwnProperty.call(object, propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CategoryFactory = (
|
||||||
|
...parameters: ConstructorParameters<typeof CollectionCategory>
|
||||||
|
) => Category;
|
||||||
|
|
||||||
|
interface CategoryParserUtilities {
|
||||||
|
readonly createCategory: CategoryFactory;
|
||||||
|
readonly wrapError: ErrorWithContextWrapper;
|
||||||
|
readonly createValidator: ExecutableValidatorFactory;
|
||||||
|
readonly parseScript: ScriptParser;
|
||||||
|
readonly parseDocs: DocsParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultCategoryParserUtilities: CategoryParserUtilities = {
|
||||||
|
createCategory: (...parameters) => new CollectionCategory(...parameters),
|
||||||
|
wrapError: wrapErrorWithAdditionalContext,
|
||||||
|
createValidator: createExecutableDataValidator,
|
||||||
|
parseScript,
|
||||||
|
parseDocs,
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { DocumentableData, DocumentationData } from '@/application/collections/';
|
import type { DocumentableData, DocumentationData } from '@/application/collections/';
|
||||||
import { isString, isArray } from '@/TypeHelpers';
|
import { isString, isArray } from '@/TypeHelpers';
|
||||||
|
|
||||||
export function parseDocs(documentable: DocumentableData): readonly string[] {
|
export const parseDocs: DocsParser = (documentable) => {
|
||||||
const { docs } = documentable;
|
const { docs } = documentable;
|
||||||
if (!docs) {
|
if (!docs) {
|
||||||
return [];
|
return [];
|
||||||
@@ -9,6 +9,12 @@ export function parseDocs(documentable: DocumentableData): readonly string[] {
|
|||||||
let result = new DocumentationContainer();
|
let result = new DocumentationContainer();
|
||||||
result = addDocs(docs, result);
|
result = addDocs(docs, result);
|
||||||
return result.getAll();
|
return result.getAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DocsParser {
|
||||||
|
(
|
||||||
|
documentable: DocumentableData,
|
||||||
|
): readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDocs(
|
function addDocs(
|
||||||
@@ -44,5 +50,5 @@ class DocumentationContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function throwInvalidType(): never {
|
function throwInvalidType(): never {
|
||||||
throw new Error('docs field (documentation) must be an array of strings');
|
throw new Error('docs field (documentation) must be a single string or an array of strings.');
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||||
import { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from './ExpressionEvaluationContext';
|
import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from './ExpressionEvaluationContext';
|
||||||
import { ExpressionPosition } from './ExpressionPosition';
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
@@ -7,15 +7,18 @@ import type { IReadOnlyFunctionParameterCollection } from '../../Function/Parame
|
|||||||
import type { IExpression } from './IExpression';
|
import type { IExpression } from './IExpression';
|
||||||
|
|
||||||
export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
|
export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
|
||||||
|
|
||||||
export class Expression implements IExpression {
|
export class Expression implements IExpression {
|
||||||
public readonly parameters: IReadOnlyFunctionParameterCollection;
|
public readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||||
|
|
||||||
constructor(
|
public readonly position: ExpressionPosition;
|
||||||
public readonly position: ExpressionPosition,
|
|
||||||
public readonly evaluator: ExpressionEvaluator,
|
public readonly evaluator: ExpressionEvaluator;
|
||||||
parameters?: IReadOnlyFunctionParameterCollection,
|
|
||||||
) {
|
constructor(parameters: ExpressionInitParameters) {
|
||||||
this.parameters = parameters ?? new FunctionParameterCollection();
|
this.parameters = parameters.parameters ?? new FunctionParameterCollection();
|
||||||
|
this.evaluator = parameters.evaluator;
|
||||||
|
this.position = parameters.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(context: IExpressionEvaluationContext): string {
|
public evaluate(context: IExpressionEvaluationContext): string {
|
||||||
@@ -26,6 +29,12 @@ export class Expression implements IExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExpressionInitParameters {
|
||||||
|
readonly position: ExpressionPosition,
|
||||||
|
readonly evaluator: ExpressionEvaluator,
|
||||||
|
readonly parameters?: IReadOnlyFunctionParameterCollection,
|
||||||
|
}
|
||||||
|
|
||||||
function validateThatAllRequiredParametersAreSatisfied(
|
function validateThatAllRequiredParametersAreSatisfied(
|
||||||
parameters: IReadOnlyFunctionParameterCollection,
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
args: IReadOnlyFunctionCallArgumentCollection,
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import { ExpressionPosition } from './ExpressionPosition';
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
|
|
||||||
export function createPositionFromRegexFullMatch(
|
export interface ExpressionPositionFactory {
|
||||||
match: RegExpMatchArray,
|
(
|
||||||
): ExpressionPosition {
|
match: RegExpMatchArray,
|
||||||
|
): ExpressionPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPositionFromRegexFullMatch
|
||||||
|
: ExpressionPositionFactory = (match) => {
|
||||||
const startPos = match.index;
|
const startPos = match.index;
|
||||||
if (startPos === undefined) {
|
if (startPos === undefined) {
|
||||||
throw new Error(`Regex match did not yield any results: ${JSON.stringify(match)}`);
|
throw new Error(`Regex match did not yield any results: ${JSON.stringify(match)}`);
|
||||||
@@ -13,4 +18,4 @@ export function createPositionFromRegexFullMatch(
|
|||||||
}
|
}
|
||||||
const endPos = startPos + fullMatch.length;
|
const endPos = startPos + fullMatch.length;
|
||||||
return new ExpressionPosition(startPos, endPos);
|
return new ExpressionPosition(startPos, endPos);
|
||||||
}
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type IExpressionEvaluationContext, ExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
import { type IExpressionEvaluationContext, ExpressionEvaluationContext } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
||||||
import type { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
|
import type { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import type { IExpressionsCompiler } from './IExpressionsCompiler';
|
import type { IExpressionsCompiler } from './IExpressionsCompiler';
|
||||||
@@ -3,10 +3,10 @@ import { WithParser } from '../SyntaxParsers/WithParser';
|
|||||||
import type { IExpression } from '../Expression/IExpression';
|
import type { IExpression } from '../Expression/IExpression';
|
||||||
import type { IExpressionParser } from './IExpressionParser';
|
import type { IExpressionParser } from './IExpressionParser';
|
||||||
|
|
||||||
const Parsers = [
|
const Parsers: readonly IExpressionParser[] = [
|
||||||
new ParameterSubstitutionParser(),
|
new ParameterSubstitutionParser(),
|
||||||
new WithParser(),
|
new WithParser(),
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
export class CompositeExpressionParser implements IExpressionParser {
|
export class CompositeExpressionParser implements IExpressionParser {
|
||||||
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
|
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||||
|
import { Expression, type ExpressionEvaluator } from '../../Expression/Expression';
|
||||||
|
import { createPositionFromRegexFullMatch, type ExpressionPositionFactory } from '../../Expression/ExpressionPositionFactory';
|
||||||
|
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from '../../../Function/Parameter/FunctionParameterCollectionFactory';
|
||||||
|
import type { IExpressionParser } from '../IExpressionParser';
|
||||||
|
import type { IExpression } from '../../Expression/IExpression';
|
||||||
|
import type { FunctionParameter } from '../../../Function/Parameter/FunctionParameter';
|
||||||
|
import type { IFunctionParameterCollection, IReadOnlyFunctionParameterCollection } from '../../../Function/Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
|
export interface RegexParserUtilities {
|
||||||
|
readonly wrapError: ErrorWithContextWrapper;
|
||||||
|
readonly createPosition: ExpressionPositionFactory;
|
||||||
|
readonly createExpression: ExpressionFactory;
|
||||||
|
readonly createParameterCollection: FunctionParameterCollectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class RegexParser implements IExpressionParser {
|
||||||
|
protected abstract readonly regex: RegExp;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly utilities: RegexParserUtilities = DefaultRegexParserUtilities,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public findExpressions(code: string): IExpression[] {
|
||||||
|
return Array.from(this.findRegexExpressions(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract buildExpression(match: RegExpMatchArray): PrimitiveExpression;
|
||||||
|
|
||||||
|
private* findRegexExpressions(code: string): Iterable<IExpression> {
|
||||||
|
if (!code) {
|
||||||
|
throw new Error(
|
||||||
|
this.buildErrorMessageWithContext({ errorMessage: 'missing code', code: 'EMPTY' }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const createErrorContext = (message: string): ErrorContext => ({ code, errorMessage: message });
|
||||||
|
const matches = this.doOrRethrow(
|
||||||
|
() => code.matchAll(this.regex),
|
||||||
|
createErrorContext('Failed to match regex.'),
|
||||||
|
);
|
||||||
|
for (const match of matches) {
|
||||||
|
const primitiveExpression = this.doOrRethrow(
|
||||||
|
() => this.buildExpression(match),
|
||||||
|
createErrorContext('Failed to build expression.'),
|
||||||
|
);
|
||||||
|
const position = this.doOrRethrow(
|
||||||
|
() => this.utilities.createPosition(match),
|
||||||
|
createErrorContext('Failed to create position.'),
|
||||||
|
);
|
||||||
|
const parameters = this.doOrRethrow(
|
||||||
|
() => createParameters(
|
||||||
|
primitiveExpression,
|
||||||
|
this.utilities.createParameterCollection(),
|
||||||
|
),
|
||||||
|
createErrorContext('Failed to create parameters.'),
|
||||||
|
);
|
||||||
|
const expression = this.doOrRethrow(
|
||||||
|
() => this.utilities.createExpression({
|
||||||
|
position,
|
||||||
|
evaluator: primitiveExpression.evaluator,
|
||||||
|
parameters,
|
||||||
|
}),
|
||||||
|
createErrorContext('Failed to create expression.'),
|
||||||
|
);
|
||||||
|
yield expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private doOrRethrow<T>(
|
||||||
|
action: () => T,
|
||||||
|
context: ErrorContext,
|
||||||
|
): T {
|
||||||
|
try {
|
||||||
|
return action();
|
||||||
|
} catch (error) {
|
||||||
|
throw this.utilities.wrapError(
|
||||||
|
error,
|
||||||
|
this.buildErrorMessageWithContext(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildErrorMessageWithContext(context: ErrorContext): string {
|
||||||
|
return [
|
||||||
|
context.errorMessage,
|
||||||
|
`Class name: ${this.constructor.name}`,
|
||||||
|
`Regex pattern used: ${this.regex}`,
|
||||||
|
`Code: ${context.code}`,
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorContext {
|
||||||
|
readonly errorMessage: string,
|
||||||
|
readonly code: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function createParameters(
|
||||||
|
expression: PrimitiveExpression,
|
||||||
|
parameterCollection: IFunctionParameterCollection,
|
||||||
|
): IReadOnlyFunctionParameterCollection {
|
||||||
|
return (expression.parameters || [])
|
||||||
|
.reduce((parameters, parameter) => {
|
||||||
|
parameters.addParameter(parameter);
|
||||||
|
return parameters;
|
||||||
|
}, parameterCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrimitiveExpression {
|
||||||
|
readonly evaluator: ExpressionEvaluator;
|
||||||
|
readonly parameters?: readonly FunctionParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExpressionFactory {
|
||||||
|
(
|
||||||
|
...args: ConstructorParameters<typeof Expression>
|
||||||
|
): IExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultRegexParserUtilities: RegexParserUtilities = {
|
||||||
|
wrapError: wrapErrorWithAdditionalContext,
|
||||||
|
createPosition: createPositionFromRegexFullMatch,
|
||||||
|
createExpression: (...args) => new Expression(...args),
|
||||||
|
createParameterCollection: createFunctionParameterCollection,
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
import { RegexParser, type PrimitiveExpression } from '../Parser/Regex/RegexParser';
|
||||||
import { RegexParser, type IPrimitiveExpression } from '../Parser/Regex/RegexParser';
|
|
||||||
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
||||||
|
|
||||||
export class ParameterSubstitutionParser extends RegexParser {
|
export class ParameterSubstitutionParser extends RegexParser {
|
||||||
@@ -12,11 +11,14 @@ export class ParameterSubstitutionParser extends RegexParser {
|
|||||||
.expectExpressionEnd()
|
.expectExpressionEnd()
|
||||||
.buildRegExp();
|
.buildRegExp();
|
||||||
|
|
||||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
protected buildExpression(match: RegExpMatchArray): PrimitiveExpression {
|
||||||
const parameterName = match[1];
|
const parameterName = match[1];
|
||||||
const pipeline = match[2];
|
const pipeline = match[2];
|
||||||
return {
|
return {
|
||||||
parameters: [new FunctionParameter(parameterName, false)],
|
parameters: [{
|
||||||
|
name: parameterName,
|
||||||
|
isOptional: false,
|
||||||
|
}],
|
||||||
evaluator: (context) => {
|
evaluator: (context) => {
|
||||||
const { argumentValue } = context.args.getArgument(parameterName);
|
const { argumentValue } = context.args.getArgument(parameterName);
|
||||||
if (!pipeline) {
|
if (!pipeline) {
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// eslint-disable-next-line max-classes-per-file
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import type { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
import type { IExpressionParser } from '@/application/Parser/Executable/Script/Compiler/Expressions/Parser/IExpressionParser';
|
||||||
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
import { FunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
|
||||||
import { ExpressionPosition } from '../Expression/ExpressionPosition';
|
import { ExpressionPosition } from '../Expression/ExpressionPosition';
|
||||||
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
||||||
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory';
|
import { createPositionFromRegexFullMatch } from '../Expression/ExpressionPositionFactory';
|
||||||
@@ -84,7 +83,10 @@ class WithStatementBuilder {
|
|||||||
|
|
||||||
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
|
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
|
||||||
const parameters = new FunctionParameterCollection();
|
const parameters = new FunctionParameterCollection();
|
||||||
parameters.addParameter(new FunctionParameter(this.parameterName, true));
|
parameters.addParameter({
|
||||||
|
name: this.parameterName,
|
||||||
|
isOptional: true,
|
||||||
|
});
|
||||||
const position = new ExpressionPosition(
|
const position = new ExpressionPosition(
|
||||||
this.startExpressionPosition.start,
|
this.startExpressionPosition.start,
|
||||||
endExpressionPosition.end,
|
endExpressionPosition.end,
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||||
|
import { validateParameterName, type ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
|
||||||
|
|
||||||
|
export interface FunctionCallArgument {
|
||||||
|
readonly parameterName: string;
|
||||||
|
readonly argumentValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FunctionCallArgumentFactory {
|
||||||
|
(
|
||||||
|
parameterName: string,
|
||||||
|
argumentValue: string,
|
||||||
|
utilities?: FunctionCallArgumentFactoryUtilities,
|
||||||
|
): FunctionCallArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createFunctionCallArgument: FunctionCallArgumentFactory = (
|
||||||
|
parameterName: string,
|
||||||
|
argumentValue: string,
|
||||||
|
utilities: FunctionCallArgumentFactoryUtilities = DefaultUtilities,
|
||||||
|
): FunctionCallArgument => {
|
||||||
|
utilities.validateParameterName(parameterName);
|
||||||
|
utilities.typeValidator.assertNonEmptyString({
|
||||||
|
value: argumentValue,
|
||||||
|
valueName: `Missing argument value for the parameter "${parameterName}".`,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
parameterName,
|
||||||
|
argumentValue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FunctionCallArgumentFactoryUtilities {
|
||||||
|
readonly typeValidator: TypeValidator;
|
||||||
|
readonly validateParameterName: ParameterNameValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultUtilities: FunctionCallArgumentFactoryUtilities = {
|
||||||
|
typeValidator: createTypeValidator(),
|
||||||
|
validateParameterName,
|
||||||
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { IFunctionCallArgument } from './IFunctionCallArgument';
|
import type { FunctionCallArgument } from './FunctionCallArgument';
|
||||||
import type { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
|
import type { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
|
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
|
||||||
private readonly arguments = new Map<string, IFunctionCallArgument>();
|
private readonly arguments = new Map<string, FunctionCallArgument>();
|
||||||
|
|
||||||
public addArgument(argument: IFunctionCallArgument): void {
|
public addArgument(argument: FunctionCallArgument): void {
|
||||||
if (this.hasArgument(argument.parameterName)) {
|
if (this.hasArgument(argument.parameterName)) {
|
||||||
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
|
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ export class FunctionCallArgumentCollection implements IFunctionCallArgumentColl
|
|||||||
return this.arguments.has(parameterName);
|
return this.arguments.has(parameterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getArgument(parameterName: string): IFunctionCallArgument {
|
public getArgument(parameterName: string): FunctionCallArgument {
|
||||||
if (!parameterName) {
|
if (!parameterName) {
|
||||||
throw new Error('missing parameter name');
|
throw new Error('missing parameter name');
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { IFunctionCallArgument } from './IFunctionCallArgument';
|
import type { FunctionCallArgument } from './FunctionCallArgument';
|
||||||
|
|
||||||
export interface IReadOnlyFunctionCallArgumentCollection {
|
export interface IReadOnlyFunctionCallArgumentCollection {
|
||||||
getArgument(parameterName: string): IFunctionCallArgument;
|
getArgument(parameterName: string): FunctionCallArgument;
|
||||||
getAllParameterNames(): string[];
|
getAllParameterNames(): string[];
|
||||||
hasArgument(parameterName: string): boolean;
|
hasArgument(parameterName: string): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFunctionCallArgumentCollection extends IReadOnlyFunctionCallArgumentCollection {
|
export interface IFunctionCallArgumentCollection extends IReadOnlyFunctionCallArgumentCollection {
|
||||||
addArgument(argument: IFunctionCallArgument): void;
|
addArgument(argument: FunctionCallArgument): void;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
import type { FunctionCall } from '../FunctionCall';
|
import type { FunctionCall } from '../FunctionCall';
|
||||||
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
|
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
import type { CompiledCode } from './CompiledCode';
|
import type { CompiledCode } from './CompiledCode';
|
||||||
import type { FunctionCall } from '../FunctionCall';
|
import type { FunctionCall } from '../FunctionCall';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
import { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
|
import { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
|
||||||
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
|
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
|
||||||
import type { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||||
@@ -72,7 +72,7 @@ function throwIfUnexpectedParametersExist(
|
|||||||
// eslint-disable-next-line prefer-template
|
// eslint-disable-next-line prefer-template
|
||||||
`Function "${functionName}" has unexpected parameter(s) provided: `
|
`Function "${functionName}" has unexpected parameter(s) provided: `
|
||||||
+ `"${unexpectedParameters.join('", "')}"`
|
+ `"${unexpectedParameters.join('", "')}"`
|
||||||
+ '. Expected parameter(s): '
|
+ '.\nExpected parameter(s): '
|
||||||
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}".` : 'none'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import type { ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||||
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
import type { CompiledCode } from '../CompiledCode';
|
import type { CompiledCode } from '../CompiledCode';
|
||||||
import type { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
import type { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||||
|
|
||||||
export interface ArgumentCompiler {
|
export interface ArgumentCompiler {
|
||||||
createCompiledNestedCall(
|
createCompiledNestedCall(
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||||
import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
import { ParsedFunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/ParsedFunctionCall';
|
||||||
import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||||
|
import { createFunctionCallArgument, type FunctionCallArgument, type FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
import type { ArgumentCompiler } from './ArgumentCompiler';
|
import type { ArgumentCompiler } from './ArgumentCompiler';
|
||||||
|
|
||||||
export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
|
export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
|
||||||
constructor(
|
constructor(private readonly utilities: ArgumentCompilationUtilities = DefaultUtilities) { }
|
||||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public createCompiledNestedCall(
|
public createCompiledNestedCall(
|
||||||
nestedFunction: FunctionCall,
|
nestedFunction: FunctionCall,
|
||||||
@@ -22,18 +21,24 @@ export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
|
|||||||
nestedFunction,
|
nestedFunction,
|
||||||
parentFunction.args,
|
parentFunction.args,
|
||||||
context,
|
context,
|
||||||
this.expressionsCompiler,
|
this.utilities,
|
||||||
);
|
);
|
||||||
const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
|
const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
|
||||||
return compiledCall;
|
return compiledCall;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ArgumentCompilationUtilities {
|
||||||
|
readonly expressionsCompiler: IExpressionsCompiler,
|
||||||
|
readonly wrapError: ErrorWithContextWrapper;
|
||||||
|
readonly createCallArgument: FunctionCallArgumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
function compileNestedFunctionArguments(
|
function compileNestedFunctionArguments(
|
||||||
nestedFunction: FunctionCall,
|
nestedFunction: FunctionCall,
|
||||||
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
||||||
context: FunctionCallCompilationContext,
|
context: FunctionCallCompilationContext,
|
||||||
expressionsCompiler: IExpressionsCompiler,
|
utilities: ArgumentCompilationUtilities,
|
||||||
): IReadOnlyFunctionCallArgumentCollection {
|
): IReadOnlyFunctionCallArgumentCollection {
|
||||||
const requiredParameterNames = context
|
const requiredParameterNames = context
|
||||||
.allFunctions
|
.allFunctions
|
||||||
@@ -47,7 +52,7 @@ function compileNestedFunctionArguments(
|
|||||||
paramName,
|
paramName,
|
||||||
nestedFunction,
|
nestedFunction,
|
||||||
parentFunctionArgs,
|
parentFunctionArgs,
|
||||||
expressionsCompiler,
|
utilities,
|
||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
// Filter out arguments with absent values
|
// Filter out arguments with absent values
|
||||||
@@ -67,7 +72,7 @@ function compileNestedFunctionArguments(
|
|||||||
.map(({
|
.map(({
|
||||||
parameterName,
|
parameterName,
|
||||||
compiledArgumentValue,
|
compiledArgumentValue,
|
||||||
}) => new FunctionCallArgument(parameterName, compiledArgumentValue));
|
}) => utilities.createCallArgument(parameterName, compiledArgumentValue));
|
||||||
return buildArgumentCollectionFromArguments(compiledArguments);
|
return buildArgumentCollectionFromArguments(compiledArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,13 +94,13 @@ function compileArgument(
|
|||||||
parameterName: string,
|
parameterName: string,
|
||||||
nestedFunction: FunctionCall,
|
nestedFunction: FunctionCall,
|
||||||
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
||||||
expressionsCompiler: IExpressionsCompiler,
|
utilities: ArgumentCompilationUtilities,
|
||||||
): string {
|
): string {
|
||||||
try {
|
try {
|
||||||
const { argumentValue: codeInArgument } = nestedFunction.args.getArgument(parameterName);
|
const { argumentValue: codeInArgument } = nestedFunction.args.getArgument(parameterName);
|
||||||
return expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
|
return utilities.expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
throw new AggregateError([err], `Error when compiling argument for "${parameterName}"`);
|
throw utilities.wrapError(error, `Error when compiling argument for "${parameterName}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,3 +112,9 @@ function buildArgumentCollectionFromArguments(
|
|||||||
return compiledArgs;
|
return compiledArgs;
|
||||||
}, new FunctionCallArgumentCollection());
|
}, new FunctionCallArgumentCollection());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DefaultUtilities: ArgumentCompilationUtilities = {
|
||||||
|
expressionsCompiler: new ExpressionsCompiler(),
|
||||||
|
wrapError: wrapErrorWithAdditionalContext,
|
||||||
|
createCallArgument: createFunctionCallArgument,
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
import { ExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
import type { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
import { FunctionBodyType, type ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import { FunctionBodyType, type ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||||
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
import type { CompiledCode } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||||
import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
||||||
|
|
||||||
export class InlineFunctionCallCompiler implements SingleCallCompilerStrategy {
|
export class InlineFunctionCallCompiler implements SingleCallCompilerStrategy {
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import { type CallFunctionBody, FunctionBodyType, type ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import {
|
||||||
import type { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
type CallFunctionBody, FunctionBodyType,
|
||||||
import type { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
type ISharedFunction,
|
||||||
import type { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
} from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import type { FunctionCallCompilationContext } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||||
|
import type { CompiledCode } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||||
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||||
import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
|
import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
|
||||||
import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
import type { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
||||||
import type { ArgumentCompiler } from './Argument/ArgumentCompiler';
|
import type { ArgumentCompiler } from './Argument/ArgumentCompiler';
|
||||||
|
|
||||||
export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
|
export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly argumentCompiler: ArgumentCompiler = new NestedFunctionArgumentCompiler(),
|
private readonly argumentCompiler: ArgumentCompiler
|
||||||
|
= new NestedFunctionArgumentCompiler(),
|
||||||
|
private readonly wrapError: ErrorWithContextWrapper
|
||||||
|
= wrapErrorWithAdditionalContext,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +36,11 @@ export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
|
|||||||
const compiledNestedCall = context.singleCallCompiler
|
const compiledNestedCall = context.singleCallCompiler
|
||||||
.compileSingleCall(compiledParentCall, context);
|
.compileSingleCall(compiledParentCall, context);
|
||||||
return compiledNestedCall;
|
return compiledNestedCall;
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
throw new AggregateError([err], `Error with call to "${nestedCall.functionName}" function from "${callToFunction.functionName}" function`);
|
throw this.wrapError(
|
||||||
|
error,
|
||||||
|
`Failed to call '${nestedCall.functionName}' (callee function) from '${callToFunction.functionName}' (caller function).`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}).flat();
|
}).flat();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import type {
|
||||||
|
FunctionCallData,
|
||||||
|
FunctionCallsData,
|
||||||
|
FunctionCallParametersData,
|
||||||
|
} from '@/application/collections/';
|
||||||
|
import { isArray, isPlainObject } from '@/TypeHelpers';
|
||||||
|
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||||
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
|
import { ParsedFunctionCall } from './ParsedFunctionCall';
|
||||||
|
import { createFunctionCallArgument, type FunctionCallArgumentFactory } from './Argument/FunctionCallArgument';
|
||||||
|
import type { FunctionCall } from './FunctionCall';
|
||||||
|
|
||||||
|
export interface FunctionCallsParser {
|
||||||
|
(
|
||||||
|
calls: FunctionCallsData,
|
||||||
|
utilities?: FunctionCallParsingUtilities,
|
||||||
|
): FunctionCall[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FunctionCallParsingUtilities {
|
||||||
|
readonly typeValidator: TypeValidator;
|
||||||
|
readonly createCallArgument: FunctionCallArgumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultUtilities: FunctionCallParsingUtilities = {
|
||||||
|
typeValidator: createTypeValidator(),
|
||||||
|
createCallArgument: createFunctionCallArgument,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseFunctionCalls: FunctionCallsParser = (
|
||||||
|
calls,
|
||||||
|
utilities = DefaultUtilities,
|
||||||
|
) => {
|
||||||
|
const sequence = getCallSequence(calls, utilities.typeValidator);
|
||||||
|
return sequence.map((call) => parseFunctionCall(call, utilities));
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCallSequence(calls: FunctionCallsData, validator: TypeValidator): FunctionCallData[] {
|
||||||
|
if (!isPlainObject(calls) && !isArray(calls)) {
|
||||||
|
throw new Error('called function(s) must be an object or array');
|
||||||
|
}
|
||||||
|
if (isArray(calls)) {
|
||||||
|
validator.assertNonEmptyCollection({
|
||||||
|
value: calls,
|
||||||
|
valueName: 'function call sequence',
|
||||||
|
});
|
||||||
|
return calls as FunctionCallData[];
|
||||||
|
}
|
||||||
|
const singleCall = calls as FunctionCallData;
|
||||||
|
return [singleCall];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFunctionCall(
|
||||||
|
call: FunctionCallData,
|
||||||
|
utilities: FunctionCallParsingUtilities,
|
||||||
|
): FunctionCall {
|
||||||
|
utilities.typeValidator.assertObject({
|
||||||
|
value: call,
|
||||||
|
valueName: 'function call',
|
||||||
|
allowedProperties: ['function', 'parameters'],
|
||||||
|
});
|
||||||
|
const callArgs = parseArgs(call.parameters, utilities.createCallArgument);
|
||||||
|
return new ParsedFunctionCall(call.function, callArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(
|
||||||
|
parameters: FunctionCallParametersData | undefined,
|
||||||
|
createArgument: FunctionCallArgumentFactory,
|
||||||
|
): FunctionCallArgumentCollection {
|
||||||
|
const parametersMap = parameters ?? {};
|
||||||
|
return Object.keys(parametersMap)
|
||||||
|
.map((parameterName) => {
|
||||||
|
const argumentValue = parametersMap[parameterName];
|
||||||
|
return createArgument(parameterName, argumentValue);
|
||||||
|
})
|
||||||
|
.reduce((args, arg) => {
|
||||||
|
args.addArgument(arg);
|
||||||
|
return args;
|
||||||
|
}, new FunctionCallArgumentCollection());
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface IFunctionParameter {
|
export interface FunctionParameter {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly isOptional: boolean;
|
readonly isOptional: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import type { IFunctionParameterCollection } from './IFunctionParameterCollection';
|
import type { IFunctionParameterCollection } from './IFunctionParameterCollection';
|
||||||
import type { IFunctionParameter } from './IFunctionParameter';
|
import type { FunctionParameter } from './FunctionParameter';
|
||||||
|
|
||||||
export class FunctionParameterCollection implements IFunctionParameterCollection {
|
export class FunctionParameterCollection implements IFunctionParameterCollection {
|
||||||
private parameters = new Array<IFunctionParameter>();
|
private parameters = new Array<FunctionParameter>();
|
||||||
|
|
||||||
public get all(): readonly IFunctionParameter[] {
|
public get all(): readonly FunctionParameter[] {
|
||||||
return this.parameters;
|
return this.parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addParameter(parameter: IFunctionParameter) {
|
public addParameter(parameter: FunctionParameter) {
|
||||||
this.ensureValidParameter(parameter);
|
this.ensureValidParameter(parameter);
|
||||||
this.parameters.push(parameter);
|
this.parameters.push(parameter);
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ export class FunctionParameterCollection implements IFunctionParameterCollection
|
|||||||
return this.parameters.find((existingParameter) => existingParameter.name === name);
|
return this.parameters.find((existingParameter) => existingParameter.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureValidParameter(parameter: IFunctionParameter) {
|
private ensureValidParameter(parameter: FunctionParameter) {
|
||||||
if (this.includesName(parameter.name)) {
|
if (this.includesName(parameter.name)) {
|
||||||
throw new Error(`duplicate parameter name: "${parameter.name}"`);
|
throw new Error(`duplicate parameter name: "${parameter.name}"`);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { FunctionParameterCollection } from './FunctionParameterCollection';
|
||||||
|
import type { IFunctionParameterCollection } from './IFunctionParameterCollection';
|
||||||
|
|
||||||
|
export interface FunctionParameterCollectionFactory {
|
||||||
|
(
|
||||||
|
...args: ConstructorParameters<typeof FunctionParameterCollection>
|
||||||
|
): IFunctionParameterCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createFunctionParameterCollection: FunctionParameterCollectionFactory = (...args) => {
|
||||||
|
return new FunctionParameterCollection(...args);
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { ParameterDefinitionData } from '@/application/collections/';
|
||||||
|
import { validateParameterName, type ParameterNameValidator } from '../Shared/ParameterNameValidator';
|
||||||
|
import type { FunctionParameter } from './FunctionParameter';
|
||||||
|
|
||||||
|
export interface FunctionParameterParser {
|
||||||
|
(
|
||||||
|
data: ParameterDefinitionData,
|
||||||
|
validator?: ParameterNameValidator,
|
||||||
|
): FunctionParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseFunctionParameter: FunctionParameterParser = (
|
||||||
|
data,
|
||||||
|
validator = validateParameterName,
|
||||||
|
) => {
|
||||||
|
validator(data.name);
|
||||||
|
return {
|
||||||
|
name: data.name,
|
||||||
|
isOptional: data.optional || false,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { FunctionParameter } from './FunctionParameter';
|
||||||
|
|
||||||
|
export interface IReadOnlyFunctionParameterCollection {
|
||||||
|
readonly all: readonly FunctionParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionParameterCollection extends IReadOnlyFunctionParameterCollection {
|
||||||
|
addParameter(parameter: FunctionParameter): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||||
|
|
||||||
|
export interface ParameterNameValidator {
|
||||||
|
(
|
||||||
|
parameterName: string,
|
||||||
|
typeValidator?: TypeValidator,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateParameterName = (
|
||||||
|
parameterName: string,
|
||||||
|
typeValidator = createTypeValidator(),
|
||||||
|
) => {
|
||||||
|
typeValidator.assertNonEmptyString({
|
||||||
|
value: parameterName,
|
||||||
|
valueName: 'parameter name',
|
||||||
|
rule: {
|
||||||
|
expectedMatch: /^[0-9a-zA-Z]+$/,
|
||||||
|
errorMessage: `parameter name must be alphanumeric but it was "${parameterName}".`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -15,7 +15,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
|||||||
if (!name) { throw Error('missing function name'); }
|
if (!name) { throw Error('missing function name'); }
|
||||||
const func = this.functionsByName.get(name);
|
const func = this.functionsByName.get(name);
|
||||||
if (!func) {
|
if (!func) {
|
||||||
throw new Error(`called function is not defined "${name}"`);
|
throw new Error(`Called function is not defined: "${name}"`);
|
||||||
}
|
}
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
@@ -1,58 +1,79 @@
|
|||||||
import type {
|
import type {
|
||||||
FunctionData, CodeInstruction, CodeFunctionData, CallFunctionData, CallInstruction,
|
FunctionData, CodeInstruction, CodeFunctionData, CallFunctionData,
|
||||||
|
CallInstruction, ParameterDefinitionData,
|
||||||
} from '@/application/collections/';
|
} from '@/application/collections/';
|
||||||
import type { ILanguageSyntax } from '@/application/Parser/Script/Validation/Syntax/ILanguageSyntax';
|
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
||||||
import { CodeValidator } from '@/application/Parser/Script/Validation/CodeValidator';
|
import { CodeValidator } from '@/application/Parser/Executable/Script/Validation/CodeValidator';
|
||||||
import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmptyLines';
|
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
|
||||||
import { NoDuplicatedLines } from '@/application/Parser/Script/Validation/Rules/NoDuplicatedLines';
|
import { NoDuplicatedLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoDuplicatedLines';
|
||||||
import type { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
|
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
||||||
import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
|
import { isArray, isNullOrUndefined, isPlainObject } from '@/TypeHelpers';
|
||||||
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||||
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
||||||
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||||
import { FunctionParameter } from './Parameter/FunctionParameter';
|
import { parseFunctionCalls, type FunctionCallsParser } from './Call/FunctionCallsParser';
|
||||||
import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection';
|
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from './Parameter/FunctionParameterCollectionFactory';
|
||||||
import { parseFunctionCalls } from './Call/FunctionCallParser';
|
import { parseFunctionParameter, type FunctionParameterParser } from './Parameter/FunctionParameterParser';
|
||||||
|
import type { FunctionParameter } from './Parameter/FunctionParameter';
|
||||||
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
import type { ISharedFunctionsParser } from './ISharedFunctionsParser';
|
|
||||||
import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
import type { ISharedFunction } from './ISharedFunction';
|
import type { ISharedFunction } from './ISharedFunction';
|
||||||
|
|
||||||
export class SharedFunctionsParser implements ISharedFunctionsParser {
|
export interface SharedFunctionsParser {
|
||||||
public static readonly instance: ISharedFunctionsParser = new SharedFunctionsParser();
|
(
|
||||||
|
|
||||||
constructor(private readonly codeValidator: ICodeValidator = CodeValidator.instance) { }
|
|
||||||
|
|
||||||
public parseFunctions(
|
|
||||||
functions: readonly FunctionData[],
|
functions: readonly FunctionData[],
|
||||||
syntax: ILanguageSyntax,
|
syntax: ILanguageSyntax,
|
||||||
): ISharedFunctionCollection {
|
utilities?: SharedFunctionsParsingUtilities,
|
||||||
const collection = new SharedFunctionCollection();
|
): ISharedFunctionCollection;
|
||||||
if (!functions.length) {
|
}
|
||||||
return collection;
|
|
||||||
}
|
export const parseSharedFunctions: SharedFunctionsParser = (
|
||||||
ensureValidFunctions(functions);
|
functions: readonly FunctionData[],
|
||||||
return functions
|
syntax: ILanguageSyntax,
|
||||||
.map((func) => parseFunction(func, syntax, this.codeValidator))
|
utilities = DefaultUtilities,
|
||||||
.reduce((acc, func) => {
|
) => {
|
||||||
acc.addFunction(func);
|
const collection = new SharedFunctionCollection();
|
||||||
return acc;
|
if (!functions.length) {
|
||||||
}, collection);
|
return collection;
|
||||||
}
|
}
|
||||||
|
ensureValidFunctions(functions);
|
||||||
|
return functions
|
||||||
|
.map((func) => parseFunction(func, syntax, utilities))
|
||||||
|
.reduce((acc, func) => {
|
||||||
|
acc.addFunction(func);
|
||||||
|
return acc;
|
||||||
|
}, collection);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DefaultUtilities: SharedFunctionsParsingUtilities = {
|
||||||
|
wrapError: wrapErrorWithAdditionalContext,
|
||||||
|
parseParameter: parseFunctionParameter,
|
||||||
|
codeValidator: CodeValidator.instance,
|
||||||
|
createParameterCollection: createFunctionParameterCollection,
|
||||||
|
parseFunctionCalls,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SharedFunctionsParsingUtilities {
|
||||||
|
readonly wrapError: ErrorWithContextWrapper;
|
||||||
|
readonly parseParameter: FunctionParameterParser;
|
||||||
|
readonly codeValidator: ICodeValidator;
|
||||||
|
readonly createParameterCollection: FunctionParameterCollectionFactory;
|
||||||
|
readonly parseFunctionCalls: FunctionCallsParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunction(
|
function parseFunction(
|
||||||
data: FunctionData,
|
data: FunctionData,
|
||||||
syntax: ILanguageSyntax,
|
syntax: ILanguageSyntax,
|
||||||
validator: ICodeValidator,
|
utilities: SharedFunctionsParsingUtilities,
|
||||||
): ISharedFunction {
|
): ISharedFunction {
|
||||||
const { name } = data;
|
const { name } = data;
|
||||||
const parameters = parseParameters(data);
|
const parameters = parseParameters(data, utilities);
|
||||||
if (hasCode(data)) {
|
if (hasCode(data)) {
|
||||||
validateCode(data, syntax, validator);
|
validateCode(data, syntax, utilities.codeValidator);
|
||||||
return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
|
return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
|
||||||
}
|
}
|
||||||
// Has call
|
// Has call
|
||||||
const calls = parseFunctionCalls(data.call);
|
const calls = utilities.parseFunctionCalls(data.call);
|
||||||
return createCallerFunction(name, parameters, calls);
|
return createCallerFunction(name, parameters, calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,22 +92,35 @@ function validateCode(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseParameters(data: FunctionData): IReadOnlyFunctionParameterCollection {
|
function parseParameters(
|
||||||
|
data: FunctionData,
|
||||||
|
utilities: SharedFunctionsParsingUtilities,
|
||||||
|
): IReadOnlyFunctionParameterCollection {
|
||||||
return (data.parameters || [])
|
return (data.parameters || [])
|
||||||
.map((parameter) => {
|
.map((parameter) => parseParameterWithContextualError(
|
||||||
try {
|
data.name,
|
||||||
return new FunctionParameter(
|
parameter,
|
||||||
parameter.name,
|
utilities,
|
||||||
parameter.optional || false,
|
))
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`"${data.name}": ${err.message}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.reduce((parameters, parameter) => {
|
.reduce((parameters, parameter) => {
|
||||||
parameters.addParameter(parameter);
|
parameters.addParameter(parameter);
|
||||||
return parameters;
|
return parameters;
|
||||||
}, new FunctionParameterCollection());
|
}, utilities.createParameterCollection());
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseParameterWithContextualError(
|
||||||
|
functionName: string,
|
||||||
|
parameterData: ParameterDefinitionData,
|
||||||
|
utilities: SharedFunctionsParsingUtilities,
|
||||||
|
): FunctionParameter {
|
||||||
|
try {
|
||||||
|
return utilities.parseParameter(parameterData);
|
||||||
|
} catch (err) {
|
||||||
|
throw utilities.wrapError(
|
||||||
|
err,
|
||||||
|
`Failed to create parameter: ${parameterData.name} for function "${functionName}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasCode(data: FunctionData): data is CodeFunctionData {
|
function hasCode(data: FunctionData): data is CodeFunctionData {
|
||||||
@@ -98,6 +132,7 @@ function hasCall(data: FunctionData): data is CallFunctionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||||
|
ensureNoUnnamedFunctions(functions);
|
||||||
ensureNoDuplicatesInFunctionNames(functions);
|
ensureNoDuplicatesInFunctionNames(functions);
|
||||||
ensureEitherCallOrCodeIsDefined(functions);
|
ensureEitherCallOrCodeIsDefined(functions);
|
||||||
ensureNoDuplicateCode(functions);
|
ensureNoDuplicateCode(functions);
|
||||||
@@ -108,6 +143,16 @@ function printList(list: readonly string[]): string {
|
|||||||
return `"${list.join('","')}"`;
|
return `"${list.join('","')}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureNoUnnamedFunctions(functions: readonly FunctionData[]) {
|
||||||
|
const functionsWithoutNames = functions.filter(
|
||||||
|
(func) => !func.name || func.name.trim().length === 0,
|
||||||
|
);
|
||||||
|
if (functionsWithoutNames.length) {
|
||||||
|
const invalidFunctions = functionsWithoutNames.map((f) => JSON.stringify(f));
|
||||||
|
throw new Error(`Some function(s) have no names:\n${invalidFunctions.join('\n')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ensureEitherCallOrCodeIsDefined(holders: readonly FunctionData[]) {
|
function ensureEitherCallOrCodeIsDefined(holders: readonly FunctionData[]) {
|
||||||
// Ensure functions do not define both call and code
|
// Ensure functions do not define both call and code
|
||||||
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
|
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user