├── .github
└── workflows
│ ├── greetings.yml
│ ├── javadoc.yml
│ ├── maven.yml
│ └── notify_on_pull_request_open.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.api.md
├── README.cli.md
├── README.desktop.md
├── README.md
├── illustrations
├── html_java_js.png
├── intellij-maven-runner-configuration.png
├── jsgenerator_intro.png
├── sample.html
└── screenshot_current_desktop_version.png
├── jsgenerator-api
└── pom.xml
├── jsgenerator-cli
└── pom.xml
├── jsgenerator-core
├── pom.xml
└── src
│ └── main
│ ├── java
│ ├── com
│ │ └── osscameroon
│ │ │ └── jsgenerator
│ │ │ └── core
│ │ │ ├── BuiltinVariableNameStrategy.java
│ │ │ ├── Configuration.java
│ │ │ ├── Converter.java
│ │ │ ├── OutputStreamResolver.java
│ │ │ ├── VariableDeclaration.java
│ │ │ ├── VariableNameStrategy.java
│ │ │ ├── autoconfigure
│ │ │ └── JsGeneratorCoreAutoconfigure.java
│ │ │ └── internal
│ │ │ ├── ConverterDefault.java
│ │ │ ├── InlineOutputStreamResolver.java
│ │ │ ├── PathOutputStreamResolver.java
│ │ │ ├── RandomVariableNameStrategy.java
│ │ │ ├── StdinOutputStreamResolver.java
│ │ │ └── TypeBasedVariableNameStrategy.java
│ └── module-info.java
│ └── resources
│ └── META-INF
│ └── spring.factories
├── jsgenerator-desktop
├── .gitignore
├── pom.xml
└── src
│ └── main
│ ├── java
│ ├── com
│ │ └── osscameroon
│ │ │ └── jsgenerator
│ │ │ └── desktop
│ │ │ ├── autoconfigure
│ │ │ └── JsGeneratorDesktop.java
│ │ │ └── controller
│ │ │ ├── FxmlNavigator.java
│ │ │ ├── FxmlResolver.java
│ │ │ └── HelloViewController.java
│ └── module-info.java
│ └── resources
│ ├── application.yml
│ ├── banner.txt
│ └── com
│ └── osscameroon
│ └── jsgenerator
│ └── desktop
│ └── controller
│ └── hello-view.fxml
├── jsgenerator-slim-api
├── pom.xml
└── src
│ └── main
│ ├── java
│ ├── com
│ │ └── osscameroon
│ │ │ └── jsgenerator
│ │ │ └── api
│ │ │ ├── JsGeneratorApi.java
│ │ │ ├── domain
│ │ │ ├── InlineOptions.java
│ │ │ ├── MultipartOptions.java
│ │ │ ├── Options.java
│ │ │ └── Output.java
│ │ │ └── rest
│ │ │ ├── ConvertController.java
│ │ │ └── Reply.java
│ └── module-info.java
│ └── resources
│ ├── META-INF
│ └── spring.factories
│ ├── application.yaml
│ └── banner.txt
├── jsgenerator-slim-cli
├── pom.xml
└── src
│ └── main
│ ├── java
│ ├── com
│ │ └── osscameroon
│ │ │ └── jsgenerator
│ │ │ └── cli
│ │ │ ├── Command.java
│ │ │ ├── JsGeneratorCli.java
│ │ │ ├── Valid.java
│ │ │ └── internal
│ │ │ └── CommandDefault.java
│ └── module-info.java
│ └── resources
│ ├── application.yaml
│ └── banner.txt
├── jsgenerator-test
├── jsgenerator-test-api
│ ├── pom.xml
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── module-info.java
│ │ └── test
│ │ ├── java
│ │ ├── com
│ │ │ └── osscameroon
│ │ │ │ └── jsgenerator
│ │ │ │ └── test
│ │ │ │ └── api
│ │ │ │ ├── JsGeneratorApiTest.java
│ │ │ │ └── helper
│ │ │ │ └── MultipartResultMatcher.java
│ │ └── module-info.java
│ │ └── resources
│ │ ├── htmlFilesInput
│ │ ├── sample.html
│ │ └── sampleWithComment.html
│ │ └── jsFilesOutput
│ │ ├── querySelectorAdded
│ │ ├── commentConversionModeActivated
│ │ │ └── sample.js
│ │ ├── commentConversionModeNotActivated
│ │ │ └── sample.js
│ │ └── sample.js
│ │ └── querySelectorNotAdded
│ │ ├── commentConversionModeActivated
│ │ └── sample.js
│ │ ├── commentConversionModeNotActivated
│ │ └── sample.js
│ │ └── sample.js
├── jsgenerator-test-core
│ ├── pom.xml
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── module-info.java
│ │ └── test
│ │ ├── java
│ │ ├── com
│ │ │ └── osscameroon
│ │ │ │ └── jsgenerator
│ │ │ │ └── test
│ │ │ │ └── core
│ │ │ │ └── ConverterTest.java
│ │ └── module-info.java
│ │ └── resources
│ │ └── htmlFilesInput
│ │ ├── sample.html
│ │ └── sampleWithComment.html
├── jsgenerator-test-desktop
│ ├── pom.xml
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── module-info.java
│ │ └── test
│ │ └── java
│ │ ├── com
│ │ └── osscameroon
│ │ │ └── jsgenerator
│ │ │ └── test
│ │ │ └── desktop
│ │ │ └── JsGeneratorDesktopTest.java
│ │ └── module-info.java
└── pom.xml
└── pom.xml
/.github/workflows/greetings.yml:
--------------------------------------------------------------------------------
1 | name: Greetings
2 | # The following commented line to should not be used in order to avoid security issues such as "Resource not accessible by integration", this article give some explanations: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
3 | #on: [pull_request, issues]
4 | on:
5 | issues:
6 | types: [opened]
7 | pull_request_target:
8 | types: [opened]
9 | jobs:
10 | greeting:
11 | runs-on: ubuntu-latest
12 | #permissions:
13 | #pull-requests: write
14 | #issues: write
15 | steps:
16 | - uses: actions/first-interaction@v1
17 | # At this moment, this GitHub Action has an issue: https://github.com/actions/first-interaction/issues/101
18 | # In order to try to solve this, we use this: https://github.com/keploy/keploy/pull/182/files
19 | # continue-on-error: true
20 | # Finally, this issue is solved by this PR https://github.com/actions/first-interaction/pull/103 so no need to use that condition anymore
21 | with:
22 | repo-token: ${{ secrets.GITHUB_TOKEN }}
23 | issue-message: 'Thanks for opening your first issue 😊 ! We really appreciate your work. Happy Coding 🎉🎊 !'
24 | pr-message: 'Thanks for opening your first pull request 😊 ! We really appreciate your work. Happy Coding 🎉🎊 !'
25 |
--------------------------------------------------------------------------------
/.github/workflows/javadoc.yml:
--------------------------------------------------------------------------------
1 | name: Maven Site
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/javadoc.yml'
8 | - 'jsgenerator-core/**'
9 | - 'pom.xml'
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Set up JDK 21
19 | uses: actions/setup-java@v4
20 | with:
21 | distribution: 'temurin'
22 | java-version: '21'
23 | # Given the fact that this is a multimodule project, build process will take long time so we activate caching
24 | # To know more: https://maven.apache.org/extensions/maven-build-cache-extension/cache.html
25 | cache: 'maven'
26 | - name: Build Javadoc Site with Maven
27 | #To see the full stack trace of the errors, re-run Maven with the -e switch.
28 | #Re-run Maven using the -X switch to enable full debug logging.
29 | # -B,--batch-mode Run in non-interactive (batch) mode (disables output color)
30 | # To learn more about options: https://maven.apache.org/ref/3.6.3/maven-embedder/cli.html
31 | run: |
32 | mvn clean site -B -e -X --projects 'jsgenerator-core'
33 | env:
34 | MAVEN_SITE_GITHUB_OAUTH_TOKEN: ${{ secrets.MAVEN_SITE_GITHUB_OAUTH_TOKEN }}
35 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | paths-ignore:
10 | - '**.md'
11 | pull_request:
12 | branches: [ main ]
13 | paths-ignore:
14 | - '**.md'
15 |
16 | permissions:
17 | contents: read
18 |
19 | jobs:
20 | build:
21 |
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - uses: actions/checkout@v4
26 | - name: Set up JDK 21
27 | uses: actions/setup-java@v4
28 | with:
29 | distribution: 'temurin'
30 | java-version: '21'
31 | # Given the fact that this is a multimodule project, build process will take long time so we activate caching
32 | # To know more: https://maven.apache.org/extensions/maven-build-cache-extension/cache.html
33 | cache: 'maven'
34 | - name: Build with Maven
35 | #To see the full stack trace of the errors, re-run Maven with the -e switch.
36 | #Re-run Maven using the -X switch to enable full debug logging.
37 | # -B,--batch-mode Run in non-interactive (batch) mode (disables output color)
38 | # To learn more about options: https://maven.apache.org/ref/3.6.3/maven-embedder/cli.html
39 | run: |
40 | mvn clean test -B -e -X
41 |
--------------------------------------------------------------------------------
/.github/workflows/notify_on_pull_request_open.yml:
--------------------------------------------------------------------------------
1 | name: notify of pull_request creation
2 | on:
3 | pull_request_target:
4 | types: [ opened ]
5 | branches:
6 | - main
7 |
8 | jobs:
9 | notify:
10 | uses: osscameroon/global-github-actions/.github/workflows/notify_on_pull_request_open.yaml@main
11 | secrets:
12 | telegram_channel_id: ${{ secrets.TELEGRAM_OSSCAMEROON_CHANNEL_ID }}
13 | telegram_token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jsgenerator.js
2 | *.build_artifacts.txt
3 | dependency-reduced-pom.xml
4 | /jsgenerator*
5 | !/jsgenerator-core
6 | !/jsgenerator-desktop
7 | !/jsgenerator-test
8 | !/jsgenerator-slim-api
9 | !/jsgenerator-slim-cli
10 | !/jsgenerator-api
11 | !/jsgenerator-cli
12 | !/jsgenerator-web
13 |
14 | # Compiled class file
15 | *.class
16 |
17 | # Log file
18 | *.log
19 |
20 | # BlueJ files
21 | *.ctxt
22 |
23 | # Mobile Tools for Java (J2ME)
24 | .mtj.tmp/
25 |
26 | # Package Files #
27 | *.jar
28 | *.war
29 | *.nar
30 | *.ear
31 | *.zip
32 | *.tar.gz
33 | *.rar
34 |
35 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
36 | hs_err_pid*
37 |
38 | HELP.md
39 | target/
40 | !.mvn/wrapper/maven-wrapper.jar
41 | !**/src/main/**
42 | !**/src/test/**
43 | mvnw
44 | mvnw.cmd
45 | .mvn/
46 |
47 | ### STS / Eclipse ###
48 | .apt_generated
49 | .classpath
50 | .factorypath
51 | .project
52 | .settings
53 | .springBeans
54 | .sts4-cache
55 |
56 | ### IntelliJ IDEA ###
57 | .idea
58 | *.iws
59 | *.iml
60 | *.ipr
61 |
62 | ### NetBeans ###
63 | /nbproject/private/
64 | /nbbuild/
65 | /dist/
66 | /nbdist/
67 | /.nb-gradle/
68 | build/
69 |
70 | ### VS Code ###
71 | .vscode/
72 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | CODE_OF_CONDUCT.md.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | ```shell
4 | # 1. Clone
5 | git clone git@github.com:osscameroon/js-generator.git
6 |
7 | # 2. Move to root directory
8 | cd js-generator
9 |
10 | # 3. Tests & Build
11 | mvn clean package
12 |
13 | # 4. Browse through code
14 | # 5. Run CLI with --help and play with it
15 | # 6. Fork the project, build, test, open a pull request
16 | ```
17 |
18 | Thanks for your commitment, we really appreciate!
19 | Happy Coding! 😊🎉💯
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 OSS Cameroon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.api.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | After starting the `jsgenerator-api` as described in the [README.md](./README.md), you can read:
4 |
5 | + OpenAPI spec. at: [http://localhost:8080/openapi.yaml](http://localhost:8080/openapi.yaml)
6 | + OpenAPI UI at: [http://localhost:8080](http://localhost:8080)
7 |
8 | Two endpoints are exposed:
9 | + `POST /convert`
10 | + `POST /convert/files`
11 |
12 | Both accept options as follow:
13 | ```json
14 | {
15 | "targetElementSelector": ":root > body",
16 | "pattern": "inline-filename-pattern",
17 | "variableNameStrategy": "TYPE_BASED",
18 | "variableDeclaration": "LET",
19 | "extension": ".jsgenerator.js",
20 | "commentConversionModeActivated": true,
21 | "querySelectorAdded": true,
22 | "contents": [
23 | "string"
24 | ]
25 | }
26 | ```
27 | > **NOTE:** The `"contents"` field is mandatory for `POST /convert` and forbidden for `POST /convert/files`
28 |
29 |
30 | ---
31 |
32 | Using [`curl`](https://curl.se/):
33 |
34 | + `POST /convert`
35 | ```shell
36 | # You can also pass as many HTML content as you want
37 | # Response will be of 'application/json' content type
38 | curl -H 'content-type: application/json' -X POST --data '{"contents": ["
js-jsgenerator
"]}' http://localhost:8080/convert
39 |
40 | #Response
41 | {"content":[{"filename":"inline.0.jsgenerator.js","content":"let targetElement_001 = document.querySelector(`:root > body`);\r\n\r\n\r\nlet div_001 = document.createElement('div');\r\nlet text_001 = document.createTextNode(`js-generator`);\r\ndiv_001.appendChild(text_001);\r\ntargetElement_001.appendChild(div_001);\r\n"}],"status":"SUCCESS"}
42 | ```
43 |
44 | + `POST /convert/files`
45 | ```shell
46 | # You can call the API with multiple **files** and at most one **options**
47 | # Response will be of 'multipart/form-data' content type
48 | curl -s -X POST -H 'content-type: multipart/form-data' -F files=@illustrations/sample.html -F "options={ \"querySelectorAdded\": true, \"variableDeclaration\": \"VAR\" }; type=application/json" http://localhost:8080/convert/files
49 |
50 | # -s flag is added in order to prevent curl to mix response and progress meter
51 | #if not added, this will happen: 100 5280 100 4275 100 1005 117k 28194 --:--:-- --:--:-- --:--:-- 147kment.createTextNode(` `);
52 |
53 | #Response
54 |
55 | --d2a-7NlH3rlmcFC3loiJxDxom6iojCunhkzzH
56 | Content-Disposition: form-data; name="inline.0.jsgenerator.js"
57 | Content-Type: application/octet-stream
58 | Content-Length: 4069
59 |
60 | var targetElement_001 = document.querySelector(`:root > body`);
61 |
62 |
63 | var html_001 = document.createElement('html');
64 | var text_001 = document.createTextNode(` `);
65 | html_001.appendChild(text_001);
66 |
67 | var head_001 = document.createElement('head');
68 | var text_002 = document.createTextNode(` `);
69 | head_001.appendChild(text_002);
70 |
71 | var meta_001 = document.createElement('meta');
72 | meta_001.setAttribute(`charset`, `utf-8`);
73 | head_001.appendChild(meta_001);
74 | var text_003 = document.createTextNode(` `);
75 | head_001.appendChild(text_003);
76 |
77 | var title_001 = document.createElement('title');
78 | var text_004 = document.createTextNode(`Sample`);
79 | title_001.appendChild(text_004);
80 | head_001.appendChild(title_001);
81 | var text_005 = document.createTextNode(` `);
82 | head_001.appendChild(text_005);
83 |
84 | var link_001 = document.createElement('link');
85 | link_001.setAttribute(`rel`, `stylesheet`);
86 | link_001.setAttribute(`href`, ``);
87 | head_001.appendChild(link_001);
88 | var text_006 = document.createTextNode(` `);
89 | head_001.appendChild(text_006);
90 | html_001.appendChild(head_001);
91 | var text_007 = document.createTextNode(` `);
92 | html_001.appendChild(text_007);
93 |
94 | var body_001 = document.createElement('body');
95 | var text_008 = document.createTextNode(` `);
96 | body_001.appendChild(text_008);
97 |
98 | var div_001 = document.createElement('div');
99 | div_001.setAttribute(`id`, `container`);
100 | var text_009 = document.createTextNode(` `);
101 | div_001.appendChild(text_009);
102 |
103 | var div_002 = document.createElement('div');
104 | div_002.setAttribute(`id`, `header`);
105 | var text_010 = document.createTextNode(` `);
106 | div_002.appendChild(text_010);
107 |
108 | var h1_001 = document.createElement('h1');
109 | var text_011 = document.createTextNode(`Sample`);
110 | h1_001.appendChild(text_011);
111 | div_002.appendChild(h1_001);
112 | var text_012 = document.createTextNode(` `);
113 | div_002.appendChild(text_012);
114 |
115 | var img_001 = document.createElement('img');
116 | img_001.setAttribute(`src`, `kanye.jpg`);
117 | img_001.setAttribute(`alt`, `kanye`);
118 | div_002.appendChild(img_001);
119 | var text_013 = document.createTextNode(` `);
120 | div_002.appendChild(text_013);
121 | div_001.appendChild(div_002);
122 | var text_014 = document.createTextNode(` `);
123 | div_001.appendChild(text_014);
124 |
125 | var div_003 = document.createElement('div');
126 | div_003.setAttribute(`id`, `main`);
127 | var text_015 = document.createTextNode(` `);
128 | div_003.appendChild(text_015);
129 |
130 | var h2_001 = document.createElement('h2');
131 | var text_016 = document.createTextNode(`Main`);
132 | h2_001.appendChild(text_016);
133 | div_003.appendChild(h2_001);
134 | var text_017 = document.createTextNode(` `);
135 | div_003.appendChild(text_017);
136 |
137 | var p_001 = document.createElement('p');
138 | var text_018 = document.createTextNode(`This is the main content.`);
139 | p_001.appendChild(text_018);
140 | div_003.appendChild(p_001);
141 | var text_019 = document.createTextNode(` `);
142 | div_003.appendChild(text_019);
143 |
144 | var img_002 = document.createElement('img');
145 | img_002.setAttribute(`src`, ``);
146 | img_002.setAttribute(`alt`, ``);
147 | div_003.appendChild(img_002);
148 | var text_020 = document.createTextNode(` `);
149 | div_003.appendChild(text_020);
150 | div_001.appendChild(div_003);
151 | var text_021 = document.createTextNode(` `);
152 | div_001.appendChild(text_021);
153 |
154 | var div_004 = document.createElement('div');
155 | div_004.setAttribute(`id`, `footer`);
156 | var text_022 = document.createTextNode(` `);
157 | div_004.appendChild(text_022);
158 |
159 | var p_002 = document.createElement('p');
160 | var text_023 = document.createTextNode(`Copyright - 2019`);
161 | p_002.appendChild(text_023);
162 | div_004.appendChild(p_002);
163 | var text_024 = document.createTextNode(` `);
164 | div_004.appendChild(text_024);
165 | div_001.appendChild(div_004);
166 | var text_025 = document.createTextNode(` `);
167 | div_001.appendChild(text_025);
168 | body_001.appendChild(div_001);
169 | var text_026 = document.createTextNode(` `);
170 | body_001.appendChild(text_026);
171 | html_001.appendChild(body_001);
172 | targetElement_001.appendChild(html_001);
173 |
174 | --d2a-7NlH3rlmcFC3loiJxDxom6iojCunhkzzH--
175 | ```
176 |
--------------------------------------------------------------------------------
/README.cli.md:
--------------------------------------------------------------------------------
1 | # Command Line Interface
2 |
3 | ```shell
4 | let targetElement_001 = document.querySelector(`:root > body`);
5 |
6 |
7 | let div_001 = document.createElement('div');
8 | let text_001 = document.createTextNode(`I am a `);
9 | div_001.appendChild(text_001);
10 |
11 | let strong_001 = document.createElement('strong');
12 | let text_002 = document.createTextNode(`tea pot`);
13 | strong_001.appendChild(text_002);
14 | div_001.appendChild(strong_001);
15 | targetElement_001.appendChild(div_001);
16 | ```
17 |
18 | ---
19 |
20 | `jsgenerator` has several options that can be used in a console here is an example of use below
21 |
22 | ```text
23 | Usage: jsgenerator [-chtV] [-qs] [-e=]
24 | [--inline-pattern=]
25 | [-k=] [--path-pattern=]
26 | [-s=]
27 | [--stdin-pattern=]
28 | [--variable-name-generation-strategy=] [-i=...]... [...]
30 | Translating files, stdin or inline from HTML to JS
31 | [...] file paths to translate content, parsed as HTML
32 | -c, --comment optional comments
33 | -e, --ext= output files' extension
34 | -h, --help Show this help message and exit.
35 | -i, --inline=...
36 | args as HTML content, not files
37 | --inline-pattern=
38 | Pattern for inline output filename
39 | -k, --keyword=
40 | variable declaration keyword
41 | --path-pattern=
42 | pattern for path-based output filenames
43 | -qs, --query-selector
44 | What the browser renders depends on whether "document.
45 | querySelector(':root > body')" is added to the
46 | output. If added, the browser will render the
47 | output successfully, it is useful for debugging
48 | purpose,
49 | to verify that the js output matches what the
50 | html input does.
51 | If not, if the user tries to run the output as
52 | it is then the browser will not be able to render,
53 | it will show a blank page.
54 | So, it depends on what the user wants to do with
55 | the output.
56 | "https://jsfiddle.net/", "https://codepen.
57 | io/pen/" and Browser Console help to give a quick
58 | feedback.
59 |
60 | -s, --selector=
61 | Target element selector
62 | --stdin-pattern=
63 | pattern for stdin output filenames
64 | -t, --tty output to stdin, not files
65 | -V, --version Print version information and exit.
66 | --variable-name-generation-strategy=
67 | Variable names generation strategy
68 | ```
69 |
--------------------------------------------------------------------------------
/README.desktop.md:
--------------------------------------------------------------------------------
1 | > Screenshot of the current desktop version:
2 | >
3 | > 
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://github.com/osscameroon/js-generator/graphs/contributors)
5 | 
6 |
7 | # Status: UNDER DEVELOPMENT
8 |
9 | # Table of Contents
10 | - [About](#about)
11 | - [Getting Started](#getting-started)
12 | - [Requirements](#requirements)
13 | - [Modules](#modules)
14 | - [Architecture](#architecture)
15 | - [Compiling](#compiling)
16 | - [Running](#running)
17 | - [Packaging](#packaging)
18 | - [Contribute](#contribute)
19 |
20 | 
21 |
22 | # About
23 |
24 | Translating from HTML to JS
25 |
26 | > This project is different from the
27 | > [JavaScript Generator Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator).
28 |
29 | The goal is to generate JS from HTML following the [Document Object Model](https://www.w3schools.com/js/js_htmldom.asp) structure. Sometimes, we forget how to write
30 | JavaScript to build dynamic web apps. Even if we know JS, it happens that we don't always have enough time to generate
31 | JS from a big HTML code. Thus, the goal of this project is helping developers gaining time by producing JS code as
32 | Output based on HTML as Input. This project will be very useful for beginners learning HTML and JavaScript. Also, it
33 | will help more experienced developers whenever they want to use JS instead of HTML, very useful in applications where we need to add dynamism.
34 |
35 | [Sherlock Wisdom](https://github.com/sherlockwisdom) shared why he needed such tool:
36 |
37 | > 😂 Yes it's hard to say why it's important. I was working on an Android based app, but was building it with Vanilla JavaScript. So I needed a quick way to turn bootstrap code into Vanilla Js objects so that I could do what ReactJS does now 🤣. This was ~4 years back. Not sure of its relevance now, but they could be some. 😅 Sorry if I rather made things not easy for you to explain.
38 |
39 | We would like to give credit to [jsoup](https://jsoup.org/) / [jsoup GitHub Repository](https://github.com/jhy/jsoup/) as the main library to help us handle HTML tokenization and traversing.
40 |
41 | 
42 |
43 | # Getting Started
44 |
45 | ## Requirements
46 |
47 | + JDK 21
48 | + Maven 4
49 | > Because of its unique features over maven 3:
50 | > namely, multi module dependency resolution under common parent, when running a maven goal only on some child
51 | + Spring Boot 3.3.1
52 | > Leverage convention over configuration and autoconfiguration discovery to enforce consistent a behaviour
53 | > throughout our frontends
54 |
55 | ## Modules
56 |
57 | The project takes advantage of Maven multimodule capabilities to enforce a consistent versioning and releases and,
58 | the specific Maven 4 features to deliver a seamless developer experience.
59 |
60 | ```text
61 | js-generator:
62 | |- jsgenerator-api
63 | |- jsgenerator-web
64 | |- jsgenerator-cli
65 | \- jsgenerator-desktop
66 | ```
67 |
68 | ## Architecture
69 |
70 | | THE MODULE | ITS CONTENT && DEPENDENCIES | PACKAGING |
71 | |------------------------------------|-------------------------------------|-------|
72 | | js-generator | Bill of Material, global properties | POM |
73 | | jsgenerator-core | Core API, Spring Boot auto-conf | JAR |
74 | | jsgenerator-slim-api | jsgenerator-core, spring-web | JAR |
75 | | jsgenerator-slim-cli | jsgenerator-core, picocli | JAR |
76 | | [jsgenerator-api](./README.api.md) | jsgenerator-slim-api | FAT JAR |
77 | | [jsgenerator-cli](./README.cli.md) | jsgenerator-slim-cli | FAT JAR |
78 | | [jsgenerator-desktop](./README.desktop.md) | jsgenerator-core, javafx-fxml | JAR |
79 |
80 | > **NOTE:** FAT JAR packaged modules are mere wrappers around slim modules. The separation is important because then,
81 | > the test modules can use slim JARs as dependencies, unlike FAT JARs. This has to do with how "normal" vs. FAT JARs
82 | > are laid out.
83 |
84 | ## Compiling
85 |
86 | ```shell
87 | # Clone the git repository
88 | git clone git@github.com:osscameroon/js-generator.git
89 |
90 | # Move at the project root
91 | cd js-generator
92 |
93 | # Compile & test all the modules
94 | mvn clean test
95 | ```
96 |
97 | ## Running
98 |
99 | > Compiling the whole project before running child modules is advised.
100 | >
101 | > To set up you IDE runner, follow this IntelliJ example:
102 | >
103 | > 
104 |
105 | API Server : [jsgenerator-api](./README.api.md)
106 | ```shell
107 | # After starting the server, visit http://localhost:8080
108 | mvn --also-make --projects jsgenerator-api clean spring-boot:run
109 | ```
110 |
111 | Command Line Interface (CLI) : [jsgenerator-cli](./README.cli.md)
112 | ```shell
113 | # After reading the help, play out with different CLI options
114 | mvn --also-make --projects jsgenerator-cli clean spring-boot:run -Dspring-boot.run.arguments=--help
115 |
116 | # For example:
117 | mvn --also-make --projects jsgenerator-cli clean spring-boot:run -Dspring-boot.run.arguments="--tty --inline 'I am a tea pot
'"
118 |
119 | # It's also possible to create the jar first
120 | mvn clean package
121 |
122 | # then run the following commands and replace {version} by the current one (0.0.1-SNAPSHOT at this time)
123 | java -jar jsgenerator-cli/target/jsgenerator-cli-{version}.jar # java -jar jsgenerator-cli/target/jsgenerator-cli-0.0.1-SNAPSHOT.jar --help
124 | java -jar jsgenerator-cli/target/jsgenerator-cli-{version}.jar --tty --inline 'I am a tea pot
'
125 | ```
126 |
127 | Desktop : [jsgenerator-desktop](./README.desktop.md)
128 | ```shell
129 | # Create the jar first
130 | mvn clean package
131 |
132 | # then run this command and replace {version} by the current one (0.0.1-SNAPSHOT at this time)
133 | java -jar jsgenerator-desktop/target/jsgenerator-desktop-{version}.jar # java -jar jsgenerator-desktop/target/jsgenerator-desktop-0.0.1-SNAPSHOT.jar
134 | ```
135 |
136 |
137 | ## Packaging
138 |
139 | ```shell
140 | # Will compile all the modules into JAR (or FAT JAR - see the table above)
141 | mvn clean package
142 | ```
143 |
144 | # Contribute
145 |
146 | All your contributions are welcome!
147 |
148 | Do not hesitate to open an issue on this repository and/or create a pull request (PR).
149 |
150 | In order to create a PR, just fork first.
151 |
152 | **[We started from the bottom 7 years ago](https://github.com/opensourcecameroon/jsGenerator), now we are here, we believe we will continue moving forward together 😊.**
153 |
154 | Thanks for your commitment, we really appreciate!
155 | Happy Coding! 😊🎉💯
156 |
157 |
158 |
159 |
160 |
161 |
162 | [Back To The Top](#table-of-contents)
163 |
--------------------------------------------------------------------------------
/illustrations/html_java_js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/html_java_js.png
--------------------------------------------------------------------------------
/illustrations/intellij-maven-runner-configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/intellij-maven-runner-configuration.png
--------------------------------------------------------------------------------
/illustrations/jsgenerator_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/jsgenerator_intro.png
--------------------------------------------------------------------------------
/illustrations/sample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Sample
7 |
8 |
9 |
10 |
11 |
15 |
16 |
Main
17 |
This is the main content.
18 |
19 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/illustrations/screenshot_current_desktop_version.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/osscameroon/js-generator/504c844491dcaf9662a3eef4bbec7244310fad32/illustrations/screenshot_current_desktop_version.png
--------------------------------------------------------------------------------
/jsgenerator-api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-api
13 |
14 |
15 | com.osscameroon.jsgenerator.api.JsGeneratorApi
16 |
17 |
18 |
19 |
20 |
21 | org.graalvm.buildtools
22 | native-maven-plugin
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-maven-plugin
27 |
28 | false
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | com.osscameroon
37 | jsgenerator-slim-api
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/jsgenerator-cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-cli
13 |
14 |
15 | com.osscameroon.jsgenerator.cli.JsGeneratorCli
16 |
17 |
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-maven-plugin
23 |
24 | false
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | com.osscameroon
33 | jsgenerator-slim-cli
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/jsgenerator-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator
7 | com.osscameroon
8 | ${revision}
9 |
10 | jar
11 | 4.0.0
12 |
13 | jsgenerator-core
14 |
15 |
16 |
17 | org.springframework.boot
18 | spring-boot-starter
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/BuiltinVariableNameStrategy.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core;
2 |
3 | import java.util.function.Supplier;
4 |
5 | public enum BuiltinVariableNameStrategy implements Supplier {
6 | TYPE_BASED(VariableNameStrategy::ofTypeBased),
7 | RANDOM(VariableNameStrategy::ofRandom),
8 | ;
9 |
10 | private final Supplier variableNameStrategySupplier;
11 |
12 | BuiltinVariableNameStrategy(final Supplier variableNameStrategySupplier) {
13 | this.variableNameStrategySupplier = variableNameStrategySupplier;
14 | }
15 |
16 | @Override
17 | public VariableNameStrategy get() {
18 | return variableNameStrategySupplier.get();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/Configuration.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core;
2 |
3 | import static com.osscameroon.jsgenerator.core.VariableDeclaration.LET;
4 | import static com.osscameroon.jsgenerator.core.VariableNameStrategy.ofTypeBased;
5 |
6 | public class Configuration {
7 | private static final String ROOT_BODY = ":root > body";
8 |
9 | private String targetElementSelector = ROOT_BODY;
10 | /**
11 | * What the browser renders depends on whether we add document.querySelector(':root > body') to the output.
12 | * If added, the browser will render the output successfully, it is useful for debugging purpose,
13 | * to verify that the js output matches what the html input does.
14 | * If not, if the user tries to run the output as it is then the browser will not be able to render,it will show a blank page.
15 | * So, it depends on what the user wants to do with the output.
16 | *
17 | * @see JSFiddle , CodePen and Browser Console help to give a quick feedback.
18 | */
19 |
20 | private boolean querySelectorAdded = true;
21 | private boolean commentConversionModeActivated = true;
22 | private VariableDeclaration variableDeclaration = LET;
23 | private VariableNameStrategy variableNameStrategy = ofTypeBased();
24 |
25 | public Configuration() {
26 | }
27 |
28 | public Configuration(VariableDeclaration variableDeclaration,
29 | boolean querySelectorAdded,
30 | boolean commentConversionModeActivated) {
31 | this.querySelectorAdded = querySelectorAdded;
32 | this.variableDeclaration = variableDeclaration;
33 | this.commentConversionModeActivated = commentConversionModeActivated;
34 | }
35 |
36 | public Configuration(VariableDeclaration variableDeclaration,
37 | boolean querySelectorAdded) {
38 | this.querySelectorAdded = querySelectorAdded;
39 | this.variableDeclaration = variableDeclaration;
40 | }
41 |
42 | public Configuration(VariableDeclaration variableDeclaration,
43 | VariableNameStrategy variableNameStrategy,
44 | boolean querySelectorAdded,
45 | boolean commentConversionModeActivated) {
46 | this.querySelectorAdded = querySelectorAdded;
47 | this.variableDeclaration = variableDeclaration;
48 | this.variableNameStrategy = variableNameStrategy;
49 | this.commentConversionModeActivated = commentConversionModeActivated;
50 | }
51 |
52 | public Configuration(String targetElementSelector,
53 | boolean querySelectorAdded,
54 | boolean commentConversionModeActivated,
55 | VariableDeclaration variableDeclaration,
56 | VariableNameStrategy variableNameStrategy) {
57 | this.querySelectorAdded = querySelectorAdded;
58 | this.variableDeclaration = variableDeclaration;
59 | this.variableNameStrategy = variableNameStrategy;
60 | this.targetElementSelector = targetElementSelector;
61 | this.commentConversionModeActivated = commentConversionModeActivated;
62 | }
63 |
64 | public String getTargetElementSelector() {
65 | return targetElementSelector;
66 | }
67 |
68 | public boolean isQuerySelectorAdded() {
69 | return querySelectorAdded;
70 | }
71 |
72 | public boolean isCommentConversionModeActivated() {
73 | return commentConversionModeActivated;
74 | }
75 |
76 | public VariableDeclaration getVariableDeclaration() {
77 | return variableDeclaration;
78 | }
79 |
80 | public VariableNameStrategy getVariableNameStrategy() {
81 | return variableNameStrategy;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/Converter.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core;
2 |
3 | import com.osscameroon.jsgenerator.core.internal.ConverterDefault;
4 | import org.springframework.lang.NonNull;
5 |
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.OutputStream;
9 |
10 | @FunctionalInterface
11 | public interface Converter {
12 | default void convert(@NonNull final InputStream inputStream, @NonNull final OutputStream outputStream) throws IOException {
13 | convert(inputStream, outputStream, new Configuration());
14 | }
15 |
16 | void convert(@NonNull final InputStream inputStream, @NonNull final OutputStream outputStream, @NonNull Configuration configuration) throws IOException;
17 |
18 | static Converter of() {
19 | return new ConverterDefault();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/OutputStreamResolver.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core;
2 |
3 | import com.osscameroon.jsgenerator.core.internal.InlineOutputStreamResolver;
4 | import com.osscameroon.jsgenerator.core.internal.PathOutputStreamResolver;
5 | import com.osscameroon.jsgenerator.core.internal.StdinOutputStreamResolver;
6 | import org.springframework.lang.NonNull;
7 |
8 | import java.util.Map;
9 |
10 | @FunctionalInterface
11 | public interface OutputStreamResolver {
12 | String ORIGINAL_DIRECTORY = "original-directory";
13 | String ORIGINAL_EXTENSION = "original-extension";
14 | String ORIGINAL_BASENAME = "original-basename";
15 | String EXTENSION = "extension";
16 | String ORIGINAL = "original";
17 | String INDEX = "index";
18 |
19 | String resolve(@NonNull final String template, @NonNull final Map container);
20 |
21 | static OutputStreamResolver ofInline() {
22 | return new InlineOutputStreamResolver();
23 | }
24 |
25 | static OutputStreamResolver ofStdin() {
26 | return new StdinOutputStreamResolver();
27 | }
28 |
29 | static OutputStreamResolver ofPath() {
30 | return new PathOutputStreamResolver();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/VariableDeclaration.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core;
2 |
3 | public enum VariableDeclaration {
4 |
5 | LET,VAR,CONST
6 | }
7 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/VariableNameStrategy.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core;
2 |
3 | import com.osscameroon.jsgenerator.core.internal.RandomVariableNameStrategy;
4 | import com.osscameroon.jsgenerator.core.internal.TypeBasedVariableNameStrategy;
5 | import org.springframework.lang.NonNull;
6 |
7 | @FunctionalInterface
8 | public interface VariableNameStrategy {
9 | String nextName(@NonNull String type);
10 |
11 | static VariableNameStrategy ofRandom() {
12 | return new RandomVariableNameStrategy();
13 | }
14 |
15 | static VariableNameStrategy ofTypeBased() {
16 | return new TypeBasedVariableNameStrategy();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/autoconfigure/JsGeneratorCoreAutoconfigure.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.autoconfigure;
2 |
3 | import com.osscameroon.jsgenerator.core.Converter;
4 | import com.osscameroon.jsgenerator.core.OutputStreamResolver;
5 | import org.springframework.boot.SpringBootConfiguration;
6 | import org.springframework.context.annotation.Bean;
7 |
8 | @SpringBootConfiguration
9 | public class JsGeneratorCoreAutoconfigure {
10 | @Bean
11 | public OutputStreamResolver pathOutputStreamResolver() {
12 | return OutputStreamResolver.ofPath();
13 | }
14 |
15 | @Bean
16 | public OutputStreamResolver stdinOutputStreamResolver() {
17 | return OutputStreamResolver.ofStdin();
18 | }
19 |
20 | @Bean
21 | public OutputStreamResolver inlineOutputStreamResolver() {
22 | return OutputStreamResolver.ofInline();
23 | }
24 |
25 | @Bean
26 | public Converter converter() {
27 | return Converter.of();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/ConverterDefault.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.internal;
2 |
3 | import com.osscameroon.jsgenerator.core.Configuration;
4 | import com.osscameroon.jsgenerator.core.Converter;
5 | import com.osscameroon.jsgenerator.core.VariableDeclaration;
6 | import org.jsoup.Jsoup;
7 | import org.jsoup.nodes.Attribute;
8 | import org.jsoup.nodes.Attributes;
9 | import org.jsoup.nodes.Comment;
10 | import org.jsoup.nodes.Element;
11 | import org.jsoup.nodes.Node;
12 | import org.jsoup.nodes.TextNode;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.OutputStream;
17 | import java.io.OutputStreamWriter;
18 | import java.io.Writer;
19 | import java.util.HashMap;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.Scanner;
23 |
24 | import static java.lang.String.format;
25 | import static java.lang.String.join;
26 | import static org.jsoup.parser.Parser.xmlParser;
27 |
28 | public class ConverterDefault implements Converter {
29 | private static final List BOOLEAN_ATTRIBUTES = List.of("allowfullscreen", "async", "autofocus",
30 | "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "ismap", "itemscope",
31 | "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required",
32 | "reversed", "selected", "truespeed", "contenteditable");
33 |
34 | private static Element resolveClosestNonSelfClosingAncestor(Node element) {
35 | // NOTE: Fix issue #41 by looking up closest non-self-closing parent/ancestor to append current element to
36 | var ancestor = (Element) element.parent();
37 |
38 | //noinspection ConstantConditions
39 | while (ancestor.tag().isSelfClosing()) {
40 | ancestor = ancestor.parent();
41 | }
42 |
43 | return ancestor;
44 | }
45 |
46 | /*
47 | * TODO: There is some issue related to encoding, should we not set utf8 encoding here instead of setting it as we do inside ConverterTest ?
48 | * Make sure that every input has utf8 encoding. If not, we set this encoding.
49 | *
50 | * java define encoding of InputStream
51 | * https://stackoverflow.com/questions/3043710/java-inputstream-encoding-charset
52 | *
53 | * */
54 | @Override
55 | public void convert(InputStream inputStream, OutputStream outputStream, Configuration configuration) throws IOException {
56 | final var stringBuilder = new StringBuilder();
57 | final var scanner = new Scanner(inputStream);
58 |
59 | while (scanner.hasNext()) {
60 | stringBuilder.append(scanner.nextLine());
61 | }
62 |
63 | final var content = stringBuilder.toString();
64 |
65 | // NOTE: There is nothing to do
66 | if (content.isBlank()) return;
67 |
68 | final var variableNameStrategy = configuration.getVariableNameStrategy();
69 | final var document = Jsoup.parse(content, xmlParser());
70 | final var writer = new OutputStreamWriter(outputStream);
71 |
72 | final var selector = configuration.getTargetElementSelector();
73 |
74 |
75 | final var variable = configuration.isQuerySelectorAdded()
76 | ? variableNameStrategy.nextName("targetElement") : null;
77 |
78 |
79 | // NOTE: We need a variable to keep track of ancestors name.
80 | // Following issue#41, elements that follow self-closing that should be added
81 | // to their real parent and not the JSoup-inferred parent (which happen to just
82 | // be their previous self-closing sibling)
83 |
84 | Map variables = new HashMap();
85 |
86 | /*
87 | * if configuration.isQuerySelectorAdded() is true then variable = variableNameStrategy.nextName("targetElement")
88 | * if configuration.isQuerySelectorAdded() is false then variable = null
89 | *
90 | * In case, variable is null, we'll get a NullPointerException from Map.of(document, variable).
91 | *
92 | * Getting a NullPointerException here is normal because the query selector is not added.
93 | * In order to handle this situation,
94 | * we do nothing inside this catch but based on this null value,
95 | * we'll change the flow of this program inside the visiting methods.
96 | * */
97 | try {
98 | variables = new HashMap(Map.of(document, variable));
99 | } catch (NullPointerException e) {
100 |
101 | /*
102 | * We do nothing in case the query selector is not added
103 | * */
104 |
105 | }
106 |
107 | final var keyword = resolveDeclarationKeyWord(configuration.getVariableDeclaration());
108 |
109 | final var lineSeparator = System.lineSeparator();
110 |
111 | if (configuration.isQuerySelectorAdded()) {
112 | writer.write("%s %s = document.querySelector(`%s`);%s%s".formatted(keyword, variable, selector,lineSeparator,lineSeparator));
113 | }
114 |
115 | visit(writer, document.childNodes(), configuration, variables);
116 | writer.flush();
117 | }
118 |
119 | private void visit(Writer writer, List nodes, Configuration configuration, Map variables) throws IOException {
120 | for (final Node node : nodes) {
121 | if (node instanceof Element) visit(writer, (Element) node, configuration, variables);
122 | else if (node instanceof Comment) {
123 | if (configuration.isCommentConversionModeActivated()) {
124 | visit(writer, (Comment) node, configuration, variables);
125 | }
126 | } else if (node instanceof TextNode) visit(writer, (TextNode) node, configuration, variables);
127 | }
128 | }
129 |
130 | private void visit(Writer writer, String parent, Attributes attributes) throws IOException {
131 | for (final Attribute attribute : attributes) {
132 | // FIXME: Should we special handle aria-* and data-* attributes ?
133 | // NOTE: Account for boolean attributes like open, required, disabled, contenteditable...
134 | final var value = attribute.hasDeclaredValue() ? attribute.getValue()
135 | : BOOLEAN_ATTRIBUTES.contains(attribute.getKey()) ? "true"
136 | : "";
137 | writer.write(format("%s.setAttribute(`%s`, `%s`);\r\n", parent, attribute.getKey(), value));
138 | }
139 | }
140 |
141 | private void visit(Writer writer, Comment comment, Configuration configuration, Map variables) throws IOException {
142 | final var variableNameStrategy = configuration.getVariableNameStrategy();
143 | final var variable = variableNameStrategy.nextName("comment");
144 | final var ancestor = resolveClosestNonSelfClosingAncestor(comment);
145 | String declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration());
146 |
147 | writer.write(format("\r\n%s %s = document.createComment(`%s`);\r\n", declarationKeyWord, variable, comment.getData()));
148 |
149 |
150 | /*
151 | * Based on this ternary operation on convert method,
152 | *
153 | * final var variable = configuration.isQuerySelectorAdded()
154 | ? variableNameStrategy.nextName("targetElement"):null;
155 |
156 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null
157 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null
158 | *
159 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition
160 | * */
161 |
162 | if (variables.get(ancestor) != null) {
163 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable));
164 | }
165 | }
166 |
167 | private void visit(Writer writer, TextNode textNode, Configuration configuration, Map variables) throws IOException {
168 | final var variableNameStrategy = configuration.getVariableNameStrategy();
169 | final var variable = variableNameStrategy.nextName("text");
170 | final var ancestor = resolveClosestNonSelfClosingAncestor(textNode);
171 | String declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration());
172 |
173 | writer.write(format("%s %s = document.createTextNode(`%s`);\r\n", declarationKeyWord, variable, textNode.getWholeText()));
174 |
175 | /*
176 | * Based on this ternary operation on convert method,
177 | *
178 | * final var variable = configuration.isQuerySelectorAdded()
179 | ? variableNameStrategy.nextName("targetElement"):null;
180 |
181 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null
182 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null
183 | *
184 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition
185 | * */
186 |
187 | if (variables.get(ancestor) != null) {
188 |
189 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable));
190 |
191 | }
192 |
193 | }
194 |
195 | private void visit(Writer writer, Element element, Configuration configuration, Map variables) throws IOException {
196 | final var declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration());
197 | final var variableNameStrategy = configuration.getVariableNameStrategy();
198 | final var variable = variableNameStrategy.nextName(element.tagName());
199 |
200 | variables.put(element, variable);
201 | writer.write(format("\r\n%s %s = document.createElement('%s');\r\n", declarationKeyWord, variable, element.tagName()));
202 | visit(writer, variable, element.attributes());
203 |
204 | if ("script".equalsIgnoreCase(element.tagName())) {
205 | visitScriptNode(writer, element, variable, configuration, variables);
206 | } else {
207 | final var ancestor = resolveClosestNonSelfClosingAncestor(element);
208 |
209 | if (element.tag().isSelfClosing()) {
210 | // NOTE: JSoup wrongly considers the current element being visited as capable of having children.
211 | // This condition ensures that we append the self-closing element to its parent,
212 | // before processing its siblings, which JSoup parses as its children.
213 |
214 | /*
215 | * Based on this ternary operation on convert method,
216 | *
217 | * final var variable = configuration.isQuerySelectorAdded()
218 | ? variableNameStrategy.nextName("targetElement"):null;
219 |
220 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null
221 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null
222 | *
223 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition
224 | * */
225 |
226 |
227 | if (variables.get(ancestor) != null) {
228 |
229 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable));
230 |
231 | }
232 |
233 | visit(writer, element.childNodes(), configuration, variables);
234 | } else {
235 | visit(writer, element.childNodes(), configuration, variables);
236 |
237 |
238 | /*
239 | * Based on this ternary operation on convert method,
240 | *
241 | * final var variable = configuration.isQuerySelectorAdded()
242 | ? variableNameStrategy.nextName("targetElement"):null;
243 |
244 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null
245 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null
246 | *
247 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition
248 | * */
249 |
250 | if (variables.get(ancestor) != null) {
251 |
252 | writer.write(format("%s.appendChild(%s);\r\n", variables.get(ancestor), variable));
253 |
254 | }
255 | }
256 | }
257 |
258 | }
259 |
260 | private void visitScriptNode(Writer writer, Element element, String variable,
261 | Configuration configuration, Map variables) throws IOException {
262 | final var variableNameStrategy = configuration.getVariableNameStrategy();
263 | final var ancestor = resolveClosestNonSelfClosingAncestor(element);
264 |
265 | if (element.attr(element.absUrl("type")).isBlank()) {
266 | writer.write(format("\r\n%s.type = `text/javascript`;\r\n", variable));
267 | }
268 |
269 | String declarationKeyWord = resolveDeclarationKeyWord(configuration.getVariableDeclaration());
270 | final var script = ((TextNode) element.childNodes().get(0)).getWholeText()
271 | .replaceAll("`", "\\\\`");
272 | // FIXME: Will be quirky without tokenizing script code but then, it doesn't matter as it could be
273 | // TypeScript or Mustache template or, Pig, etc. We may consider tokenizing those languages
274 | final var scriptTextVariable = variableNameStrategy.nextName("text");
275 |
276 | /*
277 | * Based on this ternary operation on convert method,
278 | *
279 | * final var variable = configuration.isQuerySelectorAdded()
280 | ? variableNameStrategy.nextName("targetElement"):null;
281 |
282 | * if configuration.isQuerySelectorAdded() is true then variables.get(ancestor) is not null
283 | * if configuration.isQuerySelectorAdded() is false then variables.get(ancestor) is null
284 | *
285 | * In order to not appendChild to a null element (configuration.isQuerySelectorAdded() is false), we use this condition
286 | * */
287 |
288 |
289 | if (variables.get(ancestor) != null) {
290 |
291 |
292 | writer.write(format("\r\n" + join("\r\n", "try {",
293 | " %6$s %3$s = document.createTextNode(`%1$s`);",
294 | " %2$s.appendChild(%3$s);",
295 | " %4$s.appendChild(%2$s);",
296 | "} catch (_) {",
297 | " %2$s.text = `%1$s`;",
298 | " %4$s.appendChild(%2$s);",
299 | "}") + "\r\n",
300 | script, variable, scriptTextVariable, variables.get(ancestor), variable, declarationKeyWord));
301 |
302 |
303 | } else {
304 |
305 | writer.write(format("\r\n" + join("\r\n", "try {",
306 | " %4$s %3$s = document.createTextNode(`%1$s`);",
307 | " %2$s.appendChild(%3$s);",
308 | "} catch (_) {",
309 | " %2$s.text = `%1$s`;",
310 | "}") + "\r\n",
311 | script, variable, scriptTextVariable, declarationKeyWord));
312 |
313 | }
314 | }
315 |
316 | private String resolveDeclarationKeyWord(VariableDeclaration variableDeclaration) {
317 | return variableDeclaration.name().toLowerCase();
318 | }
319 | }
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/InlineOutputStreamResolver.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.internal;
2 |
3 | import com.osscameroon.jsgenerator.core.OutputStreamResolver;
4 | import org.springframework.lang.NonNull;
5 |
6 | import java.util.Map;
7 |
8 | import static java.lang.String.format;
9 | import static java.lang.String.valueOf;
10 |
11 | public class InlineOutputStreamResolver implements OutputStreamResolver {
12 | @Override
13 | public String resolve(@NonNull String template, @NonNull Map container) {
14 | return template
15 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", EXTENSION), valueOf(container.get(EXTENSION)))
16 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", INDEX), valueOf(container.get(INDEX)));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/PathOutputStreamResolver.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.internal;
2 |
3 | import com.osscameroon.jsgenerator.core.OutputStreamResolver;
4 | import org.springframework.lang.NonNull;
5 |
6 | import java.util.Map;
7 |
8 | import static java.lang.String.format;
9 | import static java.lang.String.valueOf;
10 |
11 | public class PathOutputStreamResolver implements OutputStreamResolver {
12 | @Override
13 | public String resolve(@NonNull String template, @NonNull Map container) {
14 | return template
15 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL_DIRECTORY), valueOf(container.get(ORIGINAL_DIRECTORY)))
16 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL_EXTENSION), valueOf(container.get(ORIGINAL_EXTENSION)))
17 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL_BASENAME), valueOf(container.get(ORIGINAL_BASENAME)))
18 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", EXTENSION), valueOf(container.get(EXTENSION)))
19 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", ORIGINAL), valueOf(container.get(ORIGINAL)))
20 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", INDEX), valueOf(container.get(INDEX)));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/RandomVariableNameStrategy.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.internal;
2 |
3 | import com.osscameroon.jsgenerator.core.VariableNameStrategy;
4 | import org.springframework.lang.NonNull;
5 |
6 | import static java.util.UUID.randomUUID;
7 |
8 | public class RandomVariableNameStrategy implements VariableNameStrategy {
9 | @Override
10 | public String nextName(@NonNull String type) {
11 | return "_" + randomUUID().toString().replaceAll("[^a-zA-Z0-9]", "_");
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/StdinOutputStreamResolver.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.internal;
2 |
3 | import com.osscameroon.jsgenerator.core.OutputStreamResolver;
4 | import org.springframework.lang.NonNull;
5 |
6 | import java.util.Map;
7 |
8 | import static java.lang.String.format;
9 | import static java.lang.String.valueOf;
10 |
11 | public class StdinOutputStreamResolver implements OutputStreamResolver {
12 | @Override
13 | public String resolve(@NonNull String template, @NonNull Map container) {
14 | return template
15 | .replaceAll(format("\\{\\{\\s*%s\\s*}}", EXTENSION), valueOf(container.get(EXTENSION)));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/com/osscameroon/jsgenerator/core/internal/TypeBasedVariableNameStrategy.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.core.internal;
2 |
3 | import com.osscameroon.jsgenerator.core.VariableNameStrategy;
4 | import org.springframework.lang.NonNull;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.concurrent.atomic.AtomicLong;
9 |
10 | import static java.lang.String.format;
11 |
12 | public class TypeBasedVariableNameStrategy implements VariableNameStrategy {
13 | private final Map counters = new HashMap<>();
14 |
15 | @Override
16 | public String nextName(@NonNull String type) {
17 | // NOTE: issue#145 careful with custom element about casing and dash, not to translate in JavaScript identifiers
18 | var identifier = type;
19 | final var HAS_DASH = type.contains("-");
20 | final var IS_ROOT = "targetElement".equals(type);
21 | final var HAS_UPPER_CASE = !type.chars()
22 | .allMatch(character -> Character.toLowerCase(character) == character);
23 |
24 | if (!IS_ROOT) {
25 | identifier = HAS_UPPER_CASE ? type.toLowerCase() : identifier;
26 | identifier = HAS_DASH
27 | ? type.replaceAll("-", "_").replaceAll("_+", "_") : identifier;
28 |
29 | if (HAS_DASH || HAS_UPPER_CASE) {
30 | identifier = "custom_%s".formatted(identifier);
31 | }
32 | }
33 |
34 | return format("%s_%03d", identifier, counters.computeIfAbsent(type, __ -> new AtomicLong(1)).getAndIncrement());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.core {
2 | exports com.osscameroon.jsgenerator.core;
3 | exports com.osscameroon.jsgenerator.core.autoconfigure;
4 |
5 | opens com.osscameroon.jsgenerator.core.autoconfigure;
6 |
7 | requires org.jsoup;
8 | requires spring.boot;
9 | requires spring.context;
10 |
11 | requires spring.boot.autoconfigure;
12 | requires spring.core;
13 | }
14 |
--------------------------------------------------------------------------------
/jsgenerator-core/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure
--------------------------------------------------------------------------------
/jsgenerator-desktop/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/modules.xml
8 | .idea/jarRepositories.xml
9 | .idea/compiler.xml
10 | .idea/libraries/
11 | *.iws
12 | *.iml
13 | *.ipr
14 |
15 | ### Eclipse ###
16 | .apt_generated
17 | .classpath
18 | .factorypath
19 | .project
20 | .settings
21 | .springBeans
22 | .sts4-cache
23 |
24 | ### NetBeans ###
25 | /nbproject/private/
26 | /nbbuild/
27 | /dist/
28 | /nbdist/
29 | /.nb-gradle/
30 | build/
31 | !**/src/main/**/build/
32 | !**/src/test/**/build/
33 |
34 | ### VS Code ###
35 | .vscode/
36 |
37 | ### Mac OS ###
38 | .DS_Store
--------------------------------------------------------------------------------
/jsgenerator-desktop/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | jsgenerator
8 | com.osscameroon
9 | ${revision}
10 |
11 |
12 | 4.0.0
13 | jsgenerator-desktop
14 | jsgenerator-desktop
15 |
16 |
17 | com.osscameroon.jsgenerator.desktop.autoconfigure.JsGeneratorDesktop
18 |
19 |
20 |
21 |
22 | com.osscameroon
23 | jsgenerator-core
24 |
25 |
26 | org.openjfx
27 | javafx-fxml
28 |
29 |
30 |
31 |
32 |
33 |
34 | org.graalvm.buildtools
35 | native-maven-plugin
36 |
37 |
38 | org.springframework.boot
39 | spring-boot-maven-plugin
40 |
41 | false
42 |
43 |
44 |
45 | org.apache.maven.plugins
46 | maven-compiler-plugin
47 |
48 | 22
49 | 22
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/autoconfigure/JsGeneratorDesktop.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.desktop.autoconfigure;
2 |
3 | import com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure;
4 | import com.osscameroon.jsgenerator.desktop.controller.FxmlNavigator;
5 | import com.osscameroon.jsgenerator.desktop.controller.FxmlResolver;
6 | import com.osscameroon.jsgenerator.desktop.controller.HelloViewController;
7 | import javafx.application.Application;
8 | import javafx.fxml.FXMLLoader;
9 | import javafx.scene.Parent;
10 | import javafx.scene.Scene;
11 | import javafx.stage.Stage;
12 | import org.springframework.boot.SpringApplication;
13 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
14 | import org.springframework.boot.autoconfigure.SpringBootApplication;
15 | import org.springframework.context.ApplicationContext;
16 | import org.springframework.context.annotation.Bean;
17 | import org.springframework.context.annotation.Lazy;
18 | import org.springframework.core.io.ClassPathResource;
19 |
20 | import java.io.IOException;
21 |
22 | @ImportAutoConfiguration(JsGeneratorCoreAutoconfigure.class)
23 | @SpringBootApplication(scanBasePackageClasses = HelloViewController.class)
24 | public class JsGeneratorDesktop extends Application {
25 | private static ApplicationContext context;
26 | private static FxmlResolver fxmlResolver;
27 | private static Scene scene;
28 |
29 | public static void main(String[] args) {
30 | context = SpringApplication.run(JsGeneratorDesktop.class, args);
31 | fxmlResolver = context.getBean(FxmlResolver.class);
32 | launch(JsGeneratorDesktop.class, args);
33 | }
34 |
35 | /**
36 | * Inject this bean to navigate from one view to another, like a router.
37 | *
38 | * @return
39 | */
40 | @Bean
41 | @Lazy
42 | public FxmlNavigator fxmlNavigator() {
43 | return scene::setRoot;
44 | }
45 |
46 | @Bean
47 | public FxmlResolver fxmlResolver() {
48 | return path -> {
49 | path = "com/osscameroon/jsgenerator/desktop/controller/%s.fxml".formatted(path);
50 | final var loader = new FXMLLoader(new ClassPathResource(path).getURL());
51 | loader.setControllerFactory(context::getBean);
52 | return (Parent) loader.load();
53 | };
54 | }
55 |
56 | @Override
57 | public void start(Stage stage) throws IOException {
58 | final var parent = fxmlResolver.resolve("hello-view");
59 | stage.setScene(scene = new Scene(parent));
60 | stage.show();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/controller/FxmlNavigator.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.desktop.controller;
2 |
3 | import javafx.scene.Parent;
4 |
5 | @FunctionalInterface
6 | public interface FxmlNavigator {
7 | void navigate(Parent parent);
8 | }
9 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/controller/FxmlResolver.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.desktop.controller;
2 |
3 | import javafx.scene.Parent;
4 |
5 | import java.io.IOException;
6 |
7 | @FunctionalInterface
8 | public interface FxmlResolver {
9 | Parent resolve(String relativePathWithoutExtension) throws IOException;
10 | }
11 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/java/com/osscameroon/jsgenerator/desktop/controller/HelloViewController.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.desktop.controller;
2 |
3 | import com.osscameroon.jsgenerator.core.Converter;
4 | import javafx.fxml.FXML;
5 | import javafx.scene.control.Label;
6 | import javafx.scene.control.TextArea;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.io.ByteArrayOutputStream;
11 | import java.io.IOException;
12 | import java.nio.charset.StandardCharsets;
13 |
14 | @Component
15 | public final class HelloViewController {
16 | private final Converter converter;
17 |
18 | @FXML
19 | private TextArea inputArea;
20 | @FXML
21 | private Label outputLabel;
22 |
23 | public HelloViewController(Converter converter) {
24 | this.converter = converter;
25 | }
26 |
27 | @FXML
28 | private void convert() throws IOException {
29 | try (var stream = new ByteArrayOutputStream()) {
30 | converter.convert(new ByteArrayInputStream(inputArea.textProperty().getValue().getBytes()), stream);
31 | outputLabel.setText(stream.toString(StandardCharsets.UTF_8));
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.desktop {
2 | exports com.osscameroon.jsgenerator.desktop.autoconfigure;
3 | exports com.osscameroon.jsgenerator.desktop.controller;
4 |
5 | opens com.osscameroon.jsgenerator.desktop.controller to javafx.fxml, spring.beans;
6 | opens com.osscameroon.jsgenerator.desktop.autoconfigure to javafx.fxml, spring.beans;
7 |
8 | requires com.osscameroon.jsgenerator.core;
9 |
10 | requires spring.boot.autoconfigure;
11 | requires spring.boot;
12 | requires spring.context;
13 |
14 | requires javafx.graphics;
15 | requires javafx.controls;
16 | requires javafx.fxml;
17 | requires spring.core;
18 | }
19 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | banner:
3 | location: classpath:/banner.txt
4 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 |
2 | _ _____ _____ ______ _ _ ______ _____ _______ ____ _____ _____ ______ _____ _ _________ ____ _____
3 | | |/ ____|/ ____| ____| \ | | ____| __ \ /\|__ __/ __ \| __ \ | __ \| ____|/ ____| |/ /__ __/ __ \| __ \
4 | | | (___ | | __| |__ | \| | |__ | |__) | / \ | | | | | | |__) |_____| | | | |__ | (___ | ' / | | | | | | |__) |
5 | _ | |\___ \| | |_ | __| | . ` | __| | _ / / /\ \ | | | | | | _ /______| | | | __| \___ \| < | | | | | | ___/
6 | | |__| |____) | |__| | |____| |\ | |____| | \ \ / ____ \| | | |__| | | \ \ | |__| | |____ ____) | . \ | | | |__| | |
7 | \____/|_____/ \_____|______|_| \_|______|_| \_\/_/ \_\_| \____/|_| \_\ |_____/|______|_____/|_|\_\ |_| \____/|_|
8 |
9 |
10 |
--------------------------------------------------------------------------------
/jsgenerator-desktop/src/main/resources/com/osscameroon/jsgenerator/desktop/controller/hello-view.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-slim-api
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-actuator
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-security
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-validation
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-web
30 |
31 |
32 | org.springdoc
33 | springdoc-openapi-starter-webmvc-ui
34 |
35 |
36 |
37 | com.osscameroon
38 | jsgenerator-core
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/JsGeneratorApi.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api;
2 |
3 | import com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.boot.actuate.health.HealthEndpoint;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Import;
9 | import org.springframework.http.HttpMethod;
10 | import org.springframework.security.config.Customizer;
11 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
12 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
15 | import org.springframework.security.web.SecurityFilterChain;
16 |
17 | import static org.springframework.boot.SpringApplication.run;
18 | import static org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest.toAnyEndpoint;
19 |
20 | /*
21 | * After migrating from Spring Boot 2.7.3 to 3.3.1 and deleting lombok, we got these 2 issues described below:
22 | *
23 | * Issue 1: We added "com.osscameroon.jsgenerator.core" to be scanned
24 | *
25 | ***************************
26 | APPLICATION FAILED TO START
27 | ***************************
28 | Description:
29 | Parameter 0 of constructor in com.osscameroon.jsgenerator.api.rest.ConvertController required a bean of type 'com.osscameroon.jsgenerator.core.OutputStreamResolver' that could not be found.
30 | Action:
31 | Consider defining a bean of type 'com.osscameroon.jsgenerator.core.OutputStreamResolver' in your configuration.
32 | *
33 | *
34 | * Issue 2: API tests were failing then we add "proxyBeanMethods = false"
35 | *
36 | * org.springframework.beans.factory.BeanDefinitionStoreException: Could not enhance configuration class [com.osscameroon.jsgenerator.api.JsGeneratorApi].
37 | * Consider declaring @Configuration(proxyBeanMethods=false) without inter-bean references between @Bean methods on the configuration class,
38 | * avoiding the need for CGLIB enhancement.
39 | *
40 | * https://github.com/osscameroon/js-generator/actions/runs/10111080853/job/27962309332#step:4:11924
41 | * */
42 | @EnableWebSecurity
43 | @EnableMethodSecurity
44 | @Import(JsGeneratorCoreAutoconfigure.class)
45 | @SpringBootApplication(proxyBeanMethods = false)//, scanBasePackageClasses = {Converter.class, JsGeneratorApi.class})
46 | public class JsGeneratorApi {
47 | public final static String ACTUATOR_ROLE = "ACTUATOR";
48 |
49 | @Bean
50 | public SecurityFilterChain securityFilterChain(
51 | @Value("${management.endpoints.web.base-path:/actuator}") final String actuatorBasePath,
52 | final HttpSecurity httpSecurity) throws Exception {
53 | return httpSecurity
54 | .csrf(AbstractHttpConfigurer::disable)
55 | .logout(AbstractHttpConfigurer::disable)
56 | .formLogin(AbstractHttpConfigurer::disable)
57 | .authorizeHttpRequests(spec -> spec
58 | .requestMatchers(toAnyEndpoint().excluding(HealthEndpoint.class).excludingLinks()).authenticated()
59 | .requestMatchers(HttpMethod.GET, actuatorBasePath).permitAll()
60 | .anyRequest().permitAll())
61 | .httpBasic(Customizer.withDefaults())
62 | .build();
63 | }
64 |
65 | public static void main(String[] args) {
66 | //noinspection resource
67 | run(JsGeneratorApi.class, args);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/domain/InlineOptions.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api.domain;
2 |
3 | import jakarta.validation.constraints.NotNull;
4 | import jakarta.validation.constraints.Size;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public final class InlineOptions extends Options {
10 | @Size(min = 1)
11 | private List<@NotNull String> contents = new ArrayList<>();
12 |
13 | public List getContents() {
14 | return contents;
15 | }
16 |
17 | public InlineOptions setContents(List contents) {
18 | this.contents = contents;
19 | return this;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/domain/MultipartOptions.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api.domain;
2 |
3 | public final class MultipartOptions extends Options {
4 | }
5 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/domain/Options.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api.domain;
2 |
3 | import com.osscameroon.jsgenerator.core.BuiltinVariableNameStrategy;
4 | import com.osscameroon.jsgenerator.core.Configuration;
5 | import com.osscameroon.jsgenerator.core.VariableDeclaration;
6 |
7 | import static com.osscameroon.jsgenerator.core.BuiltinVariableNameStrategy.TYPE_BASED;
8 | import static com.osscameroon.jsgenerator.core.VariableDeclaration.LET;
9 |
10 | public sealed class Options> permits InlineOptions, MultipartOptions {
11 | private BuiltinVariableNameStrategy variableNameStrategy = TYPE_BASED;
12 | private String pattern = "inline.{{ index }}{{ extension }}";
13 | private String targetElementSelector = ":root > body";
14 | private VariableDeclaration variableDeclaration = LET;
15 | private boolean commentConversionModeActivated = true;
16 | private String extension = ".jsgenerator.js";
17 | private boolean querySelectorAdded = true;
18 |
19 | protected Options() {
20 | }
21 |
22 | public BuiltinVariableNameStrategy getVariableNameStrategy() {
23 | return variableNameStrategy;
24 | }
25 |
26 | public O setVariableNameStrategy(BuiltinVariableNameStrategy variableNameStrategy) {
27 | this.variableNameStrategy = variableNameStrategy;
28 | //noinspection unchecked
29 | return (O) this;
30 | }
31 |
32 | public String getPattern() {
33 | return pattern;
34 | }
35 |
36 | public O setPattern(String pattern) {
37 | this.pattern = pattern;
38 | //noinspection unchecked
39 | return (O) this;
40 | }
41 |
42 | public String getTargetElementSelector() {
43 | return targetElementSelector;
44 | }
45 |
46 | public O setTargetElementSelector(String targetElementSelector) {
47 | this.targetElementSelector = targetElementSelector;
48 | //noinspection unchecked
49 | return (O) this;
50 | }
51 |
52 | public VariableDeclaration getVariableDeclaration() {
53 | return variableDeclaration;
54 | }
55 |
56 | public O setVariableDeclaration(VariableDeclaration variableDeclaration) {
57 | this.variableDeclaration = variableDeclaration;
58 | //noinspection unchecked
59 | return (O) this;
60 | }
61 |
62 | public boolean isCommentConversionModeActivated() {
63 | return commentConversionModeActivated;
64 | }
65 |
66 | public O setCommentConversionModeActivated(boolean commentConversionModeActivated) {
67 | this.commentConversionModeActivated = commentConversionModeActivated;
68 | //noinspection unchecked
69 | return (O) this;
70 | }
71 |
72 | public String getExtension() {
73 | return extension;
74 | }
75 |
76 | public O setExtension(String extension) {
77 | this.extension = extension;
78 | //noinspection unchecked
79 | return (O) this;
80 | }
81 |
82 | public boolean isQuerySelectorAdded() {
83 | return querySelectorAdded;
84 | }
85 |
86 | public O setQuerySelectorAdded(boolean querySelectorAdded) {
87 | this.querySelectorAdded = querySelectorAdded;
88 | //noinspection unchecked
89 | return (O) this;
90 | }
91 |
92 | public Configuration toConfiguration() {
93 | return new Configuration(
94 | targetElementSelector, querySelectorAdded,
95 | commentConversionModeActivated, variableDeclaration, variableNameStrategy.get());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/domain/Output.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api.domain;
2 |
3 | public record Output(String filename, String content) {
4 | }
5 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/rest/ConvertController.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api.rest;
2 |
3 | import com.osscameroon.jsgenerator.api.domain.InlineOptions;
4 | import com.osscameroon.jsgenerator.api.domain.MultipartOptions;
5 | import com.osscameroon.jsgenerator.api.domain.Output;
6 | import com.osscameroon.jsgenerator.core.Configuration;
7 | import com.osscameroon.jsgenerator.core.Converter;
8 | import com.osscameroon.jsgenerator.core.OutputStreamResolver;
9 | import jakarta.validation.Valid;
10 | import jakarta.validation.constraints.Size;
11 | import org.slf4j.Logger;
12 | import org.springframework.core.io.AbstractResource;
13 | import org.springframework.core.io.ByteArrayResource;
14 | import org.springframework.util.LinkedMultiValueMap;
15 | import org.springframework.util.MultiValueMap;
16 | import org.springframework.web.bind.annotation.PostMapping;
17 | import org.springframework.web.bind.annotation.RequestBody;
18 | import org.springframework.web.bind.annotation.RequestMapping;
19 | import org.springframework.web.bind.annotation.RequestPart;
20 | import org.springframework.web.bind.annotation.RestController;
21 | import org.springframework.web.multipart.MultipartFile;
22 |
23 | import java.io.ByteArrayInputStream;
24 | import java.io.ByteArrayOutputStream;
25 | import java.io.IOException;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.Optional;
29 | import java.util.concurrent.atomic.AtomicInteger;
30 |
31 | import static com.osscameroon.jsgenerator.api.rest.ConvertController.MAPPING;
32 | import static com.osscameroon.jsgenerator.core.OutputStreamResolver.EXTENSION;
33 | import static com.osscameroon.jsgenerator.core.OutputStreamResolver.INDEX;
34 | import static com.osscameroon.jsgenerator.core.OutputStreamResolver.ORIGINAL;
35 | import static java.nio.charset.StandardCharsets.UTF_8;
36 | import static org.slf4j.LoggerFactory.getLogger;
37 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
38 | import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
39 |
40 | @RestController
41 | @RequestMapping(MAPPING)
42 | public class ConvertController {
43 | public static final String MAPPING = "/convert";
44 | private static final Logger LOGGER = getLogger(ConvertController.class);
45 |
46 | private final OutputStreamResolver inlineOutputStreamResolver;
47 | private final OutputStreamResolver pathOutputStreamResolver;
48 | private final Converter converter;
49 |
50 | @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
51 | public ConvertController(OutputStreamResolver inlineOutputStreamResolver,
52 | OutputStreamResolver pathOutputStreamResolver,
53 | Converter converter) {
54 | this.inlineOutputStreamResolver = inlineOutputStreamResolver;
55 | this.pathOutputStreamResolver = pathOutputStreamResolver;
56 | this.converter = converter;
57 | }
58 |
59 | //TODO: Make sure all these 4 case are taken into account
60 | // code html to code js OK
61 | // code html to file js
62 | // file html to code js
63 | // file html to file js OK
64 |
65 | // code html to code js OK
66 | @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
67 | public Reply extends List extends Output>> convertAction(@RequestBody @Valid final InlineOptions options) {
68 | LOGGER.info("{}", options);
69 |
70 | final var index = new AtomicInteger();
71 | final var configuration = options.toConfiguration();
72 |
73 | return Reply.ofSuccesses(options.getContents().stream()
74 | .map(content -> convert(
75 | configuration,
76 | new ByteArrayOutputStream(),
77 | new ByteArrayInputStream(content.getBytes(UTF_8))))
78 | .map(content -> {
79 | final var filename = inlineOutputStreamResolver.resolve(options.getPattern(), Map.of(
80 | INDEX, "%d".formatted(index.getAndIncrement()),
81 | EXTENSION, options.getExtension()));
82 |
83 | return new Output(filename, content);
84 | })
85 | .toList());
86 | }
87 |
88 | // file html to file js OK
89 | @PostMapping(path = "files", consumes = MULTIPART_FORM_DATA_VALUE, produces = MULTIPART_FORM_DATA_VALUE)
90 | public MultiValueMap convertAction(
91 | @RequestPart("options") @Valid Optional optionalCommand,
92 | @RequestPart("files") @Size(min = 1, max = 30) @Valid List multipartFiles) {
93 | final var command = optionalCommand.orElseGet(MultipartOptions::new);
94 | final var map = new LinkedMultiValueMap();
95 | final var indexTracker = new AtomicInteger();
96 |
97 | multipartFiles.stream().map(multipartFile -> {
98 | try {
99 | return convert(
100 | command.toConfiguration(),
101 | new ByteArrayOutputStream(),
102 | new ByteArrayInputStream(multipartFile.getBytes()));
103 | } catch (IOException e) {
104 | throw new UnsupportedOperationException(e);
105 | }
106 | })
107 | .map(content -> {
108 | //noinspection ConstantConditions
109 | final var filename = pathOutputStreamResolver.resolve(command.getPattern(), Map.of(
110 | ORIGINAL, multipartFiles.get(indexTracker.get()).getOriginalFilename(),
111 | INDEX, "%d".formatted(indexTracker.get()),
112 | EXTENSION, command.getExtension()));
113 |
114 | indexTracker.getAndIncrement();
115 |
116 | return new Output(filename, content);
117 | })
118 | .forEach(output ->
119 | map.add(output.filename(), new ByteArrayResource(output.content().getBytes(UTF_8))));
120 |
121 | return map;
122 | }
123 |
124 | private String convert(Configuration configuration, ByteArrayOutputStream outputStream, ByteArrayInputStream inputStream) {
125 | try {
126 | converter.convert(inputStream, outputStream, configuration);
127 | } catch (IOException exception) {
128 | throw new RuntimeException(exception);
129 | }
130 |
131 | return outputStream.toString(UTF_8);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/com/osscameroon/jsgenerator/api/rest/Reply.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.api.rest;
2 |
3 | import java.util.List;
4 |
5 | public sealed class Reply {
6 | private final T content;
7 | private final Status status;
8 |
9 | private Reply(T content, Status status) {
10 | this.content = content;
11 | this.status = status;
12 | }
13 |
14 | public T getContent() {
15 | return content;
16 | }
17 |
18 | public Status getStatus() {
19 | return status;
20 | }
21 |
22 | public static Reply ofSuccess(final T content) {
23 | return new Success<>(content);
24 | }
25 |
26 | public static Reply extends List extends T>> ofSuccesses(final List contents) {
27 | return new Successes<>(contents);
28 | }
29 |
30 | public enum Status {
31 | SUCCESS, ERROR;
32 | }
33 |
34 | public static final class Success extends Reply {
35 | public Success(final T content) {
36 | super(content, Status.SUCCESS);
37 | }
38 | }
39 |
40 | public static final class Successes extends Reply> {
41 | public Successes(final List content) {
42 | super(content, Status.SUCCESS);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.api {
2 | exports com.osscameroon.jsgenerator.api.domain;
3 | exports com.osscameroon.jsgenerator.api.rest;
4 | exports com.osscameroon.jsgenerator.api;
5 |
6 | opens com.osscameroon.jsgenerator.api to spring.beans;
7 | opens com.osscameroon.jsgenerator.api.domain;
8 | opens com.osscameroon.jsgenerator.api.rest to spring.beans;
9 |
10 | requires com.osscameroon.jsgenerator.core;
11 |
12 | requires spring.beans;
13 | requires spring.boot.actuator;
14 | requires spring.boot;
15 | requires spring.boot.actuator.autoconfigure;
16 | requires spring.boot.autoconfigure;
17 | requires spring.context;
18 | requires spring.core;
19 | requires spring.security.config;
20 | requires spring.security.web;
21 | requires spring.web;
22 |
23 | requires org.slf4j;
24 | requires jakarta.validation;
25 | }
26 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | com.osscameroon.jsgenerator.api.JsGeneratorApi
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | management:
2 | endpoint:
3 | health:
4 | show-details: always
5 | endpoints:
6 | web:
7 | exposure:
8 | include: '*'
9 | spring:
10 | security:
11 | user:
12 | name: jsgenerator
13 | roles: ACTUATOR
14 | application:
15 | name: jsgenerator-api
16 | banner:
17 | location: classpath:/banner.txt
18 | springdoc:
19 | api-docs:
20 | path: /openapi.yaml
21 | swagger-ui:
22 | path: /
23 | use-root-path: false
24 | disable-swagger-default-url: true
25 |
--------------------------------------------------------------------------------
/jsgenerator-slim-api/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 |
2 | _ _____ _____ ______ _ _ ______ _____ _______ ____ _____ _____ _____
3 | | |/ ____|/ ____| ____| \ | | ____| __ \ /\|__ __/ __ \| __ \ /\ | __ \_ _|
4 | | | (___ | | __| |__ | \| | |__ | |__) | / \ | | | | | | |__) |_____ / \ | |__) || |
5 | _ | |\___ \| | |_ | __| | . ` | __| | _ / / /\ \ | | | | | | _ /______/ /\ \ | ___/ | |
6 | | |__| |____) | |__| | |____| |\ | |____| | \ \ / ____ \| | | |__| | | \ \ / ____ \| | _| |_
7 | \____/|_____/ \_____|______|_| \_|______|_| \_\/_/ \_\_| \____/|_| \_\ /_/ \_\_| |_____|
8 |
9 |
10 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-slim-cli
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter
18 |
19 |
20 |
21 | com.osscameroon
22 | jsgenerator-core
23 |
24 |
25 |
26 | info.picocli
27 | picocli
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/java/com/osscameroon/jsgenerator/cli/Command.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.cli;
2 |
3 | import com.osscameroon.jsgenerator.core.BuiltinVariableNameStrategy;
4 | import com.osscameroon.jsgenerator.core.Converter;
5 | import com.osscameroon.jsgenerator.core.VariableDeclaration;
6 |
7 | import java.nio.file.Path;
8 | import java.util.List;
9 | import java.util.concurrent.Callable;
10 |
11 | public interface Command extends Callable {
12 | BuiltinVariableNameStrategy getBuiltinVariableNameStrategy();
13 |
14 | VariableDeclaration getVariableDeclaration();
15 |
16 | String getTargetElementSelector();
17 |
18 | boolean isQuerySelectorAdded();
19 |
20 | List getInlineContents();
21 |
22 | Converter getConverter();
23 |
24 | List getPaths();
25 |
26 | boolean isTty();
27 | }
28 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/java/com/osscameroon/jsgenerator/cli/JsGeneratorCli.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.cli;
2 |
3 | import com.osscameroon.jsgenerator.cli.internal.CommandDefault;
4 | import com.osscameroon.jsgenerator.core.Converter;
5 | import com.osscameroon.jsgenerator.core.OutputStreamResolver;
6 | import com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure;
7 | import org.springframework.boot.CommandLineRunner;
8 | import org.springframework.boot.SpringBootConfiguration;
9 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Import;
12 | import picocli.CommandLine;
13 |
14 | import java.util.List;
15 |
16 | import static java.lang.System.exit;
17 | import static org.springframework.boot.SpringApplication.run;
18 |
19 | @EnableAutoConfiguration
20 | @SpringBootConfiguration
21 | @Import(JsGeneratorCoreAutoconfigure.class)
22 | public class JsGeneratorCli {
23 | public static void main(String[] args) {
24 | run(JsGeneratorCli.class, 0 == args.length ? new String[]{"--help"} : args);
25 | }
26 |
27 | @Bean
28 | public CommandLineRunner picoCliRunner(final Command command) {
29 | final var commandLine = new CommandLine(command);
30 | return args -> exit(commandLine.execute(args));
31 | }
32 |
33 | @Bean
34 | public Command command(final Converter converter,
35 | final OutputStreamResolver pathOutputStreamResolver,
36 | final OutputStreamResolver stdinOutputStreamResolver,
37 | final OutputStreamResolver inlineOutputStreamResolver) {
38 | return new CommandDefault(
39 | inlineOutputStreamResolver,
40 | stdinOutputStreamResolver,
41 | pathOutputStreamResolver,
42 | converter);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/java/com/osscameroon/jsgenerator/cli/Valid.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.cli;
2 |
3 | import java.nio.file.Files;
4 | import java.nio.file.LinkOption;
5 | import java.nio.file.Path;
6 | import java.util.ArrayList;
7 |
8 | public interface Valid {
9 | /**
10 | * Validate the {@link Command} this interface is implemented on.
11 | *
12 | * @return {@code 0} is the command is valid, {@code -1} is some paths doesn't exist, {@code -2} is some paths
13 | * weren't referencing regular files as per {@link java.nio.file.Files#isRegularFile(Path, LinkOption...)}
14 | * regular file understanding.
15 | */
16 | default int isValid() {
17 | if (!(this instanceof Command)) return -999;
18 | final var command = (Command) this;
19 | final var nonExistent = new ArrayList();
20 | final var nonRegularFiles = new ArrayList();
21 |
22 | for (final var path : command.getPaths()) {
23 | if (!Files.exists(path)) nonExistent.add(path.toAbsolutePath());
24 | else if (!Files.isRegularFile(path)) nonRegularFiles.add(path.toAbsolutePath());
25 | }
26 |
27 | if (!nonExistent.isEmpty()) {
28 | System.err.println("Some path does not exists:");
29 |
30 | for (final var path : nonExistent)
31 | System.err.printf(" - %s%n", path.toAbsolutePath());
32 | }
33 |
34 | if (!nonRegularFiles.isEmpty()) {
35 | System.err.println("Some path are not regular files:");
36 |
37 | for (final var path : nonRegularFiles)
38 | System.err.printf(" - %s%n", path.toAbsolutePath());
39 | }
40 |
41 | if (!nonExistent.isEmpty()) return -1;
42 |
43 | if (!nonRegularFiles.isEmpty()) return -2;
44 |
45 | return 0;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/java/com/osscameroon/jsgenerator/cli/internal/CommandDefault.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.cli.internal;
2 |
3 | import com.osscameroon.jsgenerator.cli.Command;
4 | import com.osscameroon.jsgenerator.cli.Valid;
5 | import com.osscameroon.jsgenerator.core.*;
6 | import picocli.CommandLine;
7 | import picocli.CommandLine.Option;
8 | import picocli.CommandLine.Parameters;
9 |
10 | import java.io.*;
11 | import java.nio.file.Files;
12 | import java.nio.file.Path;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | import static com.osscameroon.jsgenerator.core.BuiltinVariableNameStrategy.TYPE_BASED;
18 | import static com.osscameroon.jsgenerator.core.OutputStreamResolver.*;
19 | import static java.lang.String.format;
20 | import static java.nio.charset.StandardCharsets.UTF_8;
21 |
22 | @CommandLine.Command(
23 | name = "jsgenerator",
24 | version = "0.0.1-SNAPSHOT",
25 | mixinStandardHelpOptions = true,
26 | description = "Translating files, stdin or inline from HTML to JS")
27 | public class CommandDefault implements Command, Valid {
28 | private final OutputStreamResolver inlineFilenameResolver;
29 | private final OutputStreamResolver stdinFilenameResolver;
30 | private final OutputStreamResolver pathFilenameResolver;
31 | private final Converter converter;
32 |
33 | //TODO:show options for variable declarations, test what we get based on user choice let const or var and querySelectorAdded and commentsAdded
34 | @Option(names = {"-k", "--keyword"}, description = "variable declaration keyword")
35 | private VariableDeclaration variableDeclaration = VariableDeclaration.LET;
36 | @Option(names = {"-t", "--tty"}, description = "output to stdin, not files")
37 | private boolean tty;
38 |
39 | @Parameters(index = "0..*", description = "file paths to translate content, parsed as HTML")
40 | private List paths = new ArrayList<>();
41 |
42 | @Option(
43 | arity = "1..*", names = {"-i", "--inline"}, description = "args as HTML content, not files")
44 | private List inlineContents = new ArrayList<>();
45 |
46 | @Option(names = {"-e", "--ext"}, defaultValue = ".jsgenerator.js", description = "output files' extension")
47 | private String extension = ".jsgenerator.js";
48 |
49 | @Option(
50 | names = {"-s", "--selector"},
51 | defaultValue = ":root > body",
52 | description = "Target element selector")
53 | private String targetElementSelector = ":root > body";
54 |
55 | @Option(
56 | names = {"-qs", "--query-selector"},
57 | defaultValue = "true",
58 | description = "What the browser renders depends on whether \"document.querySelector(':root > body')\" is added to the output." +
59 | " If added, the browser will render the output successfully, it is useful for debugging purpose,\n" +
60 | " to verify that the js output matches what the html input does.\n" +
61 | " If not, if the user tries to run the output as it is then the browser will not be able to render,it will show a blank page.\n" +
62 | " So, it depends on what the user wants to do with the output.\n" +
63 | " \"https://jsfiddle.net/\", \"https://codepen.io/pen/\" and Browser Console help to give a quick feedback.\n")
64 | private boolean querySelectorAdded = true;
65 |
66 |
67 | @Option(
68 | names = {"-c", "--comment"},
69 | defaultValue = "true",
70 | description = "optional comments")
71 | private boolean commentConversionModeActivated = true;
72 |
73 | @Option(
74 | names = "--stdin-pattern",
75 | defaultValue = "stdin{{ extension }}",
76 | description = "pattern for stdin output filenames")
77 | private String stdinPattern = format("stdin{{ %s }}", EXTENSION);
78 |
79 | @Option(
80 | names = "--path-pattern",
81 | defaultValue = "{{ original }}{{ extension }}",
82 | description = "pattern for path-based output filenames")
83 | private String pathPattern = format("{{ %s }}{{ %s }}", ORIGINAL, EXTENSION);
84 |
85 | @Option(
86 | names = "--variable-name-generation-strategy",
87 | defaultValue = "TYPE_BASED",
88 | description = "Variable names generation strategy")
89 | private BuiltinVariableNameStrategy builtinVariableNameStrategy = TYPE_BASED;
90 |
91 | @Option(
92 | names = "--inline-pattern",
93 | defaultValue = "inline.{{ index }}{{ extension }}",
94 | description = "Pattern for inline output filename")
95 | private String inlinePattern = format("inline.{{ %s }}{{ %s }}", INDEX, EXTENSION);
96 |
97 | public CommandDefault(OutputStreamResolver inlineFilenameResolver, OutputStreamResolver stdinFilenameResolver, OutputStreamResolver pathFilenameResolver, Converter converter) {
98 | this.inlineFilenameResolver = inlineFilenameResolver;
99 | this.stdinFilenameResolver = stdinFilenameResolver;
100 | this.pathFilenameResolver = pathFilenameResolver;
101 | this.converter = converter;
102 | }
103 |
104 | @Override
105 | public VariableDeclaration getVariableDeclaration() {
106 | return variableDeclaration;
107 | }
108 |
109 | @Override
110 | public boolean isTty() {
111 | return tty;
112 | }
113 |
114 | @Override
115 | public List getPaths() {
116 | return paths;
117 | }
118 |
119 | @Override
120 | public List getInlineContents() {
121 | return inlineContents;
122 | }
123 |
124 | public String getExtension() {
125 | return extension;
126 | }
127 |
128 | @Override
129 | public String getTargetElementSelector() {
130 | return targetElementSelector;
131 | }
132 |
133 | @Override
134 | public boolean isQuerySelectorAdded() {
135 | return querySelectorAdded;
136 | }
137 |
138 | public boolean isCommentConversionModeActivated() {
139 | return commentConversionModeActivated;
140 | }
141 |
142 | public String getStdinPattern() {
143 | return stdinPattern;
144 | }
145 |
146 | public String getPathPattern() {
147 | return pathPattern;
148 | }
149 |
150 | @Override
151 | public BuiltinVariableNameStrategy getBuiltinVariableNameStrategy() {
152 | return builtinVariableNameStrategy;
153 | }
154 |
155 | @Override
156 | public Converter getConverter() {
157 | return converter;
158 | }
159 |
160 | public String getInlinePattern() {
161 | return inlinePattern;
162 | }
163 |
164 | @Override
165 | public Integer call() throws IOException {
166 | final var IS_VALID = isValid();
167 | if (0 > IS_VALID) return IS_VALID;
168 | OutputStream outputStream;
169 |
170 | // NOTE: Start with standard input, if not blank
171 | final var stdinReader = new BufferedReader(new InputStreamReader(System.in));
172 | final var builder = new StringBuilder();
173 | String line;
174 | while (0 < System.in.available() && null != (line = stdinReader.readLine()))
175 | builder.append(line).append("\r\n");
176 |
177 | if (0 < builder.length()) {
178 | System.err.println("\fTranslating [stdin]: <<");
179 | converter.convert(
180 | new ByteArrayInputStream(builder.toString().getBytes(UTF_8)),
181 | outputStream = resolveStdinOutputStream());
182 | outputStream.flush();
183 | }
184 |
185 | // NOTE: Continue with inline HTML content
186 | for (var i = 0; i < inlineContents.size(); i++) {
187 | if (0 < inlineContents.get(i).length()) {
188 | System.err.printf("\fTranslating [inline]: %d%n", i);
189 | converter.convert(
190 | new ByteArrayInputStream(inlineContents.get(i).getBytes(UTF_8)),
191 | outputStream = resolveInlineOutputStream(i));
192 | outputStream.flush();
193 | }
194 | }
195 |
196 | // NOTE: Process paths
197 | for (final var path : paths.stream().map(Path::toAbsolutePath).distinct().toList()) {
198 | System.err.printf("\fTranslating: %s%n", path);
199 | converter.convert(
200 | Files.newInputStream(path),
201 | outputStream = resolvePathOutputStream(path),
202 | new Configuration(targetElementSelector, querySelectorAdded, commentConversionModeActivated, variableDeclaration, builtinVariableNameStrategy.get()));
203 | outputStream.flush();
204 | outputStream.close();
205 | }
206 |
207 | return 0;
208 | }
209 |
210 | private OutputStream resolveStdinOutputStream() throws IOException {
211 | if (tty) return System.out;
212 |
213 | final var outputPathname = pathFilenameResolver.resolve(pathPattern, Map.of(EXTENSION, extension));
214 | final var outputPath = Path.of(outputPathname);
215 |
216 | return Files.newOutputStream(outputPath);
217 | }
218 |
219 | private OutputStream resolveInlineOutputStream(final int index) throws IOException {
220 | if (tty) return System.out;
221 |
222 | final var outputPathname = pathFilenameResolver.resolve(pathPattern, Map.of(
223 | EXTENSION, extension,
224 | INDEX, index
225 | ));
226 | final var outputPath = Path.of(outputPathname);
227 |
228 | return Files.newOutputStream(outputPath);
229 | }
230 |
231 | private OutputStream resolvePathOutputStream(Path path) throws IOException {
232 | if (tty) return System.out;
233 |
234 | final var nameCount = path.getNameCount();
235 | final var outputPathname = pathFilenameResolver.resolve(pathPattern, Map.of(
236 | ORIGINAL_EXTENSION, path.getName(nameCount - 1).toString()
237 | .replace("^.*\\.(.*)$", "$1"),
238 | ORIGINAL_DIRECTORY, path.getName(nameCount - 2).toString(),
239 | ORIGINAL_BASENAME, path.getName(nameCount - 1).toString(),
240 | ORIGINAL, path.toString(),
241 | EXTENSION, extension
242 | ));
243 | final var outputPath = Path.of(outputPathname);
244 |
245 | return Files.newOutputStream(outputPath);
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.cli {
2 | exports com.osscameroon.jsgenerator.cli;
3 | opens com.osscameroon.jsgenerator.cli;
4 |
5 | requires com.osscameroon.jsgenerator.core;
6 |
7 | requires spring.boot;
8 |
9 | requires spring.boot.autoconfigure;
10 | requires spring.context;
11 | requires info.picocli;
12 |
13 | requires spring.beans;
14 | }
15 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | logging:
2 | level:
3 | root: error
4 | spring:
5 | banner:
6 | location: classpath:/banner.txt
7 |
--------------------------------------------------------------------------------
/jsgenerator-slim-cli/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 |
2 | _ _____ _____ ______ _ _ ______ _____ _______ ____ _____ _____ _ _____
3 | | |/ ____|/ ____| ____| \ | | ____| __ \ /\|__ __/ __ \| __ \ / ____| | |_ _|
4 | | | (___ | | __| |__ | \| | |__ | |__) | / \ | | | | | | |__) |_____| | | | | |
5 | _ | |\___ \| | |_ | __| | . ` | __| | _ / / /\ \ | | | | | | _ /______| | | | | |
6 | | |__| |____) | |__| | |____| |\ | |____| | \ \ / ____ \| | | |__| | | \ \ | |____| |____ _| |_
7 | \____/|_____/ \_____|______|_| \_|______|_| \_\/_/ \_\_| \____/|_| \_\ \_____|______|_____|
8 |
9 |
10 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator-test
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-test-api
13 |
14 |
15 |
16 | com.osscameroon
17 | jsgenerator-slim-api
18 |
19 |
20 |
21 | org.springframework.security
22 | spring-security-test
23 | test
24 |
25 |
26 |
27 | org.junit-pioneer
28 | junit-pioneer
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | open module com.osscameroon.jsgenerator.test.api.noop {
2 | }
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/java/com/osscameroon/jsgenerator/test/api/JsGeneratorApiTest.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.test.api;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.fasterxml.jackson.databind.json.JsonMapper;
5 | import com.osscameroon.jsgenerator.api.JsGeneratorApi;
6 | import com.osscameroon.jsgenerator.api.rest.ConvertController;
7 | import com.osscameroon.jsgenerator.core.VariableDeclaration;
8 | import com.osscameroon.jsgenerator.core.autoconfigure.JsGeneratorCoreAutoconfigure;
9 | import org.hamcrest.CustomMatcher;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.Disabled;
12 | import org.junit.jupiter.api.Test;
13 | import org.junit.jupiter.params.ParameterizedTest;
14 | import org.junit.jupiter.params.provider.Arguments;
15 | import org.junit.jupiter.params.provider.MethodSource;
16 | import org.springframework.beans.factory.annotation.Autowired;
17 | import org.springframework.boot.test.context.SpringBootTest;
18 | import org.springframework.mock.web.MockMultipartFile;
19 | import org.springframework.security.test.context.support.WithMockUser;
20 | import org.springframework.test.web.servlet.MockMvc;
21 | import org.springframework.web.context.WebApplicationContext;
22 |
23 | import java.io.IOException;
24 | import java.nio.file.Files;
25 | import java.nio.file.Path;
26 | import java.util.List;
27 | import java.util.stream.Stream;
28 |
29 | import static com.osscameroon.jsgenerator.test.api.helper.MultipartResultMatcher.withMultipart;
30 | import static java.nio.charset.StandardCharsets.UTF_8;
31 | import static java.util.Map.of;
32 | import static java.util.UUID.randomUUID;
33 | import static org.assertj.core.api.Assertions.assertThat;
34 | import static org.hamcrest.text.MatchesPattern.matchesPattern;
35 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.MOCK;
36 | import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
37 | import static org.springframework.http.MediaType.*;
38 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
39 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
40 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
41 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
42 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
43 |
44 | @SpringBootTest(webEnvironment = MOCK, classes = {ConvertController.class, JsGeneratorCoreAutoconfigure.class, JsGeneratorApi.class})
45 | class JsGeneratorApiTest {
46 | private static final Path SAMPLE_OUTPUT_QUERY_SELECTOR_NOT_ADDED_AND_COMMENT_CONVERSION_MODE_NOT_ACTIVATED = Path.of("src", "test", "resources", "jsFilesOutput", "querySelectorNotAdded", "commentConversionModeNotActivated", "sample.js");
47 | private static final Path SAMPLE_OUTPUT_QUERY_SELECTOR_NOT_ADDED_AND_COMMENT_CONVERSION_MODE_ACTIVATED = Path.of("src", "test", "resources", "jsFilesOutput", "querySelectorNotAdded", "commentConversionModeActivated", "sample.js");
48 | private static final Path SAMPLE_OUTPUT_QUERY_SELECTOR_ADDED_AND_COMMENT_CONVERSION_MODE_NOT_ACTIVATED = Path.of("src", "test", "resources", "jsFilesOutput", "querySelectorAdded", "commentConversionModeNotActivated", "sample.js");
49 | private static final Path SAMPLE_OUTPUT_QUERY_SELECTOR_ADDED_AND_COMMENT_CONVERSION_MODE_ACTIVATED = Path.of("src", "test", "resources", "jsFilesOutput", "querySelectorAdded", "commentConversionModeActivated", "sample.js");
50 | private static final Path SAMPLE_OUTPUT_QUERY_SELECTOR_NOT_ADDED = Path.of("src", "test", "resources", "jsFilesOutput", "querySelectorNotAdded", "sample.js");
51 | private static final Path SAMPLE_OUTPUT_QUERY_SELECTOR_ADDED = Path.of("src", "test", "resources", "jsFilesOutput", "querySelectorAdded", "sample.js");
52 | private static final Path SAMPLE_INPUT_WITH_COMMENT = Path.of("src", "test", "resources", "htmlFilesInput", "sampleWithComment.html");
53 | private static final Path SAMPLE_INPUT = Path.of("src", "test", "resources", "htmlFilesInput", "sample.html");
54 |
55 | @Autowired
56 | private WebApplicationContext webApplicationContext;
57 | private ObjectMapper objectMapper;
58 |
59 | private MockMvc mockMvc;
60 |
61 | private static String keyword(final VariableDeclaration variableDeclaration) {
62 | return variableDeclaration.name().toLowerCase();
63 | }
64 |
65 | private static String[] toArray(String content) {
66 | return content
67 | .lines()
68 | .map(String::strip)
69 | .filter(line -> !line.isEmpty())
70 | .toArray(String[]::new);
71 | }
72 |
73 | private static String fileContent(final Path path) throws IOException {
74 | return Files.readString(path, UTF_8);
75 | }
76 |
77 | private static Stream provideVariableDeclarationsAndQuerySelectorAddedAndCommentConversionModeActivated() {
78 | return Stream.of(
79 | Arguments.of(VariableDeclaration.LET, true, true),
80 | Arguments.of(VariableDeclaration.LET, false, true),
81 | Arguments.of(VariableDeclaration.VAR, true, true),
82 | Arguments.of(VariableDeclaration.VAR, false, true),
83 | Arguments.of(VariableDeclaration.CONST, true, true),
84 | Arguments.of(VariableDeclaration.CONST, false, true),
85 | Arguments.of(VariableDeclaration.LET, true, false),
86 | Arguments.of(VariableDeclaration.LET, false, false),
87 | Arguments.of(VariableDeclaration.VAR, true, false),
88 | Arguments.of(VariableDeclaration.VAR, false, false),
89 | Arguments.of(VariableDeclaration.CONST, true, false),
90 | Arguments.of(VariableDeclaration.CONST, false, false)
91 | );
92 | }
93 |
94 | private static Stream provideVariableDeclarationsAndQuerySelectorAdded() {
95 | return Stream.of(
96 | Arguments.of(VariableDeclaration.LET, true),
97 | Arguments.of(VariableDeclaration.LET, false),
98 | Arguments.of(VariableDeclaration.VAR, true),
99 | Arguments.of(VariableDeclaration.VAR, false),
100 | Arguments.of(VariableDeclaration.CONST, true),
101 | Arguments.of(VariableDeclaration.CONST, false)
102 | );
103 | }
104 |
105 |
106 | @BeforeEach
107 | public void beforeEach() {
108 | objectMapper = JsonMapper.builder().build();
109 | mockMvc = webAppContextSetup(webApplicationContext)
110 | .apply(springSecurity())
111 | .alwaysDo(print())
112 | .build();
113 | }
114 |
115 | @Test
116 | void actuatorPublicEndpoint() throws Exception {
117 | // GET /actuator :: public
118 | mockMvc.perform(get("/actuator"))
119 | .andExpect(status().isOk())
120 | .andExpect(header().string("Content-Type", "application/vnd.spring-boot.actuator.v3+json"));
121 | // GET /actuator/health :: public
122 | mockMvc.perform(get("/actuator/health"))
123 | .andExpect(status().isOk())
124 | .andExpect(header().string("Content-Type", "application/vnd.spring-boot.actuator.v3+json"));
125 | // GET /actuator/metrics :: secured
126 | mockMvc.perform(get("/actuator/beans"))
127 | .andExpect(status().isUnauthorized());
128 | mockMvc.perform(get("/actuator/metrics"))
129 | .andExpect(status().isUnauthorized());
130 | mockMvc.perform(get("/actuator/mappings"))
131 | .andExpect(status().isUnauthorized());
132 | }
133 |
134 | @Test
135 | @WithMockUser(username = "user", roles = "ACTUATOR")
136 | void actuatorSecuredEndpoint() throws Exception {
137 | mockMvc.perform(get("/actuator/beans"))
138 | .andExpect(status().isOk())
139 | .andExpect(header().string("Content-Type", "application/vnd.spring-boot.actuator.v3+json"));
140 | mockMvc.perform(get("/actuator/metrics"))
141 | .andExpect(status().isOk())
142 | .andExpect(header().string("Content-Type", "application/vnd.spring-boot.actuator.v3+json"));
143 | mockMvc.perform(get("/actuator/mappings"))
144 | .andExpect(status().isOk())
145 | .andExpect(header().string("Content-Type", "application/vnd.spring-boot.actuator.v3+json"));
146 | }
147 |
148 | @ParameterizedTest
149 | @MethodSource("provideVariableDeclarationsAndQuerySelectorAdded")
150 | void convertInlineContent(final VariableDeclaration variableDeclaration, final boolean querySelectorAdded) throws Exception {
151 | final var keyword = keyword(variableDeclaration);
152 | final var extension = randomUUID().toString();
153 | final var prefix = randomUUID().toString();
154 | final var content = randomUUID().toString();
155 |
156 | if (querySelectorAdded) {
157 |
158 | mockMvc.perform(post(ConvertController.MAPPING)
159 | .header(CONTENT_TYPE, APPLICATION_JSON)
160 | .content(objectMapper.writeValueAsString(of(
161 | "contents", List.of("%s
".formatted(content)),
162 | "pattern", "%s.{{ index }}{{ extension }}".formatted(prefix),
163 | "variableDeclaration", variableDeclaration,
164 | "extension", ".%s".formatted(extension),
165 | "querySelectorAdded", true,
166 | "commentConversionModeActivated", true
167 | ))))
168 | .andExpectAll(
169 | status().isOk(),
170 | header().string(CONTENT_TYPE, APPLICATION_JSON_VALUE),
171 | jsonPath("$.status").value("SUCCESS"),
172 | jsonPath("$.content").isArray(),
173 | jsonPath("$.content.length()").value(1),
174 | jsonPath("$.content.[0].filename").value("%s.0.%s".formatted(prefix, extension)),
175 | jsonPath("$.content.[0].content").value(new Match(new String[]{
176 | "%s targetElement_001 = document.querySelector(`:root > body`);".formatted(keyword),
177 | "%s div_001 = document.createElement('div');".formatted(keyword),
178 | "div_001.setAttribute(`contenteditable`, `true`);",
179 | "%s text_001 = document.createTextNode(`%s`);".formatted(keyword, content),
180 | "div_001.appendChild(text_001);",
181 | "targetElement_001.appendChild(div_001);"
182 | })));
183 |
184 |
185 | } else {
186 |
187 | mockMvc.perform(post(ConvertController.MAPPING)
188 | .header(CONTENT_TYPE, APPLICATION_JSON)
189 | .content(objectMapper.writeValueAsString(of(
190 | "contents", List.of("%s
".formatted(content)),
191 | "pattern", "%s.{{ index }}{{ extension }}".formatted(prefix),
192 | "variableDeclaration", variableDeclaration,
193 | "extension", ".%s".formatted(extension),
194 | "querySelectorAdded", false,
195 | "commentConversionModeActivated", true
196 | ))))
197 | .andExpectAll(
198 | status().isOk(),
199 | header().string(CONTENT_TYPE, APPLICATION_JSON_VALUE),
200 | jsonPath("$.status").value("SUCCESS"),
201 | jsonPath("$.content").isArray(),
202 | jsonPath("$.content.length()").value(1),
203 | jsonPath("$.content.[0].filename").value("%s.0.%s".formatted(prefix, extension)),
204 | jsonPath("$.content.[0].content").value(new Match(new String[]{
205 |
206 | "%s div_001 = document.createElement('div');".formatted(keyword),
207 | "div_001.setAttribute(`contenteditable`, `true`);",
208 | "%s text_001 = document.createTextNode(`%s`);".formatted(keyword, content),
209 | "div_001.appendChild(text_001);"
210 |
211 | })));
212 |
213 |
214 | }
215 |
216 | }
217 |
218 | @ParameterizedTest
219 | @MethodSource("provideVariableDeclarationsAndQuerySelectorAddedAndCommentConversionModeActivated")
220 | void convertInlineContentWithComment(final VariableDeclaration variableDeclaration, final boolean querySelectorAdded, final boolean commentConversionModeActivated) throws Exception {
221 | final var keyword = keyword(variableDeclaration);
222 | final var extension = randomUUID().toString();
223 | final var prefix = randomUUID().toString();
224 | final var content = randomUUID().toString();
225 | final var input = " %s
".formatted(content);
226 |
227 | if (querySelectorAdded) {
228 |
229 | if (commentConversionModeActivated) {
230 |
231 | mockMvc.perform(post(ConvertController.MAPPING)
232 | .header(CONTENT_TYPE, APPLICATION_JSON)
233 | .content(objectMapper.writeValueAsString(of(
234 | "contents", List.of(input),
235 | "pattern", "%s.{{ index }}{{ extension }}".formatted(prefix),
236 | "variableDeclaration", variableDeclaration,
237 | "extension", ".%s".formatted(extension),
238 | "querySelectorAdded", true,
239 | "commentConversionModeActivated", true
240 | ))))
241 | .andExpectAll(
242 | status().isOk(),
243 | header().string(CONTENT_TYPE, APPLICATION_JSON_VALUE),
244 | jsonPath("$.status").value("SUCCESS"),
245 | jsonPath("$.content").isArray(),
246 | jsonPath("$.content.length()").value(1),
247 | jsonPath("$.content.[0].filename").value("%s.0.%s".formatted(prefix, extension)),
248 | jsonPath("$.content.[0].content").value(new Match(new String[]{
249 | "%s targetElement_001 = document.querySelector(`:root > body`);".formatted(keyword),
250 | "%s comment_001 = document.createComment(` ContentEditable `);".formatted(keyword),
251 | "targetElement_001.appendChild(comment_001);",
252 | "%s text_001 = document.createTextNode(` `);".formatted(keyword),
253 | "targetElement_001.appendChild(text_001);",
254 | "%s div_001 = document.createElement('div');".formatted(keyword),
255 | "div_001.setAttribute(`contenteditable`, `true`);",
256 | "%s text_002 = document.createTextNode(`%s`);".formatted(keyword, content),
257 | "div_001.appendChild(text_002);",
258 | "targetElement_001.appendChild(div_001);"
259 | })));
260 |
261 |
262 | } else {
263 |
264 | mockMvc.perform(post(ConvertController.MAPPING)
265 | .header(CONTENT_TYPE, APPLICATION_JSON)
266 | .content(objectMapper.writeValueAsString(of(
267 | "contents", List.of(input),
268 | "pattern", "%s.{{ index }}{{ extension }}".formatted(prefix),
269 | "variableDeclaration", variableDeclaration,
270 | "extension", ".%s".formatted(extension),
271 | "querySelectorAdded", true,
272 | "commentConversionModeActivated", false
273 | ))))
274 | .andExpectAll(
275 | status().isOk(),
276 | header().string(CONTENT_TYPE, APPLICATION_JSON_VALUE),
277 | jsonPath("$.status").value("SUCCESS"),
278 | jsonPath("$.content").isArray(),
279 | jsonPath("$.content.length()").value(1),
280 | jsonPath("$.content.[0].filename").value("%s.0.%s".formatted(prefix, extension)),
281 | jsonPath("$.content.[0].content").value(new Match(new String[]{
282 | "%s targetElement_001 = document.querySelector(`:root > body`);".formatted(keyword),
283 | "%s text_001 = document.createTextNode(` `);".formatted(keyword),
284 | "targetElement_001.appendChild(text_001);",
285 | "%s div_001 = document.createElement('div');".formatted(keyword),
286 | "div_001.setAttribute(`contenteditable`, `true`);",
287 | "%s text_002 = document.createTextNode(`%s`);".formatted(keyword, content),
288 | "div_001.appendChild(text_002);",
289 | "targetElement_001.appendChild(div_001);"
290 | })));
291 |
292 | }
293 |
294 |
295 | } else {
296 |
297 | if (commentConversionModeActivated) {
298 | mockMvc.perform(post(ConvertController.MAPPING)
299 | .header(CONTENT_TYPE, APPLICATION_JSON)
300 | .content(objectMapper.writeValueAsString(of(
301 | "contents", List.of(input),
302 | "pattern", "%s.{{ index }}{{ extension }}".formatted(prefix),
303 | "variableDeclaration", variableDeclaration,
304 | "extension", ".%s".formatted(extension),
305 | "querySelectorAdded", false,
306 | "commentConversionModeActivated", true
307 | ))))
308 | .andExpectAll(
309 | status().isOk(),
310 | header().string(CONTENT_TYPE, APPLICATION_JSON_VALUE),
311 | jsonPath("$.status").value("SUCCESS"),
312 | jsonPath("$.content").isArray(),
313 | jsonPath("$.content.length()").value(1),
314 | jsonPath("$.content.[0].filename").value("%s.0.%s".formatted(prefix, extension)),
315 | jsonPath("$.content.[0].content").value(new Match(new String[]{
316 | "%s comment_001 = document.createComment(` ContentEditable `);".formatted(keyword),
317 | "%s text_001 = document.createTextNode(` `);".formatted(keyword),
318 | "%s div_001 = document.createElement('div');".formatted(keyword),
319 | "div_001.setAttribute(`contenteditable`, `true`);",
320 | "%s text_002 = document.createTextNode(`%s`);".formatted(keyword, content),
321 | "div_001.appendChild(text_002);"
322 | })));
323 |
324 |
325 | } else {
326 |
327 | mockMvc.perform(post(ConvertController.MAPPING)
328 | .header(CONTENT_TYPE, APPLICATION_JSON)
329 | .content(objectMapper.writeValueAsString(of(
330 | "contents", List.of(input),
331 | "pattern", "%s.{{ index }}{{ extension }}".formatted(prefix),
332 | "variableDeclaration", variableDeclaration,
333 | "extension", ".%s".formatted(extension),
334 | "querySelectorAdded", false,
335 | "commentConversionModeActivated", false
336 | ))))
337 | .andExpectAll(
338 | status().isOk(),
339 | header().string(CONTENT_TYPE, APPLICATION_JSON_VALUE),
340 | jsonPath("$.status").value("SUCCESS"),
341 | jsonPath("$.content").isArray(),
342 | jsonPath("$.content.length()").value(1),
343 | jsonPath("$.content.[0].filename").value("%s.0.%s".formatted(prefix, extension)),
344 | jsonPath("$.content.[0].content").value(new Match(new String[]{
345 | "%s text_001 = document.createTextNode(` `);".formatted(keyword),
346 | "%s div_001 = document.createElement('div');".formatted(keyword),
347 | "div_001.setAttribute(`contenteditable`, `true`);",
348 | "%s text_002 = document.createTextNode(`%s`);".formatted(keyword, content),
349 | "div_001.appendChild(text_002);"
350 | })));
351 |
352 | }
353 |
354 | }
355 | }
356 |
357 |
358 | @ParameterizedTest
359 | @MethodSource("provideVariableDeclarationsAndQuerySelectorAdded")
360 | void convertUploadedFilesContent(final VariableDeclaration variableDeclaration, final boolean querySelectorAdded) throws Exception {
361 | final var keyword = keyword(variableDeclaration);
362 | final var extension = randomUUID().toString();
363 | final var prefix = randomUUID().toString();
364 |
365 | if (querySelectorAdded) {
366 |
367 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files")
368 | .file(new MockMultipartFile(
369 | "options", "config.json", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(of(
370 | "pattern", "%s.{{ index }}{{}}{{ extension }}".formatted(prefix),
371 | "variableDeclaration", variableDeclaration,
372 | "extension", ".%s".formatted(extension),
373 | "querySelectorAdded", true,
374 | "commentConversionModeActivated", true
375 | )).getBytes(UTF_8)))
376 | .file(new MockMultipartFile(
377 | "files", SAMPLE_INPUT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT)))
378 | .file(new MockMultipartFile(
379 | "files", SAMPLE_INPUT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT))))
380 | .andExpectAll(
381 | status().isOk(),
382 | withMultipart().size(2),
383 | withMultipart().nth(0).exists(),
384 | withMultipart().nth(1).exists(),
385 | withMultipart().nth(0)
386 | .map(JsGeneratorApiTest::toArray)
387 | .passContent(lines -> assertThat(lines).containsExactly(
388 | toArray(fileContent(SAMPLE_OUTPUT_QUERY_SELECTOR_ADDED).replaceAll("\\{\\{\s*keyword\s*}}", keyword)))),
389 | content().contentTypeCompatibleWith(MULTIPART_FORM_DATA),
390 | header().string(CONTENT_TYPE, matchesPattern("^%s;boundary=.*$".formatted(MULTIPART_FORM_DATA_VALUE))));
391 |
392 |
393 | } else {
394 |
395 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files")
396 | .file(new MockMultipartFile(
397 | "options", "config.json", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(of(
398 | "pattern", "%s.{{ index }}{{}}{{ extension }}".formatted(prefix),
399 | "variableDeclaration", variableDeclaration,
400 | "extension", ".%s".formatted(extension),
401 | "querySelectorAdded", false,
402 | "commentConversionModeActivated", true
403 | )).getBytes(UTF_8)))
404 | .file(new MockMultipartFile(
405 | "files", SAMPLE_INPUT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT)))
406 | .file(new MockMultipartFile(
407 | "files", SAMPLE_INPUT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT))))
408 | .andExpectAll(
409 | status().isOk(),
410 | withMultipart().size(2),
411 | withMultipart().nth(0).exists(),
412 | withMultipart().nth(1).exists(),
413 | withMultipart().nth(0)
414 | .map(JsGeneratorApiTest::toArray)
415 | .passContent(lines -> assertThat(lines).containsExactly(
416 | toArray(fileContent(SAMPLE_OUTPUT_QUERY_SELECTOR_NOT_ADDED).replaceAll("\\{\\{\s*keyword\s*}}", keyword)))),
417 | content().contentTypeCompatibleWith(MULTIPART_FORM_DATA),
418 | header().string(CONTENT_TYPE, matchesPattern("^%s;boundary=.*$".formatted(MULTIPART_FORM_DATA_VALUE))));
419 |
420 |
421 | }
422 |
423 | }
424 |
425 | @Disabled("Encoding Issue here with '©', we will solve that")
426 | @ParameterizedTest
427 | @MethodSource("provideVariableDeclarationsAndQuerySelectorAddedAndCommentConversionModeActivated")
428 | void convertUploadedFilesContentWithComment(final VariableDeclaration variableDeclaration, final boolean querySelectorAdded, final boolean commentConversionModeActivated) throws Exception {
429 | final var keyword = keyword(variableDeclaration);
430 | final var extension = randomUUID().toString();
431 | final var prefix = randomUUID().toString();
432 |
433 | if (querySelectorAdded) {
434 |
435 | if (commentConversionModeActivated) {
436 |
437 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files")
438 | .file(new MockMultipartFile(
439 | "options", "config.json", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(of(
440 | "pattern", "%s.{{ index }}{{}}{{ extension }}".formatted(prefix),
441 | "variableDeclaration", variableDeclaration,
442 | "extension", ".%s".formatted(extension),
443 | "querySelectorAdded", true,
444 | "commentConversionModeActivated", true
445 | )).getBytes(UTF_8)))
446 | .file(new MockMultipartFile(
447 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT)))
448 | .file(new MockMultipartFile(
449 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT))))
450 | .andExpectAll(
451 | status().isOk(),
452 | withMultipart().size(2),
453 | withMultipart().nth(0).exists(),
454 | withMultipart().nth(1).exists(),
455 | withMultipart().nth(0)
456 | .map(JsGeneratorApiTest::toArray)
457 | .passContent(lines -> assertThat(lines).containsExactly(
458 | toArray(fileContent(SAMPLE_OUTPUT_QUERY_SELECTOR_ADDED_AND_COMMENT_CONVERSION_MODE_ACTIVATED).replaceAll("\\{\\{\s*keyword\s*}}", keyword)))),
459 | content().contentTypeCompatibleWith(MULTIPART_FORM_DATA),
460 | header().string(CONTENT_TYPE, matchesPattern("^%s;boundary=.*$".formatted(MULTIPART_FORM_DATA_VALUE))));
461 |
462 |
463 | } else {
464 |
465 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files")
466 | .file(new MockMultipartFile(
467 | "options", "config.json", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(of(
468 | "pattern", "%s.{{ index }}{{}}{{ extension }}".formatted(prefix),
469 | "variableDeclaration", variableDeclaration,
470 | "extension", ".%s".formatted(extension),
471 | "querySelectorAdded", true,
472 | "commentConversionModeActivated", false
473 | )).getBytes(UTF_8)))
474 | .file(new MockMultipartFile(
475 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT)))
476 | .file(new MockMultipartFile(
477 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT))))
478 | .andExpectAll(
479 | status().isOk(),
480 | withMultipart().size(2),
481 | withMultipart().nth(0).exists(),
482 | withMultipart().nth(1).exists(),
483 | withMultipart().nth(0)
484 | .map(JsGeneratorApiTest::toArray)
485 | .passContent(lines -> assertThat(lines).containsExactly(
486 | toArray(fileContent(SAMPLE_OUTPUT_QUERY_SELECTOR_ADDED_AND_COMMENT_CONVERSION_MODE_NOT_ACTIVATED).replaceAll("\\{\\{\s*keyword\s*}}", keyword)))),
487 | content().contentTypeCompatibleWith(MULTIPART_FORM_DATA),
488 | header().string(CONTENT_TYPE, matchesPattern("^%s;boundary=.*$".formatted(MULTIPART_FORM_DATA_VALUE))));
489 |
490 |
491 | }
492 |
493 | } else {
494 |
495 | if (commentConversionModeActivated) {
496 |
497 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files")
498 | .file(new MockMultipartFile(
499 | "options", "config.json", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(of(
500 | "pattern", "%s.{{ index }}{{}}{{ extension }}".formatted(prefix),
501 | "variableDeclaration", variableDeclaration,
502 | "extension", ".%s".formatted(extension),
503 | "querySelectorAdded", false,
504 | "commentConversionModeActivated", true
505 | )).getBytes(UTF_8)))
506 | .file(new MockMultipartFile(
507 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT)))
508 | .file(new MockMultipartFile(
509 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT))))
510 | .andExpectAll(
511 | status().isOk(),
512 | withMultipart().size(2),
513 | withMultipart().nth(0).exists(),
514 | withMultipart().nth(1).exists(),
515 | withMultipart().nth(0)
516 | .map(JsGeneratorApiTest::toArray)
517 | .passContent(lines -> assertThat(lines).containsExactly(
518 | toArray(fileContent(SAMPLE_OUTPUT_QUERY_SELECTOR_NOT_ADDED_AND_COMMENT_CONVERSION_MODE_ACTIVATED).replaceAll("\\{\\{\s*keyword\s*}}", keyword)))),
519 | content().contentTypeCompatibleWith(MULTIPART_FORM_DATA),
520 | header().string(CONTENT_TYPE, matchesPattern("^%s;boundary=.*$".formatted(MULTIPART_FORM_DATA_VALUE))));
521 |
522 |
523 | } else {
524 |
525 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files")
526 | .file(new MockMultipartFile(
527 | "options", "config.json", APPLICATION_JSON_VALUE, objectMapper.writeValueAsString(of(
528 | "pattern", "%s.{{ index }}{{}}{{ extension }}".formatted(prefix),
529 | "variableDeclaration", variableDeclaration,
530 | "extension", ".%s".formatted(extension),
531 | "querySelectorAdded", false,
532 | "commentConversionModeActivated", false
533 | )).getBytes(UTF_8)))
534 | .file(new MockMultipartFile(
535 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT)))
536 | .file(new MockMultipartFile(
537 | "files", SAMPLE_INPUT_WITH_COMMENT.toString(), MULTIPART_FORM_DATA_VALUE, Files.newInputStream(SAMPLE_INPUT_WITH_COMMENT))))
538 | .andExpectAll(
539 | status().isOk(),
540 | withMultipart().size(2),
541 | withMultipart().nth(0).exists(),
542 | withMultipart().nth(1).exists(),
543 | withMultipart().nth(0)
544 | .map(JsGeneratorApiTest::toArray)
545 | .passContent(lines -> assertThat(lines).containsExactly(
546 | toArray(fileContent(SAMPLE_OUTPUT_QUERY_SELECTOR_NOT_ADDED_AND_COMMENT_CONVERSION_MODE_NOT_ACTIVATED).replaceAll("\\{\\{\s*keyword\s*}}", keyword)))),
547 | content().contentTypeCompatibleWith(MULTIPART_FORM_DATA),
548 | header().string(CONTENT_TYPE, matchesPattern("^%s;boundary=.*$".formatted(MULTIPART_FORM_DATA_VALUE))));
549 |
550 |
551 | }
552 | }
553 | }
554 |
555 |
556 | @Test
557 | void convertBadRequests() throws Exception {
558 | mockMvc.perform(multipart(ConvertController.MAPPING + "/files"))
559 | .andExpectAll(status().isBadRequest());
560 | mockMvc.perform(post(ConvertController.MAPPING)
561 | .header(CONTENT_TYPE, APPLICATION_JSON)
562 | .content(objectMapper.writeValueAsString(of("extension", ".js"))))
563 | .andExpectAll(status().isBadRequest());
564 | }
565 |
566 | private static final class Match extends CustomMatcher {
567 | private final String[] lines;
568 |
569 | public Match(final String[] lines) {
570 | super("jsgenerator-matcher");
571 | this.lines = lines;
572 | }
573 |
574 | @Override
575 | public boolean matches(Object actual) {
576 | assertThat(toArray((String) actual)).containsExactly(lines);
577 | return true;
578 | }
579 | }
580 | }
581 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/java/com/osscameroon/jsgenerator/test/api/helper/MultipartResultMatcher.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.test.api.helper;
2 |
3 | import org.springframework.test.web.servlet.ResultMatcher;
4 |
5 | import java.util.function.BiConsumer;
6 | import java.util.function.Function;
7 |
8 | import static java.util.List.of;
9 | import static org.assertj.core.api.Assertions.assertThat;
10 | import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
11 |
12 | public class MultipartResultMatcher {
13 | private static final MultipartResultMatcher INSTANCE = new MultipartResultMatcher();
14 |
15 | private MultipartResultMatcher() {
16 | }
17 |
18 | public IndexedMultipart nth(final int index) {
19 | return new IndexedMultipart(index);
20 | }
21 |
22 | public ResultMatcher size(final int size) {
23 | return match((getHeader, content) -> {
24 | final var partsCount = parts(getHeader, content).length;
25 |
26 | assertThat(size).withFailMessage(() ->
27 | "Multipart got %s parts: expected %d parts".formatted(partsCount, size)
28 | ).isEqualTo(partsCount);
29 | });
30 | }
31 |
32 | public static MultipartResultMatcher multipart() {
33 | return INSTANCE;
34 | }
35 |
36 | public static MultipartResultMatcher withMultipart() {
37 | return multipart();
38 | }
39 |
40 | static String[] parts(Function getHeader, String content) {
41 | final var parts = content.split(boundary(getHeader));
42 | return of(parts).subList(1, parts.length - 1).toArray(new String[0]);
43 | }
44 |
45 | static String boundary(Function getHeader) {
46 | return "--" + getHeader.apply(CONTENT_TYPE).split("boundary=")[1];
47 | }
48 |
49 | static ResultMatcher match(final BiConsumer, String> biConsumer) {
50 | return result -> {
51 | final var response = result.getResponse();
52 | biConsumer.accept(response::getHeader, response.getContentAsString());
53 | };
54 | }
55 |
56 | public record IndexedMultipart(int index) {
57 | public ResultMatcher exists() {
58 | return match((getHeader, content) -> {
59 | final var partsCount = parts(getHeader, content).length;
60 |
61 | assertThat(index).withFailMessage(() ->
62 | "Multipart nth(%s) does not exist: only got %d parts".formatted(index, partsCount)
63 | ).isGreaterThanOrEqualTo(0).isLessThan(partsCount);
64 | });
65 | }
66 |
67 | public IndexedMappedMultipart map(final Function mapper) {
68 | return new IndexedMappedMultipart<>(mapper, index);
69 | }
70 | }
71 |
72 | public record IndexedMappedMultipart(Function mapper, int index) {
73 |
74 | public ResultMatcher passContent(final AssertionConsumer consumer) {
75 | return match((getHeader, content) -> {
76 | final var parts = parts(getHeader, content);
77 |
78 | assertThat(index).withFailMessage(() ->
79 | "Multipart nth(%s) cannot pass: only got %d parts".formatted(index, parts.length)
80 | ).isGreaterThanOrEqualTo(0).isLessThan(parts.length);
81 |
82 | final var part = parts[index].split("(\r?\n){2}", 2)[1];
83 | final var mappedPart = mapper.apply(part);
84 |
85 | try {
86 | consumer.accept(mappedPart);
87 | } catch (Exception e) {
88 | throw new RuntimeException(e);
89 | }
90 | });
91 | }
92 | }
93 |
94 | public interface AssertionConsumer {
95 | void accept(final T value) throws Exception;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.test.api {
2 | requires com.osscameroon.jsgenerator.api;
3 | requires com.osscameroon.jsgenerator.core;
4 |
5 | requires com.fasterxml.jackson.databind;
6 | requires org.hamcrest;
7 | requires org.junit.jupiter.params;
8 | requires spring.beans;
9 | requires spring.boot.autoconfigure;
10 | requires spring.boot.test;
11 | requires spring.context;
12 | requires spring.core;
13 | requires spring.security.test;
14 | requires spring.test;
15 | requires spring.web;
16 |
17 | requires org.assertj.core;
18 | requires org.junit.jupiter.api;
19 | requires org.junitpioneer;
20 |
21 | requires org.apache.tomcat.embed.core;
22 |
23 | exports com.osscameroon.jsgenerator.test.api to spring.beans, spring.context;
24 |
25 | opens com.osscameroon.jsgenerator.test.api to org.junit.platform.commons, spring.core;
26 | }
27 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/htmlFilesInput/sample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Sample
7 |
8 |
9 |
10 |
11 |
15 |
16 |
Main
17 |
This is the main content.
18 |
19 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/htmlFilesInput/sampleWithComment.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Sample
7 |
8 |
9 |
10 |
11 |
16 |
17 |
Main
18 |
This is the main content.
19 |
20 |
21 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/jsFilesOutput/querySelectorAdded/commentConversionModeActivated/sample.js:
--------------------------------------------------------------------------------
1 | {{keyword}} targetElement_001 = document.querySelector(`:root > body`);
2 | {{keyword}} html_001 = document.createElement('html');
3 | {{keyword}} text_001 = document.createTextNode(` `);
4 | html_001.appendChild(text_001);
5 | {{keyword}} head_001 = document.createElement('head');
6 | {{keyword}} text_002 = document.createTextNode(` `);
7 | head_001.appendChild(text_002);
8 | {{keyword}} meta_001 = document.createElement('meta');
9 | meta_001.setAttribute(`charset`, `utf-8`);
10 | head_001.appendChild(meta_001);
11 | {{keyword}} text_003 = document.createTextNode(` `);
12 | head_001.appendChild(text_003);
13 | {{keyword}} title_001 = document.createElement('title');
14 | {{keyword}} text_004 = document.createTextNode(`Sample`);
15 | title_001.appendChild(text_004);
16 | head_001.appendChild(title_001);
17 | {{keyword}} text_005 = document.createTextNode(` `);
18 | head_001.appendChild(text_005);
19 | {{keyword}} link_001 = document.createElement('link');
20 | link_001.setAttribute(`rel`, `stylesheet`);
21 | link_001.setAttribute(`href`, ``);
22 | head_001.appendChild(link_001);
23 | {{keyword}} text_006 = document.createTextNode(` `);
24 | head_001.appendChild(text_006);
25 | html_001.appendChild(head_001);
26 | {{keyword}} text_007 = document.createTextNode(` `);
27 | html_001.appendChild(text_007);
28 | {{keyword}} body_001 = document.createElement('body');
29 | {{keyword}} text_008 = document.createTextNode(` `);
30 | body_001.appendChild(text_008);
31 | {{keyword}} div_001 = document.createElement('div');
32 | div_001.setAttribute(`id`, `container`);
33 | {{keyword}} text_009 = document.createTextNode(` `);
34 | div_001.appendChild(text_009);
35 | {{keyword}} div_002 = document.createElement('div');
36 | div_002.setAttribute(`id`, `header`);
37 | {{keyword}} text_010 = document.createTextNode(` `);
38 | div_002.appendChild(text_010);
39 | {{keyword}} comment_001 = document.createComment(` Sample H1 `);
40 | div_002.appendChild(comment_001);
41 | {{keyword}} text_011 = document.createTextNode(` `);
42 | div_002.appendChild(text_011);
43 | {{keyword}} h1_001 = document.createElement('h1');
44 | {{keyword}} text_012 = document.createTextNode(`Sample`);
45 | h1_001.appendChild(text_012);
46 | div_002.appendChild(h1_001);
47 | {{keyword}} text_013 = document.createTextNode(` `);
48 | div_002.appendChild(text_013);
49 | {{keyword}} img_001 = document.createElement('img');
50 | img_001.setAttribute(`src`, `kanye.jpg`);
51 | img_001.setAttribute(`alt`, `kanye`);
52 | div_002.appendChild(img_001);
53 | {{keyword}} text_014 = document.createTextNode(` `);
54 | div_002.appendChild(text_014);
55 | div_001.appendChild(div_002);
56 | {{keyword}} text_015 = document.createTextNode(` `);
57 | div_001.appendChild(text_015);
58 | {{keyword}} div_003 = document.createElement('div');
59 | div_003.setAttribute(`id`, `main`);
60 | {{keyword}} text_016 = document.createTextNode(` `);
61 | div_003.appendChild(text_016);
62 | {{keyword}} h2_001 = document.createElement('h2');
63 | {{keyword}} text_017 = document.createTextNode(`Main`);
64 | h2_001.appendChild(text_017);
65 | div_003.appendChild(h2_001);
66 | {{keyword}} text_018 = document.createTextNode(` `);
67 | div_003.appendChild(text_018);
68 | {{keyword}} p_001 = document.createElement('p');
69 | {{keyword}} text_019 = document.createTextNode(`This is the main content.`);
70 | p_001.appendChild(text_019);
71 | div_003.appendChild(p_001);
72 | {{keyword}} text_020 = document.createTextNode(` `);
73 | div_003.appendChild(text_020);
74 | {{keyword}} img_002 = document.createElement('img');
75 | img_002.setAttribute(`src`, ``);
76 | img_002.setAttribute(`alt`, ``);
77 | div_003.appendChild(img_002);
78 | {{keyword}} text_021 = document.createTextNode(` `);
79 | div_003.appendChild(text_021);
80 | div_001.appendChild(div_003);
81 | {{keyword}} text_022 = document.createTextNode(` `);
82 | div_001.appendChild(text_022);
83 | {{keyword}} div_004 = document.createElement('div');
84 | div_004.setAttribute(`id`, `footer`);
85 | {{keyword}} text_023 = document.createTextNode(` `);
86 | div_004.appendChild(text_023);
87 | {{keyword}} comment_002 = document.createComment(` Copyright `);
88 | div_004.appendChild(comment_002);
89 | {{keyword}} text_024 = document.createTextNode(` `);
90 | div_004.appendChild(text_024);
91 | {{keyword}} p_002 = document.createElement('p');
92 | {{keyword}} text_025 = document.createTextNode(`Copyright © 2019`);
93 | p_002.appendChild(text_025);
94 | div_004.appendChild(p_002);
95 | {{keyword}} text_026 = document.createTextNode(` `);
96 | div_004.appendChild(text_026);
97 | div_001.appendChild(div_004);
98 | {{keyword}} text_027 = document.createTextNode(` `);
99 | div_001.appendChild(text_027);
100 | body_001.appendChild(div_001);
101 | {{keyword}} text_028 = document.createTextNode(` `);
102 | body_001.appendChild(text_028);
103 | html_001.appendChild(body_001);
104 | targetElement_001.appendChild(html_001);
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/jsFilesOutput/querySelectorAdded/commentConversionModeNotActivated/sample.js:
--------------------------------------------------------------------------------
1 | {{keyword}} targetElement_001 = document.querySelector(`:root > body`);
2 | {{keyword}} html_001 = document.createElement('html');
3 | {{keyword}} text_001 = document.createTextNode(` `);
4 | html_001.appendChild(text_001);
5 | {{keyword}} head_001 = document.createElement('head');
6 | {{keyword}} text_002 = document.createTextNode(` `);
7 | head_001.appendChild(text_002);
8 | {{keyword}} meta_001 = document.createElement('meta');
9 | meta_001.setAttribute(`charset`, `utf-8`);
10 | head_001.appendChild(meta_001);
11 | {{keyword}} text_003 = document.createTextNode(` `);
12 | head_001.appendChild(text_003);
13 | {{keyword}} title_001 = document.createElement('title');
14 | {{keyword}} text_004 = document.createTextNode(`Sample`);
15 | title_001.appendChild(text_004);
16 | head_001.appendChild(title_001);
17 | {{keyword}} text_005 = document.createTextNode(` `);
18 | head_001.appendChild(text_005);
19 | {{keyword}} link_001 = document.createElement('link');
20 | link_001.setAttribute(`rel`, `stylesheet`);
21 | link_001.setAttribute(`href`, ``);
22 | head_001.appendChild(link_001);
23 | {{keyword}} text_006 = document.createTextNode(` `);
24 | head_001.appendChild(text_006);
25 | html_001.appendChild(head_001);
26 | {{keyword}} text_007 = document.createTextNode(` `);
27 | html_001.appendChild(text_007);
28 | {{keyword}} body_001 = document.createElement('body');
29 | {{keyword}} text_008 = document.createTextNode(` `);
30 | body_001.appendChild(text_008);
31 | {{keyword}} div_001 = document.createElement('div');
32 | div_001.setAttribute(`id`, `container`);
33 | {{keyword}} text_009 = document.createTextNode(` `);
34 | div_001.appendChild(text_009);
35 | {{keyword}} div_002 = document.createElement('div');
36 | div_002.setAttribute(`id`, `header`);
37 | {{keyword}} text_010 = document.createTextNode(` `);
38 | div_002.appendChild(text_010);
39 | {{keyword}} text_011 = document.createTextNode(` `);
40 | div_002.appendChild(text_011);
41 | {{keyword}} h1_001 = document.createElement('h1');
42 | {{keyword}} text_012 = document.createTextNode(`Sample`);
43 | h1_001.appendChild(text_012);
44 | div_002.appendChild(h1_001);
45 | {{keyword}} text_013 = document.createTextNode(` `);
46 | div_002.appendChild(text_013);
47 | {{keyword}} img_001 = document.createElement('img');
48 | img_001.setAttribute(`src`, `kanye.jpg`);
49 | img_001.setAttribute(`alt`, `kanye`);
50 | div_002.appendChild(img_001);
51 | {{keyword}} text_014 = document.createTextNode(` `);
52 | div_002.appendChild(text_014);
53 | div_001.appendChild(div_002);
54 | {{keyword}} text_015 = document.createTextNode(` `);
55 | div_001.appendChild(text_015);
56 | {{keyword}} div_003 = document.createElement('div');
57 | div_003.setAttribute(`id`, `main`);
58 | {{keyword}} text_016 = document.createTextNode(` `);
59 | div_003.appendChild(text_016);
60 | {{keyword}} h2_001 = document.createElement('h2');
61 | {{keyword}} text_017 = document.createTextNode(`Main`);
62 | h2_001.appendChild(text_017);
63 | div_003.appendChild(h2_001);
64 | {{keyword}} text_018 = document.createTextNode(` `);
65 | div_003.appendChild(text_018);
66 | {{keyword}} p_001 = document.createElement('p');
67 | {{keyword}} text_019 = document.createTextNode(`This is the main content.`);
68 | p_001.appendChild(text_019);
69 | div_003.appendChild(p_001);
70 | {{keyword}} text_020 = document.createTextNode(` `);
71 | div_003.appendChild(text_020);
72 | {{keyword}} img_002 = document.createElement('img');
73 | img_002.setAttribute(`src`, ``);
74 | img_002.setAttribute(`alt`, ``);
75 | div_003.appendChild(img_002);
76 | {{keyword}} text_021 = document.createTextNode(` `);
77 | div_003.appendChild(text_021);
78 | div_001.appendChild(div_003);
79 | {{keyword}} text_022 = document.createTextNode(` `);
80 | div_001.appendChild(text_022);
81 | {{keyword}} div_004 = document.createElement('div');
82 | div_004.setAttribute(`id`, `footer`);
83 | {{keyword}} text_023 = document.createTextNode(` `);
84 | div_004.appendChild(text_023);
85 | {{keyword}} text_024 = document.createTextNode(` `);
86 | div_004.appendChild(text_024);
87 | {{keyword}} p_002 = document.createElement('p');
88 | {{keyword}} text_025 = document.createTextNode(`Copyright © 2019`);
89 | p_002.appendChild(text_025);
90 | div_004.appendChild(p_002);
91 | {{keyword}} text_026 = document.createTextNode(` `);
92 | div_004.appendChild(text_026);
93 | div_001.appendChild(div_004);
94 | {{keyword}} text_027 = document.createTextNode(` `);
95 | div_001.appendChild(text_027);
96 | body_001.appendChild(div_001);
97 | {{keyword}} text_028 = document.createTextNode(` `);
98 | body_001.appendChild(text_028);
99 | html_001.appendChild(body_001);
100 | targetElement_001.appendChild(html_001);
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/jsFilesOutput/querySelectorAdded/sample.js:
--------------------------------------------------------------------------------
1 | {{keyword}} targetElement_001 = document.querySelector(`:root > body`);
2 | {{keyword}} html_001 = document.createElement('html');
3 | {{keyword}} text_001 = document.createTextNode(` `);
4 | html_001.appendChild(text_001);
5 | {{keyword}} head_001 = document.createElement('head');
6 | {{keyword}} text_002 = document.createTextNode(` `);
7 | head_001.appendChild(text_002);
8 | {{keyword}} meta_001 = document.createElement('meta');
9 | meta_001.setAttribute(`charset`, `utf-8`);
10 | head_001.appendChild(meta_001);
11 | {{keyword}} text_003 = document.createTextNode(` `);
12 | head_001.appendChild(text_003);
13 | {{keyword}} title_001 = document.createElement('title');
14 | {{keyword}} text_004 = document.createTextNode(`Sample`);
15 | title_001.appendChild(text_004);
16 | head_001.appendChild(title_001);
17 | {{keyword}} text_005 = document.createTextNode(` `);
18 | head_001.appendChild(text_005);
19 | {{keyword}} link_001 = document.createElement('link');
20 | link_001.setAttribute(`rel`, `stylesheet`);
21 | link_001.setAttribute(`href`, ``);
22 | head_001.appendChild(link_001);
23 | {{keyword}} text_006 = document.createTextNode(` `);
24 | head_001.appendChild(text_006);
25 | html_001.appendChild(head_001);
26 | {{keyword}} text_007 = document.createTextNode(` `);
27 | html_001.appendChild(text_007);
28 | {{keyword}} body_001 = document.createElement('body');
29 | {{keyword}} text_008 = document.createTextNode(` `);
30 | body_001.appendChild(text_008);
31 | {{keyword}} div_001 = document.createElement('div');
32 | div_001.setAttribute(`id`, `container`);
33 | {{keyword}} text_009 = document.createTextNode(` `);
34 | div_001.appendChild(text_009);
35 | {{keyword}} div_002 = document.createElement('div');
36 | div_002.setAttribute(`id`, `header`);
37 | {{keyword}} text_010 = document.createTextNode(` `);
38 | div_002.appendChild(text_010);
39 | {{keyword}} h1_001 = document.createElement('h1');
40 | {{keyword}} text_011 = document.createTextNode(`Sample`);
41 | h1_001.appendChild(text_011);
42 | div_002.appendChild(h1_001);
43 | {{keyword}} text_012 = document.createTextNode(` `);
44 | div_002.appendChild(text_012);
45 | {{keyword}} img_001 = document.createElement('img');
46 | img_001.setAttribute(`src`, `kanye.jpg`);
47 | img_001.setAttribute(`alt`, `kanye`);
48 | div_002.appendChild(img_001);
49 | {{keyword}} text_013 = document.createTextNode(` `);
50 | div_002.appendChild(text_013);
51 | div_001.appendChild(div_002);
52 | {{keyword}} text_014 = document.createTextNode(` `);
53 | div_001.appendChild(text_014);
54 | {{keyword}} div_003 = document.createElement('div');
55 | div_003.setAttribute(`id`, `main`);
56 | {{keyword}} text_015 = document.createTextNode(` `);
57 | div_003.appendChild(text_015);
58 | {{keyword}} h2_001 = document.createElement('h2');
59 | {{keyword}} text_016 = document.createTextNode(`Main`);
60 | h2_001.appendChild(text_016);
61 | div_003.appendChild(h2_001);
62 | {{keyword}} text_017 = document.createTextNode(` `);
63 | div_003.appendChild(text_017);
64 | {{keyword}} p_001 = document.createElement('p');
65 | {{keyword}} text_018 = document.createTextNode(`This is the main content.`);
66 | p_001.appendChild(text_018);
67 | div_003.appendChild(p_001);
68 | {{keyword}} text_019 = document.createTextNode(` `);
69 | div_003.appendChild(text_019);
70 | {{keyword}} img_002 = document.createElement('img');
71 | img_002.setAttribute(`src`, ``);
72 | img_002.setAttribute(`alt`, ``);
73 | div_003.appendChild(img_002);
74 | {{keyword}} text_020 = document.createTextNode(` `);
75 | div_003.appendChild(text_020);
76 | div_001.appendChild(div_003);
77 | {{keyword}} text_021 = document.createTextNode(` `);
78 | div_001.appendChild(text_021);
79 | {{keyword}} div_004 = document.createElement('div');
80 | div_004.setAttribute(`id`, `footer`);
81 | {{keyword}} text_022 = document.createTextNode(` `);
82 | div_004.appendChild(text_022);
83 | {{keyword}} p_002 = document.createElement('p');
84 | {{keyword}} text_023 = document.createTextNode(`Copyright - 2019`);
85 | p_002.appendChild(text_023);
86 | div_004.appendChild(p_002);
87 | {{keyword}} text_024 = document.createTextNode(` `);
88 | div_004.appendChild(text_024);
89 | div_001.appendChild(div_004);
90 | {{keyword}} text_025 = document.createTextNode(` `);
91 | div_001.appendChild(text_025);
92 | body_001.appendChild(div_001);
93 | {{keyword}} text_026 = document.createTextNode(` `);
94 | body_001.appendChild(text_026);
95 | html_001.appendChild(body_001);
96 | targetElement_001.appendChild(html_001);
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/jsFilesOutput/querySelectorNotAdded/commentConversionModeActivated/sample.js:
--------------------------------------------------------------------------------
1 | {{keyword}} html_001 = document.createElement('html');
2 | {{keyword}} text_001 = document.createTextNode(` `);
3 | html_001.appendChild(text_001);
4 | {{keyword}} head_001 = document.createElement('head');
5 | {{keyword}} text_002 = document.createTextNode(` `);
6 | head_001.appendChild(text_002);
7 | {{keyword}} meta_001 = document.createElement('meta');
8 | meta_001.setAttribute(`charset`, `utf-8`);
9 | head_001.appendChild(meta_001);
10 | {{keyword}} text_003 = document.createTextNode(` `);
11 | head_001.appendChild(text_003);
12 | {{keyword}} title_001 = document.createElement('title');
13 | {{keyword}} text_004 = document.createTextNode(`Sample`);
14 | title_001.appendChild(text_004);
15 | head_001.appendChild(title_001);
16 | {{keyword}} text_005 = document.createTextNode(` `);
17 | head_001.appendChild(text_005);
18 | {{keyword}} link_001 = document.createElement('link');
19 | link_001.setAttribute(`rel`, `stylesheet`);
20 | link_001.setAttribute(`href`, ``);
21 | head_001.appendChild(link_001);
22 | {{keyword}} text_006 = document.createTextNode(` `);
23 | head_001.appendChild(text_006);
24 | html_001.appendChild(head_001);
25 | {{keyword}} text_007 = document.createTextNode(` `);
26 | html_001.appendChild(text_007);
27 | {{keyword}} body_001 = document.createElement('body');
28 | {{keyword}} text_008 = document.createTextNode(` `);
29 | body_001.appendChild(text_008);
30 | {{keyword}} div_001 = document.createElement('div');
31 | div_001.setAttribute(`id`, `container`);
32 | {{keyword}} text_009 = document.createTextNode(` `);
33 | div_001.appendChild(text_009);
34 | {{keyword}} div_002 = document.createElement('div');
35 | div_002.setAttribute(`id`, `header`);
36 | {{keyword}} text_010 = document.createTextNode(` `);
37 | div_002.appendChild(text_010);
38 | {{keyword}} comment_001 = document.createComment(` Sample H1 `);
39 | div_002.appendChild(comment_001);
40 | {{keyword}} text_011 = document.createTextNode(` `);
41 | div_002.appendChild(text_011);
42 | {{keyword}} h1_001 = document.createElement('h1');
43 | {{keyword}} text_012 = document.createTextNode(`Sample`);
44 | h1_001.appendChild(text_012);
45 | div_002.appendChild(h1_001);
46 | {{keyword}} text_013 = document.createTextNode(` `);
47 | div_002.appendChild(text_013);
48 | {{keyword}} img_001 = document.createElement('img');
49 | img_001.setAttribute(`src`, `kanye.jpg`);
50 | img_001.setAttribute(`alt`, `kanye`);
51 | div_002.appendChild(img_001);
52 | {{keyword}} text_014 = document.createTextNode(` `);
53 | div_002.appendChild(text_014);
54 | div_001.appendChild(div_002);
55 | {{keyword}} text_015 = document.createTextNode(` `);
56 | div_001.appendChild(text_015);
57 | {{keyword}} div_003 = document.createElement('div');
58 | div_003.setAttribute(`id`, `main`);
59 | {{keyword}} text_016 = document.createTextNode(` `);
60 | div_003.appendChild(text_016);
61 | {{keyword}} h2_001 = document.createElement('h2');
62 | {{keyword}} text_017 = document.createTextNode(`Main`);
63 | h2_001.appendChild(text_017);
64 | div_003.appendChild(h2_001);
65 | {{keyword}} text_018 = document.createTextNode(` `);
66 | div_003.appendChild(text_018);
67 | {{keyword}} p_001 = document.createElement('p');
68 | {{keyword}} text_019 = document.createTextNode(`This is the main content.`);
69 | p_001.appendChild(text_019);
70 | div_003.appendChild(p_001);
71 | {{keyword}} text_020 = document.createTextNode(` `);
72 | div_003.appendChild(text_020);
73 | {{keyword}} img_002 = document.createElement('img');
74 | img_002.setAttribute(`src`, ``);
75 | img_002.setAttribute(`alt`, ``);
76 | div_003.appendChild(img_002);
77 | {{keyword}} text_021 = document.createTextNode(` `);
78 | div_003.appendChild(text_021);
79 | div_001.appendChild(div_003);
80 | {{keyword}} text_022 = document.createTextNode(` `);
81 | div_001.appendChild(text_022);
82 | {{keyword}} div_004 = document.createElement('div');
83 | div_004.setAttribute(`id`, `footer`);
84 | {{keyword}} text_023 = document.createTextNode(` `);
85 | div_004.appendChild(text_023);
86 | {{keyword}} comment_002 = document.createComment(` Copyright `);
87 | div_004.appendChild(comment_002);
88 | {{keyword}} text_024 = document.createTextNode(` `);
89 | div_004.appendChild(text_024);
90 | {{keyword}} p_002 = document.createElement('p');
91 | {{keyword}} text_025 = document.createTextNode(`Copyright © 2019`);
92 | p_002.appendChild(text_025);
93 | div_004.appendChild(p_002);
94 | {{keyword}} text_026 = document.createTextNode(` `);
95 | div_004.appendChild(text_026);
96 | div_001.appendChild(div_004);
97 | {{keyword}} text_027 = document.createTextNode(` `);
98 | div_001.appendChild(text_027);
99 | body_001.appendChild(div_001);
100 | {{keyword}} text_028 = document.createTextNode(` `);
101 | body_001.appendChild(text_028);
102 | html_001.appendChild(body_001);
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/jsFilesOutput/querySelectorNotAdded/commentConversionModeNotActivated/sample.js:
--------------------------------------------------------------------------------
1 | {{keyword}} html_001 = document.createElement('html');
2 | {{keyword}} text_001 = document.createTextNode(` `);
3 | html_001.appendChild(text_001);
4 | {{keyword}} head_001 = document.createElement('head');
5 | {{keyword}} text_002 = document.createTextNode(` `);
6 | head_001.appendChild(text_002);
7 | {{keyword}} meta_001 = document.createElement('meta');
8 | meta_001.setAttribute(`charset`, `utf-8`);
9 | head_001.appendChild(meta_001);
10 | {{keyword}} text_003 = document.createTextNode(` `);
11 | head_001.appendChild(text_003);
12 | {{keyword}} title_001 = document.createElement('title');
13 | {{keyword}} text_004 = document.createTextNode(`Sample`);
14 | title_001.appendChild(text_004);
15 | head_001.appendChild(title_001);
16 | {{keyword}} text_005 = document.createTextNode(` `);
17 | head_001.appendChild(text_005);
18 | {{keyword}} link_001 = document.createElement('link');
19 | link_001.setAttribute(`rel`, `stylesheet`);
20 | link_001.setAttribute(`href`, ``);
21 | head_001.appendChild(link_001);
22 | {{keyword}} text_006 = document.createTextNode(` `);
23 | head_001.appendChild(text_006);
24 | html_001.appendChild(head_001);
25 | {{keyword}} text_007 = document.createTextNode(` `);
26 | html_001.appendChild(text_007);
27 | {{keyword}} body_001 = document.createElement('body');
28 | {{keyword}} text_008 = document.createTextNode(` `);
29 | body_001.appendChild(text_008);
30 | {{keyword}} div_001 = document.createElement('div');
31 | div_001.setAttribute(`id`, `container`);
32 | {{keyword}} text_009 = document.createTextNode(` `);
33 | div_001.appendChild(text_009);
34 | {{keyword}} div_002 = document.createElement('div');
35 | div_002.setAttribute(`id`, `header`);
36 | {{keyword}} text_010 = document.createTextNode(` `);
37 | div_002.appendChild(text_010);
38 | {{keyword}} text_011 = document.createTextNode(` `);
39 | div_002.appendChild(text_011);
40 | {{keyword}} h1_001 = document.createElement('h1');
41 | {{keyword}} text_012 = document.createTextNode(`Sample`);
42 | h1_001.appendChild(text_012);
43 | div_002.appendChild(h1_001);
44 | {{keyword}} text_013 = document.createTextNode(` `);
45 | div_002.appendChild(text_013);
46 | {{keyword}} img_001 = document.createElement('img');
47 | img_001.setAttribute(`src`, `kanye.jpg`);
48 | img_001.setAttribute(`alt`, `kanye`);
49 | div_002.appendChild(img_001);
50 | {{keyword}} text_014 = document.createTextNode(` `);
51 | div_002.appendChild(text_014);
52 | div_001.appendChild(div_002);
53 | {{keyword}} text_015 = document.createTextNode(` `);
54 | div_001.appendChild(text_015);
55 | {{keyword}} div_003 = document.createElement('div');
56 | div_003.setAttribute(`id`, `main`);
57 | {{keyword}} text_016 = document.createTextNode(` `);
58 | div_003.appendChild(text_016);
59 | {{keyword}} h2_001 = document.createElement('h2');
60 | {{keyword}} text_017 = document.createTextNode(`Main`);
61 | h2_001.appendChild(text_017);
62 | div_003.appendChild(h2_001);
63 | {{keyword}} text_018 = document.createTextNode(` `);
64 | div_003.appendChild(text_018);
65 | {{keyword}} p_001 = document.createElement('p');
66 | {{keyword}} text_019 = document.createTextNode(`This is the main content.`);
67 | p_001.appendChild(text_019);
68 | div_003.appendChild(p_001);
69 | {{keyword}} text_020 = document.createTextNode(` `);
70 | div_003.appendChild(text_020);
71 | {{keyword}} img_002 = document.createElement('img');
72 | img_002.setAttribute(`src`, ``);
73 | img_002.setAttribute(`alt`, ``);
74 | div_003.appendChild(img_002);
75 | {{keyword}} text_021 = document.createTextNode(` `);
76 | div_003.appendChild(text_021);
77 | div_001.appendChild(div_003);
78 | {{keyword}} text_022 = document.createTextNode(` `);
79 | div_001.appendChild(text_022);
80 | {{keyword}} div_004 = document.createElement('div');
81 | div_004.setAttribute(`id`, `footer`);
82 | {{keyword}} text_023 = document.createTextNode(` `);
83 | div_004.appendChild(text_023);
84 | {{keyword}} text_024 = document.createTextNode(` `);
85 | div_004.appendChild(text_024);
86 | {{keyword}} p_002 = document.createElement('p');
87 | {{keyword}} text_025 = document.createTextNode(`Copyright © 2019`);
88 | p_002.appendChild(text_025);
89 | div_004.appendChild(p_002);
90 | {{keyword}} text_026 = document.createTextNode(` `);
91 | div_004.appendChild(text_026);
92 | div_001.appendChild(div_004);
93 | {{keyword}} text_027 = document.createTextNode(` `);
94 | div_001.appendChild(text_027);
95 | body_001.appendChild(div_001);
96 | {{keyword}} text_028 = document.createTextNode(` `);
97 | body_001.appendChild(text_028);
98 | html_001.appendChild(body_001);
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-api/src/test/resources/jsFilesOutput/querySelectorNotAdded/sample.js:
--------------------------------------------------------------------------------
1 | {{keyword}} html_001 = document.createElement('html');
2 | {{keyword}} text_001 = document.createTextNode(` `);
3 | html_001.appendChild(text_001);
4 | {{keyword}} head_001 = document.createElement('head');
5 | {{keyword}} text_002 = document.createTextNode(` `);
6 | head_001.appendChild(text_002);
7 | {{keyword}} meta_001 = document.createElement('meta');
8 | meta_001.setAttribute(`charset`, `utf-8`);
9 | head_001.appendChild(meta_001);
10 | {{keyword}} text_003 = document.createTextNode(` `);
11 | head_001.appendChild(text_003);
12 | {{keyword}} title_001 = document.createElement('title');
13 | {{keyword}} text_004 = document.createTextNode(`Sample`);
14 | title_001.appendChild(text_004);
15 | head_001.appendChild(title_001);
16 | {{keyword}} text_005 = document.createTextNode(` `);
17 | head_001.appendChild(text_005);
18 | {{keyword}} link_001 = document.createElement('link');
19 | link_001.setAttribute(`rel`, `stylesheet`);
20 | link_001.setAttribute(`href`, ``);
21 | head_001.appendChild(link_001);
22 | {{keyword}} text_006 = document.createTextNode(` `);
23 | head_001.appendChild(text_006);
24 | html_001.appendChild(head_001);
25 | {{keyword}} text_007 = document.createTextNode(` `);
26 | html_001.appendChild(text_007);
27 | {{keyword}} body_001 = document.createElement('body');
28 | {{keyword}} text_008 = document.createTextNode(` `);
29 | body_001.appendChild(text_008);
30 | {{keyword}} div_001 = document.createElement('div');
31 | div_001.setAttribute(`id`, `container`);
32 | {{keyword}} text_009 = document.createTextNode(` `);
33 | div_001.appendChild(text_009);
34 | {{keyword}} div_002 = document.createElement('div');
35 | div_002.setAttribute(`id`, `header`);
36 | {{keyword}} text_010 = document.createTextNode(` `);
37 | div_002.appendChild(text_010);
38 | {{keyword}} h1_001 = document.createElement('h1');
39 | {{keyword}} text_011 = document.createTextNode(`Sample`);
40 | h1_001.appendChild(text_011);
41 | div_002.appendChild(h1_001);
42 | {{keyword}} text_012 = document.createTextNode(` `);
43 | div_002.appendChild(text_012);
44 | {{keyword}} img_001 = document.createElement('img');
45 | img_001.setAttribute(`src`, `kanye.jpg`);
46 | img_001.setAttribute(`alt`, `kanye`);
47 | div_002.appendChild(img_001);
48 | {{keyword}} text_013 = document.createTextNode(` `);
49 | div_002.appendChild(text_013);
50 | div_001.appendChild(div_002);
51 | {{keyword}} text_014 = document.createTextNode(` `);
52 | div_001.appendChild(text_014);
53 | {{keyword}} div_003 = document.createElement('div');
54 | div_003.setAttribute(`id`, `main`);
55 | {{keyword}} text_015 = document.createTextNode(` `);
56 | div_003.appendChild(text_015);
57 | {{keyword}} h2_001 = document.createElement('h2');
58 | {{keyword}} text_016 = document.createTextNode(`Main`);
59 | h2_001.appendChild(text_016);
60 | div_003.appendChild(h2_001);
61 | {{keyword}} text_017 = document.createTextNode(` `);
62 | div_003.appendChild(text_017);
63 | {{keyword}} p_001 = document.createElement('p');
64 | {{keyword}} text_018 = document.createTextNode(`This is the main content.`);
65 | p_001.appendChild(text_018);
66 | div_003.appendChild(p_001);
67 | {{keyword}} text_019 = document.createTextNode(` `);
68 | div_003.appendChild(text_019);
69 | {{keyword}} img_002 = document.createElement('img');
70 | img_002.setAttribute(`src`, ``);
71 | img_002.setAttribute(`alt`, ``);
72 | div_003.appendChild(img_002);
73 | {{keyword}} text_020 = document.createTextNode(` `);
74 | div_003.appendChild(text_020);
75 | div_001.appendChild(div_003);
76 | {{keyword}} text_021 = document.createTextNode(` `);
77 | div_001.appendChild(text_021);
78 | {{keyword}} div_004 = document.createElement('div');
79 | div_004.setAttribute(`id`, `footer`);
80 | {{keyword}} text_022 = document.createTextNode(` `);
81 | div_004.appendChild(text_022);
82 | {{keyword}} p_002 = document.createElement('p');
83 | {{keyword}} text_023 = document.createTextNode(`Copyright - 2019`);
84 | p_002.appendChild(text_023);
85 | div_004.appendChild(p_002);
86 | {{keyword}} text_024 = document.createTextNode(` `);
87 | div_004.appendChild(text_024);
88 | div_001.appendChild(div_004);
89 | {{keyword}} text_025 = document.createTextNode(` `);
90 | div_001.appendChild(text_025);
91 | body_001.appendChild(div_001);
92 | {{keyword}} text_026 = document.createTextNode(` `);
93 | body_001.appendChild(text_026);
94 | html_001.appendChild(body_001);
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator-test
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-test-core
13 |
14 |
15 |
16 | com.osscameroon
17 | jsgenerator-core
18 |
19 |
20 | org.junit-pioneer
21 | junit-pioneer
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-core/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | open module com.osscameroon.jsgenerator.test.core.noop {
2 | }
3 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-core/src/test/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.test.core {
2 | exports com.osscameroon.jsgenerator.test.core;
3 | opens com.osscameroon.jsgenerator.test.core;
4 |
5 | requires com.osscameroon.jsgenerator.core;
6 |
7 | requires org.assertj.core;
8 | requires org.junit.jupiter.api;
9 | requires org.mockito;
10 | requires org.mockito.junit.jupiter;
11 | requires org.junit.jupiter.params;
12 |
13 | requires org.slf4j;
14 | requires spring.core;
15 |
16 | requires org.junitpioneer;
17 | }
18 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-core/src/test/resources/htmlFilesInput/sample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Sample
7 |
8 |
9 |
10 |
11 |
15 |
16 |
Main
17 |
This is the main content.
18 |
19 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-core/src/test/resources/htmlFilesInput/sampleWithComment.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Sample
7 |
8 |
9 |
10 |
11 |
16 |
17 |
Main
18 |
This is the main content.
19 |
20 |
21 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-desktop/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator-test
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-test-desktop
13 |
14 |
15 |
16 | com.osscameroon
17 | jsgenerator-core
18 |
19 |
20 | org.junit-pioneer
21 | junit-pioneer
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-desktop/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.test.desktop.noop {
2 | }
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-desktop/src/test/java/com/osscameroon/jsgenerator/test/desktop/JsGeneratorDesktopTest.java:
--------------------------------------------------------------------------------
1 | package com.osscameroon.jsgenerator.test.desktop;
2 |
3 | public class JsGeneratorDesktopTest {
4 | }
5 |
--------------------------------------------------------------------------------
/jsgenerator-test/jsgenerator-test-desktop/src/test/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.osscameroon.jsgenerator.test.desktop {
2 | exports com.osscameroon.jsgenerator.test.desktop;
3 | opens com.osscameroon.jsgenerator.test.desktop;
4 |
5 | requires com.osscameroon.jsgenerator.core;
6 | requires org.junitpioneer;
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/jsgenerator-test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | jsgenerator
7 | com.osscameroon
8 | ${revision}
9 |
10 | 4.0.0
11 |
12 | jsgenerator-test
13 | pom
14 |
15 | jsgenerator-test-core
16 | jsgenerator-test-api
17 | jsgenerator-test-desktop
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-test
24 | test
25 |
26 |
27 | com.vaadin.external.google
28 | android-json
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.osscameroon
5 | jsgenerator
6 | pom
7 | ${revision}
8 |
9 | jsgenerator-core
10 | jsgenerator-test
11 | jsgenerator-slim-cli
12 | jsgenerator-slim-api
13 | jsgenerator-api
14 | jsgenerator-cli
15 | jsgenerator-desktop
16 |
17 |
18 |
19 | spring-boot-starter-parent
20 | org.springframework.boot
21 | 3.3.1
22 |
23 |
24 | jsgenerator
25 | Generates JavaScript from HTML
26 | 2018
27 |
28 | https://github.com/osscameroon/js-generator
29 |
30 |
31 | 21
32 | 21
33 | 21
34 |
35 | 0.0.1-SNAPSHOT
36 |
37 | 4.6.3
38 |
39 | UTF-8
40 | UTF-8
41 |
42 | github
43 | ${env.MAVEN_SITE_GITHUB_OAUTH_TOKEN}
44 | 2.3.0
45 |
46 |
47 |
48 |
49 | org.jsoup
50 | jsoup
51 |
52 |
53 |
54 |
55 |
56 |
57 | org.openjfx
58 | javafx-fxml
59 | 22.0.2
60 |
61 |
62 |
63 | org.springdoc
64 | springdoc-openapi-starter-webmvc-ui
65 | 2.1.0
66 |
67 |
68 | org.jsoup
69 | jsoup
70 | 1.15.3
71 |
72 |
73 | info.picocli
74 | picocli
75 | ${picocli.version}
76 |
77 |
78 |
79 | com.osscameroon
80 | jsgenerator-core
81 | ${revision}
82 |
83 |
84 | com.osscameroon
85 | jsgenerator-slim-api
86 | ${revision}
87 |
88 |
89 | com.osscameroon
90 | jsgenerator-slim-cli
91 | ${revision}
92 |
93 |
94 | org.junit-pioneer
95 | junit-pioneer
96 | ${junitPioneer.version}
97 | test
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | org.apache.maven.plugins
107 | maven-site-plugin
108 | 3.11.0
109 |
110 |
111 | org.apache.maven.plugins
112 | maven-project-info-reports-plugin
113 | 3.0.0
114 |
115 |
116 | com.github.github
117 | site-maven-plugin
118 | 0.12
119 |
120 |
121 | org.jacoco
122 | jacoco-maven-plugin
123 | 0.8.12
124 |
125 |
126 | org.springframework.boot
127 | spring-boot-maven-plugin
128 |
129 | true
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | org.jacoco
138 | jacoco-maven-plugin
139 |
140 |
141 |
142 | prepare-agent
143 |
144 |
145 |
146 | report
147 | test
148 |
149 | report
150 |
151 |
152 |
153 |
154 |
155 |
161 |
162 |
163 | org.apache.maven.plugins
164 | maven-site-plugin
165 |
166 |
167 | com.github.github
168 | site-maven-plugin
169 |
170 | Building site for ${project.name} ${project.version}
171 | github
172 |
173 |
174 |
175 |
176 | site
177 |
178 | site
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | org.apache.maven.plugins
190 | maven-project-info-reports-plugin
191 |
192 |
193 |
194 | index
195 | licenses
196 | dependency-info
197 | ci-management
198 | dependencies
199 | dependency-convergence
200 | dependency-management
201 | distribution-management
202 | help
203 | issue-management
204 | mailing-lists
205 | modules
206 | plugin-management
207 | plugins
208 | team
209 | scm
210 | summary
211 |
212 |
213 |
214 |
215 |
216 | org.apache.maven.plugins
217 | maven-javadoc-plugin
218 | 3.3.2
219 |
220 |
221 |
222 |
223 |
224 |
225 | MIT License
226 | http://www.opensource.org/licenses/mit-license.php
227 | repo
228 |
229 |
230 |
231 |
232 | Open Source Society Cameroon
233 | https://osscameroon.com/
234 |
235 |
236 |
237 |
238 | wisdomnji@gmail.com
239 | Sherlock Wisdom
240 | https://github.com/sherlockwisdom
241 | sherlockwisdom
242 |
243 |
244 | jupsfan@gmail.com
245 | Fanon Jupkwo
246 | https://github.com/FanJups
247 | FanJups
248 |
249 |
250 | elroykanye@gmail.com
251 | Elroy Kanye
252 | https://github.com/elroykanye
253 | elroykanye
254 |
255 |
256 | mofirojean@gmail.com
257 | Jean Mofiro
258 | https://github.com/mofirojean
259 | mofirojean
260 |
261 |
262 | salathielgenese@gmail.com
263 | Salathiel Genese YIMGA YIMGA
264 | https://github.com/SalathielGenese
265 | Salathiel Genese
266 |
267 |
268 |
269 |
270 | GitHub Issues
271 | https://github.com/osscameroon/js-generator/issues
272 |
273 |
274 |
275 | https://github.com/osscameroon/js-generator
276 | scm:git:git@github.com:osscameroon/js-generator.git
277 | scm:git:git@github.com:osscameroon/js-generator.git
278 |
279 |
280 |
--------------------------------------------------------------------------------