├── .github └── workflows │ └── hydrun.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Hydrunfile ├── LICENSE ├── Makefile ├── README.md ├── assets ├── complex.png ├── home.png ├── logo-dark.png └── logo-readme.png ├── cmd ├── html2goapp-cli │ └── main.go └── html2goapp-pwa │ └── main.go ├── example ├── index.go └── index.html ├── go.mod ├── go.sum ├── pkg ├── components │ └── home.go └── converter │ └── goapp.go └── web ├── default.png ├── large.png └── logo.png /.github/workflows/hydrun.yaml: -------------------------------------------------------------------------------- 1 | name: hydrun CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build-linux: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | target: 13 | - id: cli 14 | src: . 15 | os: golang:bullseye 16 | flags: "" 17 | cmd: ./Hydrunfile 18 | dst: out/cli/* 19 | - id: pwa 20 | src: . 21 | os: golang:bullseye 22 | flags: "" 23 | cmd: ./Hydrunfile pwa 24 | dst: out/pwa/* 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v2 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@v1 31 | - name: Set up Docker Buildx 32 | uses: docker/setup-buildx-action@v1 33 | - name: Set up hydrun 34 | run: | 35 | curl -L -o /tmp/hydrun "https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.linux-$(uname -m)" 36 | sudo install /tmp/hydrun /usr/local/bin 37 | - name: Build with hydrun 38 | working-directory: ${{ matrix.target.src }} 39 | run: hydrun -o ${{ matrix.target.os }} ${{ matrix.target.flags }} "${{ matrix.target.cmd }}" 40 | - name: Fix permissions for output 41 | run: sudo chown -R $USER . 42 | - name: Upload output 43 | uses: actions/upload-artifact@v2 44 | with: 45 | name: ${{ matrix.target.id }} 46 | path: ${{ matrix.target.dst }} 47 | publish-linux: 48 | runs-on: ubuntu-latest 49 | needs: build-linux 50 | 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v2 54 | - name: Download output 55 | uses: actions/download-artifact@v2 56 | with: 57 | path: /tmp/out 58 | - name: Isolate the repositories 59 | run: | 60 | mkdir -p /tmp/github-pages 61 | 62 | cp -r /tmp/out/pwa/web/* /tmp/github-pages 63 | - name: Publish pre-release to GitHub releases 64 | if: ${{ github.ref == 'refs/heads/main' }} 65 | uses: marvinpinto/action-automatic-releases@latest 66 | with: 67 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 68 | automatic_release_tag: unstable 69 | prerelease: true 70 | files: | 71 | /tmp/out/*/* 72 | - name: Publish release to GitHub releases 73 | if: startsWith(github.ref, 'refs/tags/v') 74 | uses: marvinpinto/action-automatic-releases@latest 75 | with: 76 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 77 | prerelease: false 78 | files: | 79 | /tmp/out/*/* 80 | - name: Publish release to GitHub pages 81 | if: startsWith(github.ref, 'refs/tags/v') 82 | uses: JamesIves/github-pages-deploy-action@4.1.0 83 | with: 84 | branch: gh-pages 85 | folder: /tmp/github-pages 86 | git-config-name: GitHub Pages Bot 87 | git-config-email: bot@example.com 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | web/*.wasm 2 | out 3 | -------------------------------------------------------------------------------- /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 | felicitas@pojtinger.com. 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 | -------------------------------------------------------------------------------- /Hydrunfile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = "pwa" ]; then 4 | # Build 5 | make -j "$(nproc)" build/pwa 6 | else 7 | # Install native dependencies 8 | apt update 9 | apt install -y curl 10 | 11 | # Install bagop 12 | curl -L -o /tmp/bagop "https://github.com/pojntfx/bagop/releases/latest/download/bagop.linux-$(uname -m)" 13 | install /tmp/bagop /usr/local/bin 14 | 15 | # Build 16 | CGO_ENABLED=0 bagop -j "$(nproc)" -b html2goapp-cli -x '(android/*|ios/*)' -p 'make build/cli DST=$DST' -d out/cli 17 | fi 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Variables 2 | DESTDIR ?= 3 | PREFIX ?= /usr/local 4 | DST ?= out/cli/html2goapp-cli 5 | WWWROOT ?= /var/www/html 6 | WWWPREFIX ?= /html2goapp 7 | 8 | all: build 9 | 10 | # Build 11 | build: build/cli build/pwa 12 | 13 | build/cli: 14 | go build -o $(DST) ./cmd/html2goapp-cli 15 | 16 | build/pwa: 17 | GOARCH=wasm GOOS=js go build -o web/app.wasm ./cmd/html2goapp-pwa 18 | go run ./cmd/html2goapp-pwa -prefix $(WWWPREFIX) 19 | cp -rf web/* out/pwa/web/web 20 | tar -cvzf out/pwa/html2goapp-pwa.tar.gz -C out/pwa/web . 21 | 22 | # Install 23 | install: install/cli install/pwa 24 | 25 | install/cli: 26 | install -D -m 0755 $(DST) $(DESTDIR)$(PREFIX)/bin/html2goapp-cli 27 | 28 | install/pwa: 29 | mkdir -p $(DESTDIR)$(WWWROOT)$(WWWPREFIX) 30 | cp -rf out/pwa/web/* $(DESTDIR)$(WWWROOT)$(WWWPREFIX) 31 | 32 | # Uninstall 33 | uninstall: uninstall/cli uninstall/pwa 34 | 35 | uninstall/cli: 36 | rm $(DESTDIR)$(PREFIX)/bin/html2goapp-cli 37 | 38 | uninstall/pwa: 39 | rm -rf $(DESTDIR)$(WWWROOT)$(WWWPREFIX) 40 | 41 | # Run 42 | run: run/cli run/pwa 43 | 44 | run/cli: build 45 | $(DST) 46 | 47 | run/pwa: 48 | GOARCH=wasm GOOS=js go build -o web/app.wasm ./cmd/html2goapp-pwa 49 | go run ./cmd/html2goapp-pwa -serve 50 | 51 | # Clean 52 | clean: 53 | rm -rf out web/app.wasm 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML to go-app Converter 2 | 3 | ![Logo](./assets/logo-readme.png) 4 | 5 | CLI and web app to convert HTML markup to [go-app.dev](https://go-app.dev/)'s syntax. 6 | 7 | [![hydrun CI](https://github.com/pojntfx/html2goapp/actions/workflows/hydrun.yaml/badge.svg)](https://github.com/pojntfx/html2goapp/actions/workflows/hydrun.yaml) 8 | [![Go Reference](https://pkg.go.dev/badge/github.com/pojntfx/html2goapp.svg)](https://pkg.go.dev/github.com/pojntfx/html2goapp) 9 | [![Matrix](https://img.shields.io/matrix/html2goapp:matrix.org)](https://matrix.to/#/#html2goapp:matrix.org?via=matrix.org) 10 | [![Binary Downloads](https://img.shields.io/github/downloads/pojntfx/html2goapp/total?label=binary%20downloads)](https://github.com/pojntfx/html2goapp/releases) 11 | 12 | ## Installation 13 | 14 | ### CLI 15 | 16 | Static binaries are also available on [GitHub releases](https://github.com/pojntfx/html2goapp/releases). 17 | 18 | You can install them like so: 19 | 20 | ```shell 21 | $ curl -L -o /tmp/html2goapp-cli https://github.com/pojntfx/html2goapp/releases/latest/download/html2goapp-cli.linux-$(uname -m) 22 | $ sudo install /tmp/html2goapp-cli /usr/local/bin 23 | ``` 24 | 25 | ### Web App 26 | 27 | The frontend is also available on [GitHub releases](https://github.com/pojntfx/html2goapp/releases) in the form of a static `.tar.gz` archive; to deploy it, simply upload it to a CDN or copy it to a web server. For most users, this shouldn't be necessary though; thanks to [@maxence-charriere](https://github.com/maxence-charriere)'s [go-app package](https://go-app.dev/), html2goapp is a progressive web app. By simply visiting the [public deployment](https://pojntfx.github.io/html2goapp/) once, it will be available for offline use whenever you need it: 28 | 29 | [](https://pojntfx.github.io/html2goapp/) 30 | 31 | ## Usage 32 | 33 | ### Web App 34 | 35 | To convert HTML to go-app's syntax, simply paste the HTML you want to convert into the `Source Code` input and click on `Convert`. You may set the component name, go-app package import path and target package using the options. 36 | 37 | ### CLI 38 | 39 | You can use the CLI to convert a HTML input file to go-app's syntax like so: 40 | 41 | ```shell 42 | $ html2goapp-cli -component PF4Tabs -src example/index.html -pkg example > example/index.go 43 | ``` 44 | 45 | If you want to use the WebAssembly version of the CLI, use `go_js_wasm_exec`: 46 | 47 | ```shell 48 | $ /usr/local/go/misc/wasm/go_js_wasm_exec out/cli/html2goapp-cli.js-wasm.wasm -component PF4Tabs -src example/index.html -pkg example > example/index.go 49 | ``` 50 | 51 | You can find the example [index.html](./example/index.html) and [index.go](./example/index.go) files in this repository. 52 | 53 | ## Screenshots 54 | 55 | Click on an image to see a larger version. 56 | 57 | 58 | Screenshot of the home screen 59 | 60 | 61 | 62 | Screenshot of a complex conversion 63 | 64 | 65 | ## Reference 66 | 67 | ### Command Line Arguments 68 | 69 | ```shell 70 | $ html2goapp-cli --help 71 | Usage of html2goapp-cli: 72 | -component string 73 | Name of the component to generate (default "MyComponent") 74 | -goAppPkg string 75 | Package to use for go-app (default "github.com/maxence-charriere/go-app/v9/pkg/app") 76 | -pkg string 77 | Package to generate component in (default "components") 78 | -src string 79 | HTML source file to convert (default "index.html") 80 | ``` 81 | 82 | ## Acknowledgements 83 | 84 | - This project would not have been possible were it not for [@maxence-charriere](https://github.com/maxence-charriere)'s [go-app package](https://go-app.dev/); if you enjoy using html2goapp, please donate to him! 85 | - The open source [PatternFly design system](https://www.patternfly.org/v4/) provides the components for the project. 86 | - [dave/jennifer](https://github.com/dave/jennifer) enables this project to generate the Go source code in a simple and declarative way. 87 | - All the rest of the authors who worked on the dependencies used! Thanks a lot! 88 | 89 | ## Contributing 90 | 91 | To contribute, please use the [GitHub flow](https://guides.github.com/introduction/flow/) and follow our [Code of Conduct](./CODE_OF_CONDUCT.md). 92 | 93 | To build and start a development version of html2goapp locally, run the following: 94 | 95 | ```shell 96 | $ git clone https://github.com/pojntfx/html2goapp.git 97 | $ cd html2goapp 98 | $ make run/pwa # To launch the web app 99 | $ make run/cli # To launch the CLI 100 | ``` 101 | 102 | Have any questions or need help? Chat with us [on Matrix](https://matrix.to/#/#html2goapp:matrix.org?via=matrix.org)! 103 | 104 | ## Troubleshooting 105 | 106 | - You get the `failed to execute 'compile' on 'webassembly': incorrect response mime type. expected 'application/wasm'.` error when trying to launch the web app? Please make sure that you have [added the WebAssembly MIME type](https://github.com/WebAssembly/spec/issues/573#issuecomment-824715263) on your webserver. 107 | - Elements are missing in the generated output? Make sure you only have one root HTML element and do not include the `html`, `head` or `body` tags in your input. 108 | 109 | ## License 110 | 111 | html2goapp (c) 2021 Felicitas Pojtinger and contributors 112 | 113 | SPDX-License-Identifier: AGPL-3.0 114 | -------------------------------------------------------------------------------- /assets/complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/assets/complex.png -------------------------------------------------------------------------------- /assets/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/assets/home.png -------------------------------------------------------------------------------- /assets/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/assets/logo-dark.png -------------------------------------------------------------------------------- /assets/logo-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/assets/logo-readme.png -------------------------------------------------------------------------------- /cmd/html2goapp-cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | 9 | "github.com/pojntfx/html2goapp/pkg/converter" 10 | ) 11 | 12 | func main() { 13 | // Parse the flags 14 | src := flag.String("src", "index.html", "HTML source file to convert") 15 | goAppPkg := flag.String("goAppPkg", "github.com/maxence-charriere/go-app/v9/pkg/app", "Package to use for go-app") 16 | pkg := flag.String("pkg", "components", "Package to generate component in") 17 | component := flag.String("component", "MyComponent", "Name of the component to generate") 18 | 19 | flag.Parse() 20 | 21 | // Open the input file 22 | htmlInput, err := ioutil.ReadFile(*src) 23 | if err != nil { 24 | log.Fatal("could not open HTML source file:", err) 25 | } 26 | 27 | // Convert to Go 28 | goOutput, err := converter.ConvertHTMLToComponent( 29 | string(htmlInput), 30 | *goAppPkg, 31 | *pkg, 32 | *component, 33 | ) 34 | if err != nil { 35 | fmt.Print(goOutput) 36 | 37 | log.Fatal("could not convert HTML to component:", err) 38 | } 39 | 40 | // Output the generated Go source 41 | fmt.Print(goOutput) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/html2goapp-pwa/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/maxence-charriere/go-app/v9/pkg/app" 9 | "github.com/pojntfx/html2goapp/pkg/components" 10 | ) 11 | 12 | func main() { 13 | // Client-side code 14 | { 15 | app.Route("/", &components.Home{}) 16 | app.RunWhenOnBrowser() 17 | } 18 | 19 | // Server-/build-side code 20 | 21 | // Parse the flags 22 | serve := flag.Bool("serve", false, "Serve the app instead of building it") 23 | laddr := flag.String("laddr", "0.0.0.0:21255", "Address to listen on when serving the app") 24 | dist := flag.String("dist", "out/pwa/web", "Directory to build the app to") 25 | prefix := flag.String("prefix", "/html2goapp", "Prefix to build the app for") 26 | 27 | flag.Parse() 28 | 29 | // Define the handler 30 | h := &app.Handler{ 31 | Title: "HTML to go-app Converter", 32 | Name: "HTML to go-app Converter", 33 | ShortName: "html2goapp", 34 | Description: "Convert HTML markup to go-app.dev's syntax.", 35 | LoadingLabel: "Convert HTML markup to go-app.dev's syntax.", 36 | Author: "Felicitas Pojtinger", 37 | ThemeColor: "#151515", 38 | BackgroundColor: "#151515", 39 | Icon: app.Icon{ 40 | Default: "/web/default.png", 41 | Large: "/web/large.png", 42 | }, 43 | Keywords: []string{ 44 | "html-converter", 45 | "code-generation", 46 | "go-app", 47 | }, 48 | RawHeaders: []string{ 49 | ``, 50 | ``, 51 | ``, 52 | ``, 53 | }, 54 | Styles: []string{ 55 | "https://unpkg.com/@patternfly/patternfly@4.135.2/patternfly.css", 56 | "https://unpkg.com/@patternfly/patternfly@4.135.2/patternfly-addons.css", 57 | }, 58 | } 59 | 60 | // Serve if specified 61 | if *serve { 62 | log.Println("Listening on", *laddr) 63 | 64 | if err := http.ListenAndServe(*laddr, h); err != nil { 65 | log.Fatal("could not serve:", err) 66 | } 67 | 68 | return 69 | } 70 | 71 | // Build if not specified 72 | h.Resources = app.GitHubPages(*prefix) 73 | 74 | if err := app.GenerateStaticWebsite(*dist, h); err != nil { 75 | log.Fatal("could not build static website:", err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /example/index.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import app "github.com/maxence-charriere/go-app/v9/pkg/app" 4 | 5 | type PF4Tabs struct { 6 | app.Compo 7 | } 8 | 9 | func (c *PF4Tabs) Render() app.UI { 10 | return app.Div(). 11 | Body( 12 | app.Div(). 13 | Class("pf-c-tabs pf-m-fill"). 14 | DataSet("ouia-component-type", "PF4/Tabs"). 15 | DataSet("ouia-safe", true). 16 | DataSet("ouia-component-id", "OUIA-Generated-Tabs-12"). 17 | Body( 18 | app.Button(). 19 | Class("pf-c-tabs__scroll-button"). 20 | Aria("label", "Scroll left"). 21 | Aria("hidden", true). 22 | Disabled(true), 23 | app.Ul(). 24 | Class("pf-c-tabs__list"). 25 | Body( 26 | app.Li(). 27 | Class("pf-c-tabs__item pf-m-current"). 28 | Body( 29 | app.Button(). 30 | DataSet("ouia-component-type", "PF4/TabButton"). 31 | DataSet("ouia-safe", true). 32 | Class("pf-c-tabs__link"). 33 | ID("pf-tab-0-pf-1633041198065oegccuf7ng"). 34 | Aria("controls", "pf-tab-section-0-pf-1633041198065oegccuf7ng"). 35 | Body( 36 | app.Span(). 37 | Class("pf-c-tabs__item-icon"), 38 | app.Span(). 39 | Class("pf-c-tabs__item-text"). 40 | Body( 41 | app.Text("Users"), 42 | ), 43 | ), 44 | ), 45 | app.Li(). 46 | Class("pf-c-tabs__item"). 47 | Body( 48 | app.Button(). 49 | DataSet("ouia-component-type", "PF4/TabButton"). 50 | DataSet("ouia-safe", true). 51 | Class("pf-c-tabs__link"). 52 | ID("pf-tab-1-pf-1633041198065oegccuf7ng"). 53 | Aria("controls", "pf-tab-section-1-pf-1633041198065oegccuf7ng"). 54 | Body( 55 | app.Span(). 56 | Class("pf-c-tabs__item-icon"), 57 | app.Span(). 58 | Class("pf-c-tabs__item-text"). 59 | Body( 60 | app.Text("Containers"), 61 | ), 62 | ), 63 | ), 64 | app.Li(). 65 | Class("pf-c-tabs__item"). 66 | Body( 67 | app.Button(). 68 | DataSet("ouia-component-type", "PF4/TabButton"). 69 | DataSet("ouia-safe", true). 70 | Class("pf-c-tabs__link"). 71 | ID("pf-tab-2-pf-1633041198065oegccuf7ng"). 72 | Aria("controls", "pf-tab-section-2-pf-1633041198065oegccuf7ng"). 73 | Body( 74 | app.Span(). 75 | Class("pf-c-tabs__item-icon"), 76 | app.Span(). 77 | Class("pf-c-tabs__item-text"). 78 | Body( 79 | app.Text("Database"), 80 | ), 81 | ), 82 | ), 83 | ), 84 | app.Button(). 85 | Class("pf-c-tabs__scroll-button"). 86 | Aria("label", "Scroll right"). 87 | Aria("hidden", true). 88 | Disabled(true), 89 | ), 90 | app.Section(). 91 | Class("pf-c-tab-content"). 92 | ID("pf-tab-section-0-pf-1633041198065oegccuf7ng"). 93 | Aria("labelledby", "pf-tab-0-pf-1633041198065oegccuf7ng"). 94 | Aria("role", "tabpanel"). 95 | TabIndex(0). 96 | DataSet("ouia-component-type", "PF4/TabContent"). 97 | DataSet("ouia-safe", true). 98 | Body( 99 | app.Text("Users"), 100 | ), 101 | app.Section(). 102 | Class("pf-c-tab-content"). 103 | ID("pf-tab-section-1-pf-1633041198065oegccuf7ng"). 104 | Aria("labelledby", "pf-tab-1-pf-1633041198065oegccuf7ng"). 105 | Aria("role", "tabpanel"). 106 | TabIndex(0). 107 | DataSet("ouia-component-type", "PF4/TabContent"). 108 | DataSet("ouia-safe", true). 109 | Hidden(true). 110 | Body( 111 | app.Text("Containers"), 112 | ), 113 | app.Section(). 114 | Hidden(true). 115 | Class("pf-c-tab-content"). 116 | ID("pf-tab-section-2-pf-1633041198065oegccuf7ng"). 117 | Aria("labelledby", "pf-tab-2-pf-1633041198065oegccuf7ng"). 118 | Aria("role", "tabpanel"). 119 | TabIndex(0). 120 | DataSet("ouia-component-type", "PF4/TabContent"). 121 | DataSet("ouia-safe", true). 122 | Body( 123 | app.Text("Database"), 124 | ), 125 | app.Div(). 126 | Style("margin-top", " 20px"). 127 | Body( 128 | app.Div(). 129 | Class("pf-c-check"). 130 | Body( 131 | app.Input(). 132 | ID("toggle-box-filled-icon"). 133 | Name("toggle-box-filled-icon"). 134 | Class("pf-c-check__input"). 135 | Type("checkbox"). 136 | Aria("invalid", "false"). 137 | Aria("label", "show box variation checkbox with filled icon tabs"). 138 | DataSet("ouia-component-type", "PF4/Checkbox"). 139 | DataSet("ouia-safe", true). 140 | DataSet("ouia-component-id", "OUIA-Generated-Checkbox-9"), 141 | app.Label(). 142 | Class("pf-c-check__label"). 143 | For("toggle-box-filled-icon"). 144 | Body( 145 | app.Text("isBox"), 146 | ), 147 | ), 148 | ), 149 | ) 150 | } 151 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 |
2 |
8 | 28 |
    29 |
  • 30 | 52 |
  • 53 |
  • 54 | 76 |
  • 77 |
  • 78 | 100 |
  • 101 |
102 | 122 |
123 |
132 | Users 133 |
134 | 146 | 158 |
159 |
160 | 173 |
174 |
175 |
176 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pojntfx/html2goapp 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/dave/jennifer v1.4.1 7 | github.com/maxence-charriere/go-app/v9 v9.0.0 8 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 9 | golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 10 | mvdan.cc/gofumpt v0.1.1-0.20210926212236-00a38e9e18de 11 | ) 12 | 13 | require ( 14 | github.com/google/go-cmp v0.5.4 // indirect 15 | github.com/google/uuid v1.3.0 // indirect 16 | golang.org/x/mod v0.4.0 // indirect 17 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw= 2 | github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= 6 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 7 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 8 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 9 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 10 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 12 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/maxence-charriere/go-app/v9 v9.0.0 h1:+YuEmk+dl2QeVNqoVBGzpN2NM+UOGy4pvxDR/8YCoqQ= 15 | github.com/maxence-charriere/go-app/v9 v9.0.0/go.mod h1:zo0n1kh4OMKn7P+MrTUUi7QwUMU2HOfHsZ293TITtxI= 16 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/rogpeppe/go-internal v1.7.1-0.20210301144926-2630b2f15b04/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 21 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 22 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts= 24 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE= 25 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 26 | golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= 27 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 28 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 29 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 30 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 31 | golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= 32 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 33 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 34 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 35 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 36 | golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 37 | golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6 h1:Z04ewVs7JhXaYkmDhBERPi41gnltfQpMWDnTnQbaCqk= 38 | golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 39 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 49 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 50 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 51 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 52 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 53 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c h1:dS09fXwOFF9cXBnIzZexIuUBj95U1NyQjkEhkgidDow= 54 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 55 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 56 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 57 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 58 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 59 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 60 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 61 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 62 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 63 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 64 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 65 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 66 | mvdan.cc/gofumpt v0.1.1-0.20210926212236-00a38e9e18de h1:O49Yf7ELAsH3dA6OkuBmCSS9NmuUH+MsmX2XEZsavx8= 67 | mvdan.cc/gofumpt v0.1.1-0.20210926212236-00a38e9e18de/go.mod h1:7bzL6tiZRZIiUqleAymdJpugj0fQ5bbf40kDrHWHCVI= 68 | -------------------------------------------------------------------------------- /pkg/components/home.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/maxence-charriere/go-app/v9/pkg/app" 7 | "github.com/pojntfx/html2goapp/pkg/converter" 8 | "github.com/yosssi/gohtml" 9 | ) 10 | 11 | type Home struct { 12 | app.Compo 13 | 14 | input string 15 | 16 | goAppPkg string 17 | pkg string 18 | component string 19 | 20 | output string 21 | } 22 | 23 | func (c *Home) Render() app.UI { 24 | return app.Div(). 25 | Class("pf-c-page"). 26 | Body( 27 | app.A(). 28 | Class("pf-c-skip-to-content pf-c-button pf-m-primary"). 29 | Href("#main"). 30 | Text("Skip to content"), 31 | app.Header(). 32 | Class("pf-c-page__header"). 33 | Body( 34 | app.Div(). 35 | Class("pf-c-page__header-brand"). 36 | Body( 37 | app.A(). 38 | Href("#"). 39 | Class("pf-c-page__header-brand-link"). 40 | Body( 41 | app.Img(). 42 | Class("pf-c-brand"). 43 | Src("/web/logo.png"). 44 | Alt("Logo"), 45 | ), 46 | ), 47 | app.Div(). 48 | Class("pf-c-page__header-tools"). 49 | Body( 50 | app.Div(). 51 | Class("pf-c-page__header-tools-group"). 52 | Body( 53 | app.Div(). 54 | Class("pf-c-page__header-tools-item"). 55 | Body( 56 | app.A(). 57 | Href("https://github.com/pojntfx/html2goapp"). 58 | Target("_blank"). 59 | Class("pf-c-button pf-m-plain"). 60 | Aria("label", "Help"). 61 | Body( 62 | app.I(). 63 | Class("pf-icon pf-icon-help"). 64 | Aria("hidden", true), 65 | ), 66 | ), 67 | ), 68 | ), 69 | ), 70 | app.Main(). 71 | ID("main"). 72 | Class("pf-c-page__main"). 73 | TabIndex(-1). 74 | Body(app.Section(). 75 | Class("pf-c-page__main-section pf-m-fill"). 76 | Body( 77 | app.Div(). 78 | Class("pf-l-grid pf-m-gutter pf-m-all-6-col-on-xl"). 79 | Body( 80 | app.Div(). 81 | Class("pf-l-grid__item"). 82 | Body( 83 | app.Div(). 84 | Class("pf-c-card"). 85 | Body( 86 | app.Div(). 87 | Class("pf-c-card__title"). 88 | Text("Input"), 89 | app.Div(). 90 | Class("pf-c-card__body"). 91 | Body( 92 | app.Form(). 93 | Class("pf-c-form"). 94 | OnSubmit(func(ctx app.Context, e app.Event) { 95 | e.PreventDefault() 96 | 97 | c.convert() 98 | }). 99 | Body( 100 | app.Div(). 101 | Class("pf-c-form__group"). 102 | Body( 103 | app.Div(). 104 | Class("pf-c-form__group-label"). 105 | Body( 106 | app.Label(). 107 | Class("pf-c-form__label"). 108 | For("go-app-pkg-input"). 109 | Body( 110 | app.Span(). 111 | Class("pf-c-form__label-text"). 112 | Text("go-App Package"), 113 | app.Span(). 114 | Class("pf-c-form__label-required"). 115 | Aria("hidden", true). 116 | Text("*"), 117 | ), 118 | ), 119 | app.Div(). 120 | Class("pf-c-form__group-control"). 121 | Body( 122 | app.Input(). 123 | Class("pf-c-form-control"). 124 | Required(true). 125 | OnInput(func(ctx app.Context, e app.Event) { 126 | if input := ctx.JSSrc().Get("value").String(); input != "" { 127 | c.goAppPkg = input 128 | 129 | c.convert() 130 | } 131 | }). 132 | Value(c.goAppPkg). 133 | Type("text"). 134 | ID("go-app-pkg-input"), 135 | ), 136 | ), 137 | app.Div(). 138 | Class("pf-c-form__group"). 139 | Body( 140 | app.Div(). 141 | Class("pf-c-form__group-label"). 142 | Body( 143 | app.Label(). 144 | Class("pf-c-form__label"). 145 | For("component-pkg-input"). 146 | Body( 147 | app.Span(). 148 | Class("pf-c-form__label-text"). 149 | Text("Target Package"), 150 | app.Span(). 151 | Class("pf-c-form__label-required"). 152 | Aria("hidden", true). 153 | Text("*"), 154 | ), 155 | ), 156 | app.Div(). 157 | Class("pf-c-form__group-control"). 158 | Body( 159 | app.Input(). 160 | Class("pf-c-form-control"). 161 | Required(true). 162 | OnInput(func(ctx app.Context, e app.Event) { 163 | if input := ctx.JSSrc().Get("value").String(); input != "" { 164 | c.pkg = input 165 | 166 | c.convert() 167 | } 168 | }). 169 | Value(c.pkg). 170 | Type("text"). 171 | ID("component-pkg-input"), 172 | ), 173 | ), 174 | app.Div(). 175 | Class("pf-c-form__group"). 176 | Body( 177 | app.Div(). 178 | Class("pf-c-form__group-label"). 179 | Body( 180 | app.Label(). 181 | Class("pf-c-form__label"). 182 | For("component-name-input"). 183 | Body( 184 | app.Span(). 185 | Class("pf-c-form__label-text"). 186 | Text("Component Name"), 187 | app.Span(). 188 | Class("pf-c-form__label-required"). 189 | Aria("hidden", true). 190 | Text("*"), 191 | ), 192 | ), 193 | app.Div(). 194 | Class("pf-c-form__group-control"). 195 | Body( 196 | app.Input(). 197 | Class("pf-c-form-control"). 198 | Type("text"). 199 | Required(true). 200 | OnInput(func(ctx app.Context, e app.Event) { 201 | if input := ctx.JSSrc().Get("value").String(); input != "" { 202 | c.component = input 203 | 204 | c.convert() 205 | } 206 | }). 207 | Value(c.component). 208 | ID("component-name-input"), 209 | ), 210 | ), 211 | app.Div(). 212 | Class("pf-c-form__group"). 213 | Body( 214 | app.Div(). 215 | Class("pf-c-form__group-label"). 216 | Body( 217 | app.Label(). 218 | Class("pf-c-form__label"). 219 | For("html-input"). 220 | Body( 221 | app.Span(). 222 | Class("pf-c-form__label-text"). 223 | Text("Source Code"), 224 | app.Span(). 225 | Class("pf-c-form__label-required"). 226 | Aria("hidden", true). 227 | Text("*"), 228 | ), 229 | ), 230 | app.Div(). 231 | Class("pf-c-form__group-control"). 232 | Body( 233 | app.Div(). 234 | Class("pf-c-code-editor"). 235 | Body( 236 | app.Div(). 237 | Class("pf-c-code-editor__header"). 238 | Body( 239 | app.Div(). 240 | Class("pf-c-code-editor__controls"). 241 | Body( 242 | app.Button(). 243 | Class("pf-c-button pf-m-control"). 244 | Type("button"). 245 | Aria("label", "Format"). 246 | OnClick(func(ctx app.Context, e app.Event) { 247 | c.input = gohtml.Format(c.input) 248 | 249 | c.convert() 250 | }). 251 | Body( 252 | app.I(). 253 | Class("fas fa-magic"). 254 | Aria("hidden", true), 255 | ), 256 | ), 257 | app.Div(). 258 | Class("pf-c-code-editor__tab"). 259 | Body( 260 | app.Span(). 261 | Class("pf-c-code-editor__tab-icon"). 262 | Body( 263 | app.I(). 264 | Class("fas fa-code"), 265 | ), 266 | app.Span(). 267 | Class("pf-c-code-editor__tab-text"). 268 | Body( 269 | app.Text("HTML"), 270 | ), 271 | ), 272 | ), 273 | app.Div(). 274 | Class("pf-c-code-editor__main"). 275 | Body( 276 | app.Textarea(). 277 | ID("html-input"). 278 | Placeholder("Enter HTML input here"). 279 | Required(true). 280 | OnInput(func(ctx app.Context, e app.Event) { 281 | c.input = ctx.JSSrc().Get("value").String() 282 | 283 | c.convert() 284 | }). 285 | Style("width", "100%"). 286 | Style("resize", "vertical"). 287 | Style("border", "0"). 288 | Class("pf-c-form-control"). 289 | Rows(25). 290 | Text(c.input), 291 | ), 292 | ), 293 | ), 294 | ), 295 | app.Div(). 296 | Class("pf-c-form__group"). 297 | Body( 298 | app.Div(). 299 | Class("pf-c-form__group-control"). 300 | Body( 301 | app.Div(). 302 | Class("pf-c-form__actions"). 303 | Body( 304 | app.Button(). 305 | Class("pf-c-button pf-m-primary"). 306 | Type("submit"). 307 | Text("Convert to Go"), 308 | ), 309 | ), 310 | ), 311 | ), 312 | ), 313 | ), 314 | ), 315 | app.Div(). 316 | Class("pf-l-grid__item"). 317 | Body( 318 | app.Div(). 319 | Class("pf-c-card"). 320 | Body( 321 | app.Div(). 322 | Class("pf-c-card__title"). 323 | Text("Output"), 324 | app.Div(). 325 | Class("pf-c-card__body"). 326 | Body( 327 | app.Div(). 328 | Class("pf-c-code-editor pf-m-read-only"). 329 | Body( 330 | app.Div(). 331 | Class("pf-c-code-editor__header"). 332 | Body( 333 | app.Div(). 334 | Class("pf-c-code-editor__tab"). 335 | Body( 336 | app.Span(). 337 | Class("pf-c-code-editor__tab-icon"). 338 | Body( 339 | app.I().Class("fas fa-code"), 340 | ), 341 | app.Span(). 342 | Class("pf-c-code-editor__tab-text").Text("Go"), 343 | ), 344 | ), 345 | app.Div(). 346 | Class("pf-c-code-editor__main"). 347 | Body( 348 | app.Textarea(). 349 | Placeholder("go-app's syntax will be here"). 350 | ReadOnly(true). 351 | Style("width", "100%"). 352 | Style("resize", "vertical"). 353 | Style("border", "0"). 354 | Class("pf-c-form-control"). 355 | Rows(25). 356 | Text(c.output), 357 | ), 358 | ), 359 | ), 360 | ), 361 | ), 362 | ), 363 | ), 364 | ), 365 | ) 366 | } 367 | 368 | func (c *Home) OnMount(app.Context) { 369 | c.goAppPkg = "github.com/maxence-charriere/go-app/v9/pkg/app" 370 | c.pkg = "components" 371 | c.component = "MyComponent" 372 | } 373 | 374 | func (c *Home) OnAppUpdate(ctx app.Context) { 375 | if ctx.AppUpdateAvailable() { 376 | ctx.Reload() 377 | } 378 | } 379 | 380 | func (c *Home) convert() { 381 | if c.input == "" { 382 | c.output = "" 383 | 384 | return 385 | } 386 | 387 | generated, err := converter.ConvertHTMLToComponent( 388 | c.input, 389 | c.goAppPkg, 390 | c.pkg, 391 | c.component, 392 | ) 393 | if err != nil { 394 | generated += err.Error() 395 | 396 | log.Println("could not convert HTML to component:", err) 397 | } 398 | 399 | c.output = generated 400 | } 401 | -------------------------------------------------------------------------------- /pkg/converter/goapp.go: -------------------------------------------------------------------------------- 1 | package converter 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | 9 | . "github.com/dave/jennifer/jen" 10 | "github.com/yosssi/gohtml" 11 | "golang.org/x/net/html" 12 | "mvdan.cc/gofumpt/format" 13 | ) 14 | 15 | func convertHTMLToStatements(doc *html.Node, goAppPkg string) (*Statement, error) { 16 | var crawler func(node *html.Node, nthChild int) (*Statement, error) 17 | crawler = func(node *html.Node, nthChild int) (*Statement, error) { 18 | el := Null() 19 | 20 | if node == nil { 21 | return el, nil 22 | } 23 | 24 | if node.Type == html.TextNode && strings.TrimSpace(node.Data) != "" { 25 | // Handle text node 26 | el = Qual(goAppPkg, "Text").Call(Lit(strings.TrimSpace(node.Data))) 27 | if nthChild > 1 { 28 | el = Line().Qual(goAppPkg, "Text").Call(Lit(strings.TrimSpace(node.Data))) 29 | } 30 | } else if node.Type == html.ElementNode && node.DataAtom.String() != "" { 31 | // Handle complex node 32 | el = Qual(goAppPkg, formatTag(node.DataAtom.String())).Call() 33 | if nthChild > 1 { 34 | el = Line().Qual(goAppPkg, formatTag(node.DataAtom.String())).Call() 35 | } 36 | 37 | for _, attr := range node.Attr { 38 | // Attributes to ignore 39 | if attr.Key == "gutter" || attr.Key == "onload" || attr.Key == "onclick" || attr.Key == "loading" || attr.Key == "itemscope" || attr.Key == "itemtype" || attr.Key == "itemprop" || attr.Key == "scoped" { 40 | continue 41 | } 42 | 43 | // Handle empty attributes 44 | var val interface{} 45 | val = attr.Val 46 | if val == "" { 47 | val = true 48 | } 49 | 50 | // Handle `aria-*` and `data-*` attributes 51 | key := formatKey(attr.Key) 52 | if strings.Contains(key, "-") { 53 | parts := strings.Split(key, "-") 54 | 55 | key = formatKey(formatTag(parts[0])) 56 | 57 | el.Dot("").Line().Id(key) 58 | 59 | if val == "true" { 60 | el.Call(Lit(strings.Join(parts[1:], "-")), Lit(true)) 61 | } else { 62 | el.Call(Lit(strings.Join(parts[1:], "-")), Lit(val)) 63 | } 64 | 65 | val = nil 66 | } else { 67 | key = formatKey(formatTag(attr.Key)) 68 | 69 | el.Dot("").Line().Id(key) 70 | } 71 | 72 | if key == "TabIndex" { 73 | // Parse ints for `TabIndex` 74 | v, err := strconv.Atoi(fmt.Sprintf("%v", val)) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | el.Call(Lit(v)) 80 | } else if key == "Style" { 81 | // Convert string representation of CSS in the style tag to multiple calls 82 | 83 | styleParts := strings.Split(fmt.Sprintf("%v", val), ":") 84 | 85 | // style="" or invalid CSS 86 | if val == true || len(styleParts) <= 1 || len(styleParts)%2 != 0 { 87 | el.Call(Lit(""), Lit("")) 88 | } else { 89 | for i, key := range styleParts { 90 | if i%2 == 0 { 91 | if i == 0 { 92 | el.Call(Lit(key), Lit(styleParts[i+1])) 93 | } else { 94 | el.Dot("").Line().Id("Style").Call(Lit(key), Lit(styleParts[i+1])) 95 | } 96 | } 97 | } 98 | } 99 | } else if key == "AutoComplete" { 100 | // Parse booleans for `AutoComplete` 101 | if val == "off" { 102 | el.Call(Lit(false)) 103 | } else { 104 | el.Call(Lit(true)) 105 | } 106 | } else if key == "Spellcheck" { 107 | // Parse booleans for `Spellcheck` 108 | if val == "true" { 109 | el.Call(Lit(true)) 110 | } else { 111 | el.Call(Lit(false)) 112 | } 113 | } else if key == "CrossOrigin" || key == "Title" { 114 | // Convert boolean to strings for attributes 115 | if val == true { 116 | el.Call(Lit("true")) 117 | } else { 118 | el.Call(Lit("false")) 119 | } 120 | } else if key == "Class" { 121 | // Handle empty `Class` attributes 122 | if val == true { 123 | el.Call(Lit("")) 124 | } else { 125 | el.Call(Lit(val)) 126 | } 127 | } else if key == "Width" || key == "Height" || key == "Rows" || key == "Cols" { 128 | // Parse ints for `Width` and `Height` 129 | v, err := strconv.Atoi(strings.Trim(fmt.Sprintf("%v", val), "px")) 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | el.Call(Lit(v)) 135 | } else if val != nil { 136 | el.Call(Lit(val)) 137 | } 138 | } 139 | } 140 | 141 | children := []Code{} 142 | i := 0 143 | for child := node.FirstChild; child != nil; child = child.NextSibling { 144 | // Tags to ignore 145 | // TODO: Render SVGs using `app.Raw` 146 | if child.DataAtom.String() != "svg" { 147 | child, err := crawler(child, i) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | children = append(children, child) 153 | } 154 | 155 | i++ 156 | } 157 | 158 | if len(children) > 0 { 159 | // Add to the body if children are not empty 160 | if len(children) >= 2 && fmt.Sprintf("%#v", children[1]) == "" { 161 | return el, nil 162 | } 163 | 164 | el.Dot("").Line().Id("Body").Call(Line().List(append(children, Line())...)) 165 | } 166 | 167 | return el, nil 168 | } 169 | 170 | return crawler(doc, 0) 171 | } 172 | 173 | func formatTag(tag string) string { 174 | if tag == "noscript" { 175 | return "NoScript" 176 | } 177 | 178 | return strings.Join(strings.Fields(strings.Title(strings.ReplaceAll(tag, "-", " "))), "") 179 | } 180 | 181 | func formatKey(key string) string { 182 | if key == "Id" { 183 | return "ID" 184 | } 185 | 186 | if key == "Tabindex" { 187 | return "TabIndex" 188 | } 189 | 190 | if key == "role" { 191 | return "aria-role" 192 | } 193 | 194 | if key == "Data" { 195 | return "DataSet" 196 | } 197 | 198 | if key == "Autocomplete" { 199 | return "AutoComplete" 200 | } 201 | 202 | if key == "Crossorigin" { 203 | return "CrossOrigin" 204 | } 205 | 206 | if key == "Srcset" { 207 | return "SrcSet" 208 | } 209 | 210 | if key == "Datetime" { 211 | return "DateTime" 212 | } 213 | 214 | if key == "Tbody" { 215 | return "TBody" 216 | } 217 | 218 | if key == "Fieldset" { 219 | return "FieldSet" 220 | } 221 | 222 | return key 223 | } 224 | 225 | // ConvertHTMLToComponent converts HTML markup to go-app's syntax 226 | func ConvertHTMLToComponent( 227 | htmlInput, 228 | goAppPkg, 229 | componentPkg, 230 | componentName string, 231 | ) (string, error) { 232 | // Parse HTML input 233 | root, err := html.Parse(strings.NewReader(gohtml.Format(htmlInput))) 234 | if err != nil { 235 | return "", err 236 | } 237 | 238 | statements, err := convertHTMLToStatements(root.FirstChild.LastChild.FirstChild, goAppPkg) 239 | if err != nil { 240 | return "", err 241 | } 242 | 243 | // Create package 244 | src := NewFile(componentPkg) 245 | 246 | // Component Struct 247 | src.Type().Id(componentName). 248 | Struct( 249 | Qual(goAppPkg, "Compo"), 250 | ) 251 | 252 | // Render function 253 | src.Func(). 254 | Params( 255 | Id("c").Id("*" + componentName), 256 | ). 257 | Id("Render"). 258 | Params(). 259 | Params(Qual(goAppPkg, "UI")). 260 | // Generated statements 261 | Block(Return(statements)) 262 | 263 | // Format source code 264 | if err := os.Setenv("GOFUMPT_SPLIT_LONG_LINES", "on"); err != nil { 265 | return "", err 266 | } 267 | 268 | out, err := format.Source([]byte(fmt.Sprintf("%#v", src)), format.Options{}) 269 | if err != nil { 270 | return fmt.Sprintf("%#v", src), err 271 | } 272 | 273 | return string(out), nil 274 | } 275 | -------------------------------------------------------------------------------- /web/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/web/default.png -------------------------------------------------------------------------------- /web/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/web/large.png -------------------------------------------------------------------------------- /web/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pojntfx/html2goapp/970c29558293172723deb140b8b35427e43a33ec/web/logo.png --------------------------------------------------------------------------------