├── .drone.yml
├── .gitattributes
├── .gitignore
├── .jvmopts
├── .travis.yml
├── LICENSE
├── README.md
├── build.sbt
├── build.toml
├── build211.toml
├── build212.toml
├── build213.toml
├── cache
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLAreaElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLBRElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLBaseElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLBodyElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLButtonElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLContentElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLDListElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLDataListElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLDetailsElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLDialogElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLDivElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLEmbedElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLFormElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLHRElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLHeadElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLHeadingElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLHtmlElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLIFrameElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLImageElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLInputElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLLIElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLLabelElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLLinkElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLMenuItemElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLMetaElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLMeterElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLModElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLOListElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLObjectElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLOptGroupElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLOutputElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLParagraphElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLParamElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLPreElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLProgressElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLShadowElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLSpanElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLStyleElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLTableCaptionElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLTableColElement.html
├── https___developer.mozilla.org_en-US_docs_Web_API_HTMLTemplateElement.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_a.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_acronym.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_address.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_applet.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_area.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_article.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_audio.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_b.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_base.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_basefont.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_big.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_blink.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_body.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_br.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_button.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_canvas.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_caption.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_center.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_code.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_col.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_colgroup.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_content.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_datalist.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_dd.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_decorator.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_del.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_details.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_dialog.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_dir.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_div.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_dl.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_dt.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_element.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_em.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_embed.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_fieldset.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_figcaption.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_figure.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_footer.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_form.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_frame.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_frameset.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h1.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h2.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h3.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h4.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h5.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h6.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_head.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_header.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_hgroup.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_hr.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_html.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_i.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_iframe.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_img.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_input.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_ins.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_isindex.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_keygen.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_label.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_legend.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_li.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_link.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_listing.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_main.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_map.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_menu.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_menuitem.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_meta.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_meter.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_nav.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_noembed.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_noscript.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_object.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_ol.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_optgroup.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_option.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_output.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_p.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_param.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_plaintext.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_pre.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_progress.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_script.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_section.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_select.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_shadow.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_small.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_source.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_spacer.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_span.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_strike.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_strong.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_style.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_summary.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_table.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_tbody.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_td.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_template.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_textarea.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_tfoot.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_th.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_thead.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_title.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_tr.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_track.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_tt.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_ul.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_video.html
├── https___developer.mozilla.org_en-US_docs_Web_HTML_Element_xmp.html
└── https___developer.mozilla.org_en-US_docs_Web_HTML_Global_attributes.html
├── docs
├── 1-tree-construction.md
├── 2-tree-updates.md
├── 3-web-development.md
├── 4-development.md
└── 5-manual.md
├── manual.toml
├── project
├── MDNParser.scala
├── build.properties
├── build.sbt
└── plugins.sbt
├── src
├── bench
│ ├── js
│ │ └── pine
│ │ │ └── bench
│ │ │ ├── PlatformBench.scala
│ │ │ └── PlatformUtil.scala
│ ├── jvm
│ │ └── pine
│ │ │ └── bench
│ │ │ ├── PlatformBench.scala
│ │ │ └── PlatformUtil.scala
│ └── shared
│ │ └── pine
│ │ └── bench
│ │ ├── Bench.scala
│ │ ├── BenchUtil.scala
│ │ ├── SharedBench.scala
│ │ └── TreeBench.scala
├── main
│ ├── scala-js
│ │ └── pine
│ │ │ └── dom
│ │ │ ├── DOM.scala
│ │ │ ├── DiffRender.scala
│ │ │ ├── Document.scala
│ │ │ ├── Event.scala
│ │ │ ├── Implicits.scala
│ │ │ ├── Js.scala
│ │ │ ├── NodeRender.scala
│ │ │ ├── Window.scala
│ │ │ └── package.scala
│ ├── scala-jvm
│ │ └── pine
│ │ │ └── GenerateEntities.scala
│ └── scala
│ │ └── pine
│ │ ├── Attribute.scala
│ │ ├── Diff.scala
│ │ ├── DiffRender.scala
│ │ ├── HtmlEntities.scala
│ │ ├── HtmlHelpers.scala
│ │ ├── Node.scala
│ │ ├── ParseError.scala
│ │ ├── Parser.scala
│ │ ├── Reader.scala
│ │ ├── RenderContext.scala
│ │ ├── TagRef.scala
│ │ ├── TagRender.scala
│ │ ├── XmlEntities.scala
│ │ ├── dsl
│ │ ├── Display.scala
│ │ └── Imports.scala
│ │ ├── macros
│ │ ├── ExternalHtml.scala
│ │ ├── Helpers.scala
│ │ └── InlineHtml.scala
│ │ ├── package.scala
│ │ └── tag
│ │ ├── Attributes.scala
│ │ └── package.scala
└── test
│ ├── html
│ ├── list.html
│ ├── test.html
│ └── test2.html
│ ├── scala-js
│ └── pine
│ │ └── dom
│ │ ├── DOMSpec.scala
│ │ └── TagRefSpec.scala
│ └── scala
│ └── pine
│ ├── BindingsSpec.scala
│ ├── DiffSpec.scala
│ ├── ExternalHtmlSpec.scala
│ ├── HtmlHelpersSpec.scala
│ ├── HtmlParserSpec.scala
│ ├── ImplicitsSpec.scala
│ ├── InlineHtmlSpec.scala
│ ├── NodePropSpec.scala
│ ├── NodeSpec.scala
│ ├── TagRefSpec.scala
│ ├── TextSpec.scala
│ └── XmlParserSpec.scala
└── version.sbt
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | name: default
3 | steps:
4 | - name: build
5 | image: tindzk/seed:0.1.7
6 | commands:
7 | - apk add --no-cache yarn
8 | - yarn add jsdom object-sizeof
9 | # Patch Bloop version to support Scala.js 1.0
10 | - apk add --no-cache curl && curl -L https://github.com/scalacenter/bloop/releases/download/v1.4.0-RC1/install.py | python - -d /usr/bin/ -v 1.4.0-RC1-229-b7c15aa9 || true
11 | - blp-server &
12 | - seed --build=build211.toml bloop
13 | - bloop compile pine-native
14 | - bloop test pine-jvm
15 | - sleep 5 # Synchronise analysis.bin files, otherwise rm might fail
16 | - rm -rf .bloop build
17 | - seed --build=build212.toml bloop
18 | - bloop test pine-jvm pine-js
19 | - sleep 5
20 | - rm -rf .bloop build
21 | - seed --build=build213.toml bloop
22 | - bloop test pine-jvm pine-js
23 | - bloop run pine-bench-jvm -- fast
24 | - bloop run pine-bench-js -- fast
25 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | cache/* -diff
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | .cache/
6 | .history/
7 | .lib/
8 | dist/*
9 | target/
10 | lib_managed/
11 | src_managed/
12 | project/boot/
13 | project/plugins/project/
14 |
15 | # Scala-IDE specific
16 | .scala_dependencies
17 | .worksheet
18 | .idea
19 |
20 | node_modules/
21 | .bloop/
22 | .metals/
23 | *.DS_Store
24 | /package-lock.json
25 | /build/
26 | /seed
27 |
--------------------------------------------------------------------------------
/.jvmopts:
--------------------------------------------------------------------------------
1 | -Xms768m
2 | -Xmx1536m
3 | -Xss2m
4 | -XX:+UseG1GC
5 | -XX:+CMSClassUnloadingEnabled
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | scala:
3 | - 2.11.11-bin-typelevel-4
4 | jdk:
5 | - openjdk8
6 |
7 | # See https://github.com/scala-js/scala-js/issues/2642
8 | install:
9 | - . $HOME/.nvm/nvm.sh
10 | - nvm install stable
11 | - nvm use stable
12 | - npm install
13 | - npm install jsdom
14 | - curl https://raw.githubusercontent.com/scala-native/scala-native/master/scripts/travis_setup.sh | bash -x
15 |
16 | # See https://github.com/scala-js/scala-js/issues/2642
17 | script:
18 | sbt 'set parallelExecution in ThisBuild := false' pineJVM2_11/test pineNative2_11/test pineJS2_12/test pineJVM2_12/test pineJS2_13/test pineJVM2_13/test
19 |
20 | # From http://www.scala-sbt.org/0.13/docs/Travis-CI-with-sbt.html
21 | # Use container-based infrastructure
22 | sudo: false
23 | # These directories are cached to S3 at the end of the build
24 | cache:
25 | directories:
26 | - $HOME/.ivy2/cache
27 | - $HOME/.sbt
28 | before_cache:
29 | # Cleanup the cached directories to avoid unnecessary cache updates
30 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
31 | - find $HOME/.sbt -name "*.lock" -print -delete
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2019 sparse.tech OÜ
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 | [](https://travis-ci.org/sparsetech/pine)
3 | [](http://ci.sparse.tech/sparsetech/pine)
4 | [](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22tech.sparse%22%20AND%20a%3A%22pine_2.12%22)
5 |
6 | Pine is a functional HTML5 and XML library for the Scala platform. It supports parsing, manipulating and rendering of HTML. Pine provides type-safe bindings for HTML5 generated from [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element). Tree nodes are immutable and can be constructed from compile-time or runtime HTML/XML content. The tree may be manipulated and rendered back as HTML or as a browser node.
7 |
8 | ## Features
9 | * Immutable and typed trees
10 | * Type-safe bindings, generated from MDN
11 | * Support for custom elements and attributes
12 | * HTML5 and XML parser based on recursive descent
13 | * Macros for compile-time HTML string/file parsing
14 | * Tree manipulation
15 | * Rendering as HTML strings
16 | * DSL for common operations
17 | * Unit and property-based tests
18 | * Few dependencies
19 |
20 | ### JavaScript back end
21 | * Rendering as DOM nodes
22 | * Updating nodes in DOM
23 | * DSL for attaching/detaching events
24 |
25 | ## Compatibility
26 | | Platform | Platform version | Scala versions |
27 | |:-------------|:-----------------|:-----------------------------|
28 | | JVM | | 2.11 (T), 2.12 (T), 2.13 (L) |
29 | | Scala.js | 1.0 | 2.12 (T), 2.13 (L) |
30 | | Scala Native | 0.4.0-M2 | 2.11 (T) |
31 |
32 | * (T): Typelevel Scala
33 | * (L): Lightbend Scala
34 |
35 | ## Examples
36 | ```scala
37 | import pine._
38 |
39 | val url = "http://github.com/"
40 | val root = html"GitHub"
41 |
42 | println(root.toHtml) // GitHub
43 | ```
44 |
45 | ### JavaScript
46 | ```scala
47 | import pine.dom._
48 | println(root.toDom) // [object HTMLAnchorElement]
49 | ```
50 |
51 | ## sbt
52 | Pine makes use of a language extension called *literal types*, see [SIP-23](http://docs.scala-lang.org/sips/pending/42.type.html). For Scala 2.11 and 2.12, only [Typelevel Scala](https://github.com/typelevel/scala) implements this feature. However, it is available in Lightbend Scala from 2.13 onwards.
53 |
54 | ### 2.13 onwards
55 | ```scala
56 | scalaVersion := "2."
57 | libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
58 | ```
59 |
60 | ### < 2.13
61 | ```scala
62 | scalaVersion := "2.12.4-bin-typelevel-4" // or "2.11.11-bin-typelevel-4"
63 | scalaOrganization := "org.typelevel"
64 | scalacOptions += "-Yliteral-types"
65 |
66 | libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
67 | ```
68 |
69 | #### Scala.js settings
70 | ```scala
71 | libraryDependencies := libraryDependencies.value.filterNot(_.name == "scalajs-compiler")
72 | addCompilerPlugin("org.scala-js" % "scalajs-compiler" % scalaJSVersion cross CrossVersion.patch)
73 | ```
74 |
75 | #### Scala Native settings
76 | ```scala
77 | libraryDependencies := libraryDependencies.value.filterNot(_.name == "nscplugin")
78 | addCompilerPlugin("org.scala-native" % "nscplugin" % nativeVersion cross CrossVersion.patch)
79 | ```
80 |
81 | ### Dependencies
82 | ```scala
83 | libraryDependencies += "tech.sparse" %% "pine" % "" // JVM
84 | libraryDependencies += "tech.sparse" %%% "pine" % "" // JavaScript, Native
85 | ```
86 |
87 | ## Links
88 | * [Documentation](http://sparse.tech/docs/pine.html)
89 | * [ScalaDoc](https://www.javadoc.io/doc/tech.sparse/pine_2.12/)
90 |
91 | ## Licence
92 | Pine is licensed under the terms of the Apache v2.0 licence.
93 |
94 | ## Contributors
95 | * Tim Nieradzik
96 | * Matt Hicks
97 | * Anatoliy Kmetyuk
98 | * Keven Wright
99 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 |
2 | val convertMDN = taskKey[Unit]("Generate MDN bindings")
3 |
4 | val V = new {
5 | val paradise = "2.1.1"
6 | val scala2_11 = "2.11.11-bin-typelevel-4"
7 | val scala2_12 = "2.12.4-bin-typelevel-4"
8 | val scala2_13 = "2.13.3"
9 | val scalaTest = "3.2.2"
10 | val scalaCheck = "1.14.3"
11 | val scalaJsDom = "1.1.0"
12 | }
13 |
14 | ThisBuild / scalaVersion := V.scala2_13
15 |
16 | val commonSettings = nocomma {
17 | name := "pine"
18 | convertMDN := MDNParser.createFiles(new File("src/main/scala"))
19 |
20 | // See https://github.com/sbt/sbt/pull/2659
21 | incOptions := incOptions.value.withLogRecompileOnMacro(false)
22 |
23 | libraryDependencies ++= Seq(
24 | scalaOrganization.value % "scala-reflect" % scalaVersion.value % "provided",
25 | scalaOrganization.value % "scala-compiler" % scalaVersion.value % "provided",
26 |
27 | "org.scalatest" %%% "scalatest" % V.scalaTest % "test",
28 | "org.scalacheck" %%% "scalacheck" % V.scalaCheck % "test"
29 | )
30 |
31 | scalacOptions ++= Seq(
32 | "-deprecation", // Emit warning and location for usages of deprecated APIs.
33 | "-encoding", "utf-8", // Specify character encoding used by source files.
34 | "-explaintypes", // Explain type errors in more detail.
35 | "-feature", // Emit warning and location for usages of features that should be imported explicitly.
36 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred
37 | "-language:experimental.macros", // Allow macro definition (besides implementation and application)
38 | "-language:higherKinds", // Allow higher-kinded types
39 | "-unchecked", // Enable additional warnings where generated code depends on assumptions.
40 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
41 | "-Xfatal-warnings", // Fail the compilation if there are any warnings.
42 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
43 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit.
44 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
45 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
46 | "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`.
47 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
48 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit.
49 | "-Xlint:option-implicit", // Option.apply used implicit view.
50 | "-Xlint:package-object-classes", // Class or object defined in package object.
51 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
52 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
53 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
54 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
55 | // "-Ywarn-dead-code", // Warn when dead code is identified.
56 | "-Ywarn-numeric-widen", // Warn when numerics are widened.
57 | )
58 | }
59 |
60 | val Pre13Settings = nocomma {
61 | scalaOrganization := "org.typelevel"
62 |
63 | scalacOptions ++= Seq(
64 | "-Xfuture", // Turn on future language features.
65 | "-Xlint:by-name-right-associative", // By-name parameter of right associative operator.
66 | "-Xlint:unsound-match", // Pattern match may not be typesafe.
67 | "-Yliteral-types", // ... this is why we need the typelevel org
68 | "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
69 | "-Ypartial-unification", // Enable partial unification in type constructor inference
70 | "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures.
71 | "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`.
72 | "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
73 | "-Ywarn-nullary-unit", // Warn when nullary methods return Unit.
74 | )
75 |
76 | addCompilerPlugin("org.scalamacros" %% "paradise" % V.paradise cross CrossVersion.patch)
77 | }
78 |
79 | val Post11Settings = nocomma {
80 | scalacOptions ++= Seq(
81 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
82 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
83 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced.
84 | "-Ywarn-unused:locals", // Warn if a local definition is unused.
85 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
86 | "-Ywarn-unused:privates", // Warn if a private member is unused.
87 | // "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
88 | // "-Ywarn-unused:params", // Warn if a value parameter is unused.
89 | // "-Ywarn-value-discard" // Warn when non-Unit expression results are unused.
90 | )
91 | }
92 |
93 | val Scala11Settings = Pre13Settings
94 |
95 | val Scala12Settings = Pre13Settings ++ Post11Settings
96 |
97 | val Scala13Settings = Post11Settings ++ nocomma {
98 | scalacOptions += "-Ymacro-annotations"
99 | }
100 |
101 | val JvmSettings = Seq()
102 |
103 | val JsSettings = nocomma {
104 | libraryDependencies ++= Seq(
105 | "org.scala-js" %%% "scalajs-dom" % V.scalaJsDom
106 | )
107 |
108 | // We need to remove and re-add this if working under the typelevel compiler
109 | // under the lighbend compiler it causes no harm
110 | // See https://github.com/scala-js/scala-js/pull/2954
111 | libraryDependencies ~= (_.filterNot(_.name == "scalajs-compiler"))
112 | addCompilerPlugin("org.scala-js" % "scalajs-compiler" % scalaJSVersion cross CrossVersion.patch)
113 |
114 | Test / jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv
115 | Global / scalaJSStage := FastOptStage
116 | }
117 |
118 | val NativeSettings = nocomma {
119 | libraryDependencies ~= (_.filterNot(_.name == "nscplugin"))
120 | addCompilerPlugin("org.scala-native" % "nscplugin" % nativeVersion cross CrossVersion.patch)
121 |
122 | // See https://github.com/scalalandio/chimney/issues/78#issuecomment-419705142
123 | nativeLinkStubs := true
124 |
125 | // Workaround for "No documentation generated with unsuccessful compiler run"
126 | sources in (Compile, doc) := Seq()
127 | }
128 |
129 | lazy val pine = (projectMatrix in file("."))
130 | .settings(commonSettings)
131 | .jvmPlatform( Seq(V.scala2_13), JvmSettings ++ Scala13Settings)
132 | .jvmPlatform( Seq(V.scala2_12), JvmSettings ++ Scala12Settings)
133 | .jvmPlatform( Seq(V.scala2_11), JvmSettings ++ Scala11Settings)
134 | .jsPlatform( Seq(V.scala2_13), JsSettings ++ Scala13Settings)
135 | .jsPlatform( Seq(V.scala2_12), JsSettings ++ Scala12Settings)
136 | .nativePlatform( Seq(V.scala2_11), NativeSettings ++ Pre13Settings)
137 |
138 | // root settings. src/ is handled by sbt-projectmatrix
139 | publish / skip := false
140 | Compile / sources := List()
141 | Test / sources := List()
142 |
143 | // publish settings
144 | ThisBuild / organization := "tech.sparse"
145 | ThisBuild / homepage := Some(url("https://github.com/sparsetech/pine"))
146 | ThisBuild / licenses := List("Apache 2" -> url("https://www.apache.org/licenses/LICENSE-2.0.html"))
147 | ThisBuild / scmInfo := Some(
148 | ScmInfo(
149 | url("https://github.com/sparsetech/pine"),
150 | "scm:git@github.com:sparsetech/pine.git"
151 | )
152 | )
153 | ThisBuild / developers := List(
154 | Developer(
155 | id = "tindzk",
156 | name = "Tim Nieradzik",
157 | email = "@tindzk",
158 | url = url("http://github.com/tindzk")
159 | )
160 | )
161 |
--------------------------------------------------------------------------------
/build.toml:
--------------------------------------------------------------------------------
1 | build213.toml
--------------------------------------------------------------------------------
/build211.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | scalaVersion = "2.11.11-bin-typelevel-4"
3 | scalaNativeVersion = "0.4.0-M2"
4 | scalaOptions = [
5 | "-deprecation", # Emit warning and location for usages of deprecated APIs.
6 | "-encoding", "utf-8", # Specify character encoding used by source files.
7 | "-explaintypes", # Explain type errors in more detail.
8 | "-feature", # Emit warning and location for usages of features that should be imported explicitly.
9 | "-language:existentials", # Existential types (besides wildcard types) can be written and inferred
10 | "-language:experimental.macros", # Allow macro definition (besides implementation and application)
11 | "-language:higherKinds", # Allow higher-kinded types
12 | "-unchecked", # Enable additional warnings where generated code depends on assumptions.
13 | "-Xcheckinit", # Wrap field accessors to throw an exception on uninitialized access.
14 | "-Xfatal-warnings", # Fail the compilation if there are any warnings.
15 | "-Xlint:adapted-args", # Warn if an argument list is modified to match the receiver.
16 | "-Xlint:delayedinit-select", # Selecting member of DelayedInit.
17 | "-Xlint:doc-detached", # A Scaladoc comment appears to be detached from its element.
18 | "-Xlint:inaccessible", # Warn about inaccessible types in method signatures.
19 | "-Xlint:infer-any", # Warn when a type argument is inferred to be `Any`.
20 | "-Xlint:missing-interpolator", # A string literal appears to be missing an interpolator id.
21 | "-Xlint:nullary-override", # Warn when non-nullary `def f()' overrides nullary `def f'.
22 | "-Xlint:nullary-unit", # Warn when nullary methods return Unit.
23 | "-Xlint:option-implicit", # Option.apply used implicit view.
24 | "-Xlint:package-object-classes", # Class or object defined in package object.
25 | "-Xlint:poly-implicit-overload", # Parameterized overloaded implicit methods are not visible as view bounds.
26 | "-Xlint:private-shadow", # A private field (or class parameter) shadows a superclass field.
27 | "-Xlint:stars-align", # Pattern sequence wildcard must align with sequence component.
28 | "-Xlint:type-parameter-shadow", # A local type parameter shadows a type already in scope.
29 | # "-Ywarn-dead-code", # Warn when dead code is identified.
30 | "-Ywarn-numeric-widen", # Warn when numerics are widened.
31 |
32 | ### pre-13 options ###
33 |
34 | "-Xfuture", # Turn on future language features.
35 | "-Xlint:by-name-right-associative", # By-name parameter of right associative operator.
36 | "-Xlint:unsound-match", # Pattern match may not be typesafe.
37 | "-Yliteral-types", # ... this is why we need the typelevel org
38 | "-Yno-adapted-args", # Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
39 | "-Ypartial-unification", # Enable partial unification in type constructor inference
40 | "-Ywarn-inaccessible", # Warn about inaccessible types in method signatures.
41 | "-Ywarn-infer-any", # Warn when a type argument is inferred to be `Any`.
42 | "-Ywarn-nullary-override", # Warn when non-nullary `def f()' overrides nullary `def f'.
43 | "-Ywarn-nullary-unit", # Warn when nullary methods return Unit.
44 | ]
45 | scalaOrganisation = "org.typelevel"
46 | testFrameworks = [
47 | "org.scalatest.tools.Framework",
48 | "org.scalacheck.ScalaCheckFramework"
49 | ]
50 |
51 | [module.pine]
52 | root = "src/main/scala"
53 | sources = ["src/main/scala"]
54 | targets = ["jvm", "native"]
55 |
56 | [module.pine.test]
57 | sources = ["src/test/scala"]
58 | targets = ["jvm"]
59 | scalaDeps = [
60 | ["org.scalatest" , "scalatest" , "3.2.2" ],
61 | ["org.scalacheck", "scalacheck", "1.14.3"]
62 | ]
63 |
64 | [module.pine.jvm]
65 | root = "src/main/scala-jvm"
66 | sources = ["src/main/scala-jvm"]
67 |
68 | [module.pine-bench]
69 | moduleDeps = ["pine"]
70 | root = "src/bench"
71 | sources = ["src/bench/shared"]
72 |
73 | [module.pine-bench.jvm]
74 | root = "src/bench/jvm"
75 | sources = ["src/bench/jvm"]
76 | javaDeps = [
77 | ["org.openjdk.jol", "jol-core", "0.13"]
78 | ]
79 |
--------------------------------------------------------------------------------
/build212.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | scalaVersion = "2.12.4-bin-typelevel-4"
3 | scalaJsVersion = "1.0.1"
4 | scalaOptions = [
5 | "-deprecation", # Emit warning and location for usages of deprecated APIs.
6 | "-encoding", "utf-8", # Specify character encoding used by source files.
7 | "-explaintypes", # Explain type errors in more detail.
8 | "-feature", # Emit warning and location for usages of features that should be imported explicitly.
9 | "-language:existentials", # Existential types (besides wildcard types) can be written and inferred
10 | "-language:experimental.macros", # Allow macro definition (besides implementation and application)
11 | "-language:higherKinds", # Allow higher-kinded types
12 | "-unchecked", # Enable additional warnings where generated code depends on assumptions.
13 | "-Xcheckinit", # Wrap field accessors to throw an exception on uninitialized access.
14 | "-Xfatal-warnings", # Fail the compilation if there are any warnings.
15 | "-Xlint:adapted-args", # Warn if an argument list is modified to match the receiver.
16 | "-Xlint:delayedinit-select", # Selecting member of DelayedInit.
17 | "-Xlint:doc-detached", # A Scaladoc comment appears to be detached from its element.
18 | "-Xlint:inaccessible", # Warn about inaccessible types in method signatures.
19 | "-Xlint:infer-any", # Warn when a type argument is inferred to be `Any`.
20 | "-Xlint:missing-interpolator", # A string literal appears to be missing an interpolator id.
21 | "-Xlint:nullary-override", # Warn when non-nullary `def f()' overrides nullary `def f'.
22 | "-Xlint:nullary-unit", # Warn when nullary methods return Unit.
23 | "-Xlint:option-implicit", # Option.apply used implicit view.
24 | "-Xlint:package-object-classes", # Class or object defined in package object.
25 | "-Xlint:poly-implicit-overload", # Parameterized overloaded implicit methods are not visible as view bounds.
26 | "-Xlint:private-shadow", # A private field (or class parameter) shadows a superclass field.
27 | "-Xlint:stars-align", # Pattern sequence wildcard must align with sequence component.
28 | "-Xlint:type-parameter-shadow", # A local type parameter shadows a type already in scope.
29 | # "-Ywarn-dead-code", # Warn when dead code is identified.
30 | "-Ywarn-numeric-widen", # Warn when numerics are widened.
31 |
32 | ### post-11 options ###
33 |
34 | "-Xlint:constant", # Evaluation of a constant arithmetic expression results in an error.
35 | "-Ywarn-extra-implicit", # Warn when more than one implicit parameter section is defined.
36 | "-Ywarn-unused:imports", # Warn if an import selector is not referenced.
37 | "-Ywarn-unused:locals", # Warn if a local definition is unused.
38 | "-Ywarn-unused:patvars", # Warn if a variable bound in a pattern is unused.
39 | "-Ywarn-unused:privates", # Warn if a private member is unused.
40 | # "-Ywarn-unused:implicits", # Warn if an implicit parameter is unused.
41 | # "-Ywarn-unused:params", # Warn if a value parameter is unused.
42 | # "-Ywarn-value-discard" # Warn when non-Unit expression results are unused.
43 |
44 | ### pre-13 options ###
45 |
46 | "-Xfuture", # Turn on future language features.
47 | "-Xlint:by-name-right-associative", # By-name parameter of right associative operator.
48 | "-Xlint:unsound-match", # Pattern match may not be typesafe.
49 | "-Yliteral-types", # ... this is why we need the typelevel org
50 | "-Yno-adapted-args", # Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
51 | "-Ypartial-unification", # Enable partial unification in type constructor inference
52 | "-Ywarn-inaccessible", # Warn about inaccessible types in method signatures.
53 | "-Ywarn-infer-any", # Warn when a type argument is inferred to be `Any`.
54 | "-Ywarn-nullary-override", # Warn when non-nullary `def f()' overrides nullary `def f'.
55 | "-Ywarn-nullary-unit", # Warn when nullary methods return Unit.
56 | ]
57 | scalaOrganisation = "org.typelevel"
58 | testFrameworks = [
59 | "org.scalatest.tools.Framework",
60 | "org.scalacheck.ScalaCheckFramework"
61 | ]
62 |
63 | [module.pine]
64 | root = "src/main/scala"
65 | sources = ["src/main/scala"]
66 | targets = ["js", "jvm"]
67 |
68 | [module.pine.test]
69 | sources = ["src/test/scala"]
70 | targets = ["js", "jvm"]
71 | scalaDeps = [
72 | ["org.scalatest" , "scalatest" , "3.1.1" ],
73 | ["org.scalacheck", "scalacheck", "1.14.3"]
74 | ]
75 |
76 | [module.pine.jvm]
77 | root = "src/main/scala-jvm"
78 | sources = ["src/main/scala-jvm"]
79 |
80 | [module.pine.js]
81 | root = "src/main/scala-js"
82 | sources = ["src/main/scala-js"]
83 | scalaDeps = [
84 | ["org.scala-js", "scalajs-dom", "1.1.0"]
85 | ]
86 |
87 | [module.pine.test.js]
88 | jsdom = true
89 | sources = ["src/test/scala-js"]
90 |
91 | [module.pine-bench]
92 | moduleDeps = ["pine"]
93 | root = "src/bench"
94 | sources = ["src/bench/shared"]
95 | targets = ["js", "jvm"]
96 |
97 | [module.pine-bench.jvm]
98 | root = "src/bench/jvm"
99 | sources = ["src/bench/jvm"]
100 | javaDeps = [
101 | ["org.openjdk.jol", "jol-core", "0.13"]
102 | ]
103 |
104 | [module.pine-bench.js]
105 | root = "src/bench/js"
106 | sources = ["src/bench/js"]
107 | moduleKind = "commonjs"
108 |
--------------------------------------------------------------------------------
/build213.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | scalaVersion = "2.13.2"
3 | scalaJsVersion = "1.0.1"
4 | scalaOptions = [
5 | "-deprecation", # Emit warning and location for usages of deprecated APIs.
6 | "-encoding", "utf-8", # Specify character encoding used by source files.
7 | "-explaintypes", # Explain type errors in more detail.
8 | "-feature", # Emit warning and location for usages of features that should be imported explicitly.
9 | "-language:existentials", # Existential types (besides wildcard types) can be written and inferred
10 | "-language:experimental.macros", # Allow macro definition (besides implementation and application)
11 | "-language:higherKinds", # Allow higher-kinded types
12 | "-unchecked", # Enable additional warnings where generated code depends on assumptions.
13 | "-Xcheckinit", # Wrap field accessors to throw an exception on uninitialized access.
14 | "-Xfatal-warnings", # Fail the compilation if there are any warnings.
15 | "-Xlint:adapted-args", # Warn if an argument list is modified to match the receiver.
16 | "-Xlint:delayedinit-select", # Selecting member of DelayedInit.
17 | "-Xlint:doc-detached", # A Scaladoc comment appears to be detached from its element.
18 | "-Xlint:inaccessible", # Warn about inaccessible types in method signatures.
19 | "-Xlint:infer-any", # Warn when a type argument is inferred to be `Any`.
20 | "-Xlint:missing-interpolator", # A string literal appears to be missing an interpolator id.
21 | "-Xlint:nullary-unit", # Warn when nullary methods return Unit.
22 | "-Xlint:option-implicit", # Option.apply used implicit view.
23 | "-Xlint:package-object-classes", # Class or object defined in package object.
24 | "-Xlint:poly-implicit-overload", # Parameterized overloaded implicit methods are not visible as view bounds.
25 | "-Xlint:private-shadow", # A private field (or class parameter) shadows a superclass field.
26 | "-Xlint:stars-align", # Pattern sequence wildcard must align with sequence component.
27 | "-Xlint:type-parameter-shadow", # A local type parameter shadows a type already in scope.
28 | # "-Ywarn-dead-code", # Warn when dead code is identified.
29 | "-Ywarn-numeric-widen", # Warn when numerics are widened.
30 |
31 | ### post-11 options ###
32 |
33 | "-Xlint:constant", # Evaluation of a constant arithmetic expression results in an error.
34 | "-Ywarn-extra-implicit", # Warn when more than one implicit parameter section is defined.
35 | "-Ywarn-unused:imports", # Warn if an import selector is not referenced.
36 | "-Ywarn-unused:locals", # Warn if a local definition is unused.
37 | "-Ywarn-unused:patvars", # Warn if a variable bound in a pattern is unused.
38 | "-Ywarn-unused:privates", # Warn if a private member is unused.
39 | # "-Ywarn-unused:implicits", # Warn if an implicit parameter is unused.
40 | # "-Ywarn-unused:params", # Warn if a value parameter is unused.
41 | # "-Ywarn-value-discard" # Warn when non-Unit expression results are unused.
42 | ]
43 | testFrameworks = [
44 | "org.scalatest.tools.Framework",
45 | "org.scalacheck.ScalaCheckFramework"
46 | ]
47 |
48 | [module.pine]
49 | root = "src/main/scala"
50 | sources = ["src/main/scala"]
51 | targets = ["js", "jvm"]
52 |
53 | [module.pine.test]
54 | sources = ["src/test/scala"]
55 | targets = ["js", "jvm"]
56 | scalaDeps = [
57 | ["org.scalatest" , "scalatest" , "3.1.1" ],
58 | ["org.scalacheck", "scalacheck", "1.14.3"]
59 | ]
60 |
61 | [module.pine.jvm]
62 | root = "src/main/scala-jvm"
63 | sources = ["src/main/scala-jvm"]
64 |
65 | [module.pine.js]
66 | root = "src/main/scala-js"
67 | sources = ["src/main/scala-js"]
68 | scalaDeps = [
69 | ["org.scala-js", "scalajs-dom", "1.1.0"]
70 | ]
71 |
72 | [module.pine.test.js]
73 | jsdom = true
74 | sources = ["src/test/scala-js"]
75 |
76 | [module.pine-bench]
77 | moduleDeps = ["pine"]
78 | root = "src/bench"
79 | sources = ["src/bench/shared"]
80 | targets = ["js", "jvm"]
81 |
82 | [module.pine-bench.jvm]
83 | root = "src/bench/jvm"
84 | sources = ["src/bench/jvm"]
85 | javaDeps = [
86 | ["org.openjdk.jol", "jol-core", "0.13"]
87 | ]
88 |
89 | [module.pine-bench.js]
90 | root = "src/bench/js"
91 | sources = ["src/bench/js"]
92 | moduleKind = "commonjs"
93 |
--------------------------------------------------------------------------------
/cache/https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h1.html:
--------------------------------------------------------------------------------
1 | https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
--------------------------------------------------------------------------------
/cache/https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h2.html:
--------------------------------------------------------------------------------
1 | https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
--------------------------------------------------------------------------------
/cache/https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h3.html:
--------------------------------------------------------------------------------
1 | https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
--------------------------------------------------------------------------------
/cache/https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h4.html:
--------------------------------------------------------------------------------
1 | https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
--------------------------------------------------------------------------------
/cache/https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h5.html:
--------------------------------------------------------------------------------
1 | https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
--------------------------------------------------------------------------------
/cache/https___developer.mozilla.org_en-US_docs_Web_HTML_Element_h6.html:
--------------------------------------------------------------------------------
1 | https___developer.mozilla.org_en-US_docs_Web_HTML_Element_Heading_Elements.html
--------------------------------------------------------------------------------
/docs/1-tree-construction.md:
--------------------------------------------------------------------------------
1 | # Tree construction
2 | Unless otherwise stated, all code samples require a prior `import pine._`.
3 |
4 | ## DSL
5 | Pine offers a DSL that allows to create trees in terms of immutable objects:
6 |
7 | ```scala
8 | val a = tag.A
9 | .href("http://github.com/")
10 | .set(Text("GitHub"))
11 |
12 | a.toHtml // GitHub
13 | ```
14 |
15 | The bindings are derived from the MDN documentation. For all attributes, we provide getters and setters. `set()` replaces the children of a node.
16 |
17 | ## Macros
18 | Pine defines a couple of compile-time macros for increased comfort or performance.
19 |
20 | ### Inline HTML
21 | Inline HTML can include placeholders referring to Scala values:
22 |
23 | ```scala
24 | val url = "http://github.com/"
25 | val title = "GitHub"
26 | val root = html"""$title"""
27 |
28 | root.toHtml // GitHub
29 | ```
30 |
31 | A placeholder may also reference `Seq[Node]` values:
32 |
33 | ```scala
34 | val spans = Seq(
35 | html"test",
36 | html"test2"
37 | )
38 |
39 | val div = html"
$spans
"
40 | div.toHtml //
testtest2
41 | ```
42 |
43 | ### External HTML
44 | For loading external HTML files during compile-time, a constant file name must be passed:
45 |
46 | ```scala
47 | val tpl = html("test.html")
48 | tpl.toHtml //
...
49 | ```
50 |
51 | ## Runtime HTML parser
52 | Pine provides an HTML parser with the same semantics on all backends:
53 |
54 | ```scala
55 | val html = """
42
"""
56 | val node = HtmlParser.fromString(html)
57 | node.toHtml == html // true
58 | ```
59 |
60 | HTML code is parsed during compile-time and then translated to an immutable tree. This reduces any runtime overhead. HTML can be specified inline or loaded from external files.
61 |
62 | The parser has the following limitations:
63 |
64 | - The `DOCTYPE` tag is ignored
65 | - The input is expected to be valid HTML5
66 |
67 | The parser supports the complete set of more than 2100 HTML entities such as `"` as well as numeric ones (`"`). These entities can be decoded using the function `HtmlHelpers.decodeEntity()`. If you would like to decode a text that may contain such entities, you can call `decodeText()` instead.
68 |
69 | ## XML
70 | XML has slightly different semantics with regards to self-closing tags. The following example is valid XML, but would yield a parse error when parsed as HTML:
71 |
72 | ```xml
73 |
74 | ```
75 |
76 | Also, the typical XML header `""")
80 | ```
81 |
82 | ```scala
83 | xml"""
84 |
85 |
88 |
89 |
90 |
91 |
92 | """
93 | ```
94 |
95 | As per the XML specification, Pine supports only the following four entities:
96 |
97 | * `'` (`'`)
98 | * `<` (`<`)
99 | * `>` (`>`)
100 | * `&` (`&`)
101 |
102 | The underlying data structures are the same for HTML and XML trees. Pine strives for simplicity and performance at the cost of implementing only a subset of XML's features. Please refer to [scala-xml](https://github.com/scala/scala-xml) for a more complete implementation.
103 |
104 | At the moment, we are aware of the following parser limitations:
105 |
106 | - The XML header is optional and its attributes are ignored. The input is expected to be in UTF-8 regardless of the specified character set.
107 | - [DTDs](https://docstore.mik.ua/orelly/web2/xhtml/ch15_03.htm) are not supported. Therefore, additional entity or element declarations cannot be defined.
108 | - Processing instructions (other than ``) are not supported
109 |
110 | ## Conversion
111 | Some functions return `Tag[_]` when the tag type cannot be statically determined. A more concrete type is useful if you want to access element-specific attributes, like `href` on anchor nodes. You can use `as` to convert a tag to its correct type:
112 |
113 | ```scala
114 | val tag = html""
115 | val div = tag.as[tag.Div]
116 | ```
117 |
118 | Unlike `asInstanceOf`, this function ensures that the conversion is well-defined.
119 |
120 | ## Custom tags
121 | So far, we have used elements from the `tag` namespace. For each HTML element, Pine defines a type and an empty instance, i.e. without attributes and children. If you want to support a new element such as ``, you could define it as follows:
122 |
123 | ```scala
124 | type CustomType = "custom-type"
125 | val CustomType = Tag("CustomType")
126 | ```
127 |
128 | The compiler feature we use here are literal types. Originally developed within Typelevel Scala, it is now part of Lightbend Scala 2.13 onwards.
129 |
130 | Additionally, you can define methods to access attributes conveniently:
131 |
132 | ```scala
133 | implicit class TagAttributesCustomType(tag: Tag[CustomType]) {
134 | val myValue = TagAttribute[CustomType, String](tag, "my-value")
135 | }
136 | ```
137 |
138 | Now, you can access and modify your custom HTML element while preserving type-safety:
139 |
140 | ```scala
141 | val tag = html""""""
142 | val ct = tag.as[CustomType]
143 | ct.myValue() // value
144 | ct.myValue("value2").toHtml //
145 | ```
146 |
147 | Note that the type definition above is optional and you could also write the literal type directly:
148 |
149 | ```scala
150 | val ct2 = tag.as["custom-type"]
151 | ```
152 |
153 | `TagAttribute` takes an implicit `AttributeCodec`. If you would like to enforce more type-safety in attributes, you could define an enumeration and create an `AttributeCodec` instance for it:
154 |
155 | ```scala
156 | sealed abstract class Language(val id: String)
157 | object Language {
158 | case object French extends Language("french")
159 | case object Spanish extends Language("spanish")
160 | case object Unknown extends Language("unknown")
161 | val All = Set(French, Spanish)
162 | }
163 |
164 | implicit case object LanguageAttributeCodec extends AttributeCodec[Language] {
165 | override def encode(value: Language): Option[String] = Some(value.id)
166 | override def decode(value: Option[String]): Language =
167 | value.flatMap(id => Language.All.find(_.id == id))
168 | .getOrElse(Language.Unknown)
169 | }
170 |
171 | implicit class TagAttributesCustomDiv(tag: Tag[pine.tag.Div]) {
172 | val dataLanguage = TagAttribute[pine.tag.Div, Language](tag, "data-language")
173 | }
174 |
175 | tag.Div.dataLanguage(Language.Spanish)
176 | ```
177 |
178 | ## Rendering
179 | A node has several rendering methods:
180 |
181 | - **HTML:** `toHtml` is defined on every node and will return the tree as an HTML5 string. If the root node is an `` tag, the `DOCTYPE` will be included as well.
182 | - **XML:** `toXml` returns the tree as an XML 1.0 string. It always includes the XML header, specifying the encoding as UTF-8.
183 | - **DOM:** `toDom` is only available in JavaScript. It renders the tree as a browser node, which can be inserted into the DOM.
184 |
--------------------------------------------------------------------------------
/docs/2-tree-updates.md:
--------------------------------------------------------------------------------
1 | # Tree updates
2 | ## Operations
3 | A `Node` is equipped with a variety of functions to easily manipulate trees such as `prepend`, `append`, `remove`, `clearAll`, `filter`, `flatMap`, `map` and others. See the [source code](https://github.com/sparsetech/pine/blob/master/shared/src/main/scala/pine/Node.scala) for an overview.
4 |
5 | ## Referencing nodes
6 | While the operations from the previous section allow you to modify the tree, they operate on either the root node or are applied recursively to all children.
7 |
8 | If you would like to update a specific child further down in the hierarchy, Pine introduces the concept of tag references (`TagRef`s). These have the advantage that changes can be batched and applied efficiently.
9 |
10 | In order to do so, you need to make the nodes you would like to reference identifiable, for example by setting the `id` attributes:
11 |
12 | ```scala
13 | val node = html"""
14 |
15 |
16 |
17 |
18 | """
19 | ```
20 |
21 | Now you can reference these nodes using `TagRef`s. A `TagRef` takes the referenced tag's ID and HTML type:
22 |
23 | ```scala
24 | val spanAge = TagRef[tag.Span]("age")
25 | val spanName = TagRef[tag.Span]("name")
26 | ```
27 |
28 | There are more ways to reference nodes such as by class name or tag type. See section "Tag references".
29 |
30 | ## Updating nodes
31 | You can use the `update()` method to change the node:
32 |
33 | ```scala
34 | val result = node.update { implicit ctx =>
35 | spanAge := 42
36 | spanName := "Joe"
37 | }
38 | ```
39 |
40 | The changes (_diffs_) take an implicit rendering context. When you call `update()`, the changes will be queued up in the rendering context and processed in a batch.
41 |
42 | `result` will be equivalent to:
43 |
44 | ```html
45 |
46 | 42
47 | Joe
48 |
49 | ```
50 |
51 | ## Replacing nodes
52 | If you would like to replace the node itself, you can use `replace()`:
53 |
54 | ```scala
55 | val result = node.update { implicit ctx =>
56 | spanAge .replace(42)
57 | spanName.replace("Joe")
58 | }
59 | ```
60 |
61 | `result` will be equivalent to:
62 |
63 | ```html
64 |
65 | 42
66 | Joe
67 |
68 | ```
69 |
70 | ## Updating children
71 | ```scala
72 | val node = html""""""
73 | val root = TagRef[tag.Div]("page")
74 | ```
75 |
76 | In order to render a list, you can use the `:=` function (alias for `set`):
77 |
78 | ```scala
79 | root.update { implicit ctx =>
80 | root := List(
81 | html"
Hello,
",
82 | html"
world!
"
83 | )
84 | }
85 | ```
86 |
87 | But if you would like to later access those child nodes they need unique IDs. This is particularly useful when you render your HTML on the server and want to access it in JavaScript, e.g. in order to attach event handlers.
88 |
89 | First, we define a data type we would like to render:
90 |
91 | ```scala
92 | case class Item(id: Int, name: String)
93 | ```
94 |
95 | Next, we define a function that returns a child node given an item.
96 |
97 | ```scala
98 | val itemView = html"""
"""
99 | def idOf(item: Item): String = item.id.toString
100 | def renderItem(item: Item): Tag[_] = {
101 | val id = idOf(item)
102 | val node = itemView.suffixIds(id)
103 | val spanName = TagRef[tag.Span]("name", id)
104 | node.update(implicit ctx => spanName := item.name)
105 | }
106 | ```
107 |
108 | Finally, we render a list of items using the `set` method.
109 |
110 | ```scala
111 | val items = List(Item(0, "Joe"), Item(1, "Jeff"))
112 | val result = node.update(implicit ctx => root.set(items.map(renderItem)))
113 | ```
114 |
115 | `result` will be equivalent to:
116 |
117 | ```html
118 |
119 |
120 | Joe
121 |
122 |
123 | Jeff
124 |
125 |
126 | ```
127 |
128 | Now, we can reference child nodes using a `TagRef`:
129 |
130 | ```scala
131 | TagRef[tag.Div]("child", idOf(items.head)) // TagRef[tag.Child]("child0")
132 | ```
133 |
134 | ## Updating attributes
135 | As our `TagRef` objects are typed, we can provide implicits for supported attributes.
136 |
137 | ```scala
138 | val node = html"""GitHub"""
139 | node.update(implicit ctx =>
140 | NodeRef[tag.A]("lnk").href := "https://github.com/"
141 | )
142 | ```
143 |
144 | ## Tag references
145 | Tags can be referenced using:
146 |
147 | * ID attribute: `TagRef[tag.A]("id")`
148 | * Tag type: `TagRef[tag.A]`
149 | * Class name: `TagRef.byClass[tag.A]("class-name")`
150 |
151 | A `TagRef` exposes methods for manipulating nodes and their attributes. See its [source code](https://github.com/sparsetech/pine/tree/master/src/main/scala/pine/TagRef.scala) for a full list of operations.
152 |
153 | ## Diffs
154 | A `Diff` is an immutable object which describes tree changes. It is instantiated for example by the `TagRef` operations you have seen before such as `:=` (`set`), `replace` etc.
155 |
156 | So far, these changes were performed directly on the tree. However, for the JavaScript back end, we have an additional rendering context that can apply those changes to the DOM. This will be explained in the next chapter.
157 |
158 | The full list of supported diffs can be found [here](https://github.com/sparsetech/pine/tree/master/src/main/scala/pine/Diff.scala).
159 |
160 | ### Multiple occurrences
161 | If you would like to perform a change on all occurrences of a `TagRef`, use the `each` function:
162 |
163 | ```scala
164 | val div = html"""
168 | ```
169 |
170 | `each` can also be used in conjunction with any other diff type, such as attribute updates:
171 |
172 | ```scala
173 | val div = html"""
177 | ```
178 |
179 | ## HTML/CSS extensions
180 | Pine's DSL provides extensions to facilitate interaction with HTML/CSS. For toggling the visibility of a node, you can use `hide()`:
181 |
182 | ```scala
183 | div.hide(true) // Sets `style` attribute to hide the element in the browser
184 | ```
185 |
186 | ## Token list attributes
187 | There are certain HTML attributes whose values are encoded as space-separated tokens. `class` and `rel` are the most prominent examples.
188 |
189 | These attributes have a special mapping in Pine that models their underlying sequential nature:
190 |
191 | ```scala
192 | tag.Div.`class`("a", "b") // Sets the classes "a" and "b"
193 | tag.Div.`class`.set(Seq("a", "b")) // Same as before
194 | tag.Div.`class`.get // Returns the list of classes
195 | tag.Div.`class`.add("a") // Adds the class "a"
196 | tag.Div.`class`.remove("a") // Removes the class "a"
197 | tag.Div.`class`.clear() // Removes all classes
198 | tag.Div.`class`.toggle("a") // Toggles the class "a"
199 | tag.Div.`class`.state(value, "a") // Adds the class "a" if value is true, remove otherwise
200 | tag.Div.`class`.update(_ :+ "a") // Updates the classes
201 | ```
202 |
203 | The same functionality is available on `TagRef`s.
204 |
205 | ## Custom attributes
206 | If you would like to support custom attributes, you can extend the functionality of any tag by defining an implicit class. This is the same approach which Pine uses internally to define attributes for HTML elements.
207 |
208 | For example, to define attributes on anchor nodes, you would write:
209 |
210 | ```scala
211 | implicit class TagRefAttributesA(tagRef: TagRef[tag.A]) {
212 | val dataTooltip = TagRefAttribute[tag.A, String](tagRef, "data-tooltip")
213 | val dataShow = TagRefAttribute[tag.A, Boolean](tagRef, "data-show")
214 | }
215 | ```
216 |
--------------------------------------------------------------------------------
/docs/3-web-development.md:
--------------------------------------------------------------------------------
1 | # Web development
2 | Pine may be used for web development. You can use it in various architectures, for example:
3 |
4 | * Client-side rendered pages (SPA, single-page applications)
5 | * Server-side rendered pages without client logic
6 | * Server-side rendered pages with client logic
7 | * Server-side rendered pages with client logic and client-side rendering
8 |
9 | Please refer to our [sample project](https://github.com/sparsetech/pine-example) which implements the last architecture.
10 |
11 | _Server_ refers to either the JVM or LLVM back end, whereas _client_ refers to JavaScript.
12 |
13 | All examples require a prior `import pine.dom._`.
14 |
15 | ## Architectures
16 | Pine advocates web development in the FP style. You are advised to split your HTML rendering into composable functions and share the code across platforms. Pine does not provide any abstractions for pages or components to maximise its use cases.
17 |
18 | The fourth architecture is the most sophisticated and allows for the best user experience. For this, you have to define a shared protocol for the data layer as well as shared code for populating the templates. On the client, you evaluate which page the server rendered and then attach the event handlers. Also, when the user clicks an internal page link, instead of redirecting to it, you can use the shared template layer to perform the rendering directly in the browser.
19 |
20 | This architecture has the following life cycle for a page `p`, which you could define in terms of four functions:
21 |
22 | 1. `node(p)`: Creates an immutable tree node (`shared` project)
23 | 2. `populate(p)`: Populates the tree with content (`shared` project)
24 | 3. `attach(p)`: Attach event handlers, only called in JavaScript (`js` project)
25 | 4. `detach(p)`: Detach event handlers, only called in JavaScript (`js` project)
26 |
27 | ## Render JavaScript node
28 | To render a Pine node as a JavaScript node, use the function `toDom`:
29 |
30 | ```scala
31 | val div = html"""""".as[tag.Div]
32 | val jsNode = div.toDom // dom.html.Div
33 | ```
34 |
35 | `toDom` returns the correct JavaScript type depending on your node type:
36 |
37 | ```scala
38 | Text("test").toDom // dom.raw.Text
39 | ```
40 |
41 | You need to add the JavaScript node manually to the DOM to be able to access it via a `TagRef`:
42 |
43 | ```scala
44 | dom.document.body.appendChild(jsNode)
45 | ```
46 |
47 | ## Access DOM node
48 | Use `dom` on a `TagRef` to access the underlying DOM node:
49 |
50 | ```scala
51 | val text = TagRef[tag.Div]("text")
52 | text.dom // Returns browser node, of type org.scalajs.dom.html.Div
53 | ```
54 |
55 | If you would like to retrieve all matching nodes, use `each` and `domAll` instead:
56 |
57 | ```scala
58 | val input = TagRef[tag.Input].each
59 | input.domAll // List[org.scalajs.dom.html.Input]
60 | ```
61 |
62 | ## Access DOM attribute
63 | ```scala
64 | val text = TagRef[tag.Div]("text")
65 | text.`class`.get // Retrieves 'class' attribute from DOM node, of type Option[String]
66 | ```
67 |
68 | Note that in JavaScript, DOM attributes may not represent the current state of a node. If this is the case, you can retrieve the value via `dom`:
69 |
70 | ```scala
71 | val name = TagRef[tag.Input]("name")
72 | name.value.get // Returns value the DOM node was initialised with
73 | name.dom.value // Returns current value
74 | ```
75 |
76 | ## Converting JavaScript nodes
77 | It is also possible to convert regular DOM nodes to Pine:
78 |
79 | ```scala
80 | val node = dom.document.createElement("span")
81 | node.setAttribute("id", "test")
82 | node.appendChild(dom.document.createTextNode("Hello world"))
83 |
84 | DOM.toTree(node) // Tag(span,Map(id -> test),List(Text(Hello world)))
85 | ```
86 |
87 | ## Diffs
88 | Previously, we used `update` to perform the changes on the nodes. To carry out the changes in the DOM, we have to use `DOM.render`:
89 |
90 | ```scala
91 | DOM.render(implicit ctx => text := "Hello, world!")
92 | ```
93 |
94 | ### Events
95 | As an extension to content updates, you can set event handlers. In JavaScript projects, a `TagRef` exposes all event handlers which the underlying DOM element supports. These changes are side-effecting and therefore do not require a rendering context. The motivation is that event handlers do not change the visual page content. Therefore, instantiating `Diff`s and performing a batch execution would be redundant.
96 |
97 | ```scala
98 | val btnRemove = TagRef[tag.Button]("remove")
99 | btnRemove.click := println("Remove click")
100 | ```
101 |
102 | It is possible to attach an event to all matching elements using `each`:
103 |
104 | ```scala
105 | val input = TagRef[tag.Div].each
106 | input.click := println("Any div was clicked")
107 | ```
108 |
109 | See also `dom.Window` and `dom.Document` for global events.
110 |
111 | ## Troubleshooting
112 | ### Dangling rendering context
113 | If you encounter a *Dangling rendering context* exception, this may be reminiscent of a dangling pointer in C. The underlying problem is the same: You set up a rendering context passing it a function, which adds diffs to the context. After this function returns, the diffs are processed. Now, the rendering context should not be used anymore.
114 |
115 | Most likely an asynchronous event took place which re-used the implicit context from the scope and added a diff to it. One such example is:
116 |
117 | ```scala
118 | DOM.render { implicit ctx =>
119 | button.click := box.hide(true)
120 | }
121 | ```
122 |
123 | When the button was clicked, `hide` will re-use `ctx`. The following fixes the situation:
124 |
125 | ```scala
126 | button.click := DOM.render(implicit ctx => box.hide(true))
127 | ```
128 |
129 | You can safely nest multiple `DOM.render` blocks. The inner-most block will always use the context from the immediate scope. It is advisable to limit the rendering context only to functions that change the DOM and take an implicit context. This could prevent problems as above since an implicit context would not have been found in the first place.
130 |
131 | ### IntelliJ support
132 | When loading the sample projects in IntelliJ, some references in the `shared` module may not be resolved properly. This happens because IntelliJ doesn't add a dependency from platform-specific modules (i.e. `jvm` and `js`) to the `shared` module.
133 |
134 | To fix this, please go to `File -> Project Structure... -> Modules -> project-Sources -> Dependencies`. Then, add a module dependency to `projectJVM`/`projectNative` and `projectJS`.
135 |
--------------------------------------------------------------------------------
/docs/4-development.md:
--------------------------------------------------------------------------------
1 | # Development
2 | ## Benchmarking
3 | The benchmark suite measures the performance of Pine's core functionality. It is available for the JVM and JavaScript back ends.
4 |
5 | The benchmarks can be run on the JVM as follows:
6 |
7 | ```shell
8 | bloop run pine-bench-jvm -- fast # Fast profile
9 | bloop run pine-bench-jvm -- slow # Slow profile
10 | ```
11 |
12 | The JavaScript suite requires two dependencies to be installed:
13 |
14 | ```shell
15 | yarn add jsdom object-sizeof
16 | ```
17 |
18 | If you are compiling to tmpfs, link the `node_modules` folder:
19 |
20 | ```shell
21 | ln -s $(pwd)/node_modules /tmp/build-pine/node_modules
22 | ```
23 |
24 | Then, run the suite with Node.js:
25 |
26 | ```
27 | bloop run pine-bench-js -- fast # Fast profile
28 | bloop run pine-bench-js -- slow # Slow profile
29 | ```
30 |
31 | Since Node.js is lacking a DOM implementation, several benchmarks will be omitted. However, you can run the full benchmark suite in the browser. This requires the webpack dependency:
32 |
33 | ```shell
34 | yarn add webpack webpack-cli
35 | ```
36 |
37 | Next, create an HTML file in the build folder (e.g. `/tmp/build-pine/`):
38 |
39 | ```html
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ```
50 |
51 | And the webpack configuration `webpack.config.js`:
52 |
53 | ```javascript
54 | const path = require('path');
55 |
56 | module.exports = {
57 | entry: './pine-bench.js',
58 | output: {
59 | filename: 'main.js',
60 | path: __dirname
61 | }
62 | };
63 | ```
64 |
65 | Finally, link the benchmark suite and bundle all external dependencies into a single JavaScript file:
66 |
67 | ```shell
68 | bloop link pine-bench-js
69 | yarn exec webpack --config webpack.config.js
70 | ```
71 |
72 | You can now open the HTML file in the browser. The results will be printed to the browser console.
73 |
74 | To detect performance regressions, the benchmarks are run with JVM and Node.js as part of every CI build. Since the benchmark suite uses the fast profile to speed up CI runs, it is advisable to also run the benchmarks locally in the `slow` profile. Similarly, the DOM benchmarks are not run as part of CI and should be tested manually in a browser.
75 |
--------------------------------------------------------------------------------
/docs/5-manual.md:
--------------------------------------------------------------------------------
1 | # Development
2 | [](https://travis-ci.org/sparsetech/pine)
3 |
4 | ## Manual
5 | The manual was generated using [Instructor](https://github.com/sparsetech/instructor). Follow its installation instructions, then run the following command:
6 |
7 | ```shell
8 | instructor manual.toml
9 | ```
10 |
--------------------------------------------------------------------------------
/manual.toml:
--------------------------------------------------------------------------------
1 | [meta]
2 | title = "Pine User Manual"
3 | author = "Tim Nieradzik"
4 | affiliation = "sparse.tech"
5 | abstract = "Functional HTML5 and XML library for the Scala platform"
6 | language = "en-GB"
7 | editSourceUrl = "https://github.com/sparsetech/pine/edit/master/"
8 |
9 | [input]
10 | paths = ["docs/*.md"]
11 |
12 | [output]
13 | highlightJsStyle = "tomorrow"
14 |
15 | [constants]
16 | inherit = "version.sbt"
17 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.3.8
2 |
--------------------------------------------------------------------------------
/project/build.sbt:
--------------------------------------------------------------------------------
1 | libraryDependencies += "org.jsoup" % "jsoup" % "1.8.3"
2 |
3 | libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.3.0"
4 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 |
2 | logLevel := Level.Warn
3 |
4 | // see http://eed3si9n.com/removing-commas-with-sbt-nocomma
5 | addSbtPlugin("com.eed3si9n" % "sbt-nocomma" % "0.1.0")
6 | addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.3.0")
7 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.2.0")
8 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.0-M2")
9 |
10 | libraryDependencies += "org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0"
11 |
--------------------------------------------------------------------------------
/src/bench/js/pine/bench/PlatformBench.scala:
--------------------------------------------------------------------------------
1 | package pine.bench
2 |
3 | import scala.scalajs.js
4 |
5 | import pine._
6 | import pine.dom._
7 | import pine.bench.BenchUtil._
8 |
9 | class PlatformBench extends BenchmarkSuite with TreeBench {
10 | val isNodeJs = js.typeOf(js.Dynamic.global.window) == "undefined"
11 |
12 | if (isNodeJs) println("[warn] Ignoring all tests requiring DOM access")
13 | else {
14 | bench("Render and update node in DOM", depths, measureMemory = false) { depth =>
15 | val ref = TagRef.ByTag("span").each
16 | val rendered = treesWoAttributes(depth).toDom
17 | org.scalajs.dom.document.body.appendChild(rendered)
18 | DOM.render { implicit ctx => ref := "test" }
19 | org.scalajs.dom.document.body.removeChild(rendered)
20 | numberOfNodes(depth) -> (())
21 | }
22 |
23 | bench("Render as DOM node", depths, measureMemory = false) { depth =>
24 | numberOfNodes(depth) -> treesWithAttributes(depth).toDom
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/bench/js/pine/bench/PlatformUtil.scala:
--------------------------------------------------------------------------------
1 | package pine.bench
2 |
3 | import scala.scalajs.js
4 | import scala.scalajs.js.JSNumberOps._
5 | import scala.scalajs.js.annotation.JSImport
6 |
7 | @js.native
8 | @JSImport("object-sizeof", JSImport.Namespace)
9 | object SizeOf extends js.Object {
10 | def apply(obj: js.Any): Any = js.native
11 | }
12 |
13 | object PlatformUtil {
14 | def cliArgs(): List[String] =
15 | if (js.typeOf(js.Dynamic.global.process) == "undefined") List()
16 | else js.Dynamic.global.process.argv.asInstanceOf[js.Array[String]].toList.drop(2)
17 | def format(value: Double): String = value.toFixed(1)
18 | def measure(obj: Any): Long =
19 | SizeOf(obj.asInstanceOf[js.Any]) match {
20 | case i: Int => i.toLong
21 | case i: Float => i.toLong // TODO Should be Long instead of Float
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/bench/jvm/pine/bench/PlatformBench.scala:
--------------------------------------------------------------------------------
1 | package pine.bench
2 |
3 | class PlatformBench extends BenchmarkSuite
4 |
--------------------------------------------------------------------------------
/src/bench/jvm/pine/bench/PlatformUtil.scala:
--------------------------------------------------------------------------------
1 | package pine.bench
2 |
3 | import java.text.DecimalFormat
4 |
5 | import org.openjdk.jol.info.GraphLayout
6 |
7 | object PlatformUtil {
8 | private val decimalFormat = new DecimalFormat("0.0")
9 |
10 | def cliArgs(): List[String] = List()
11 | def format(value: Double): String = decimalFormat.format(value)
12 | def measure(obj: Any): Long =
13 | GraphLayout.parseInstance(obj.asInstanceOf[Object]).totalSize()
14 | }
15 |
--------------------------------------------------------------------------------
/src/bench/shared/pine/bench/Bench.scala:
--------------------------------------------------------------------------------
1 | package pine.bench
2 |
3 | import scala.collection.mutable.ListBuffer
4 |
5 | case class Benchmark[T, U](
6 | description: String,
7 | values: List[T],
8 | f: T => (Long, U),
9 | measureMemory: Boolean
10 | )
11 |
12 | trait BenchmarkSuite {
13 | private[bench] val benchmarks = ListBuffer[Benchmark[_, _]]()
14 |
15 | def bench[T, U](
16 | description: String, values: List[T], measureMemory: Boolean
17 | )(f: T => (Long, U)): Unit =
18 | benchmarks += Benchmark[T, U](description, values, f, measureMemory)
19 | }
20 |
21 | /**
22 | * @param minimumDuration Milliseconds for which each benchmark must be run at
23 | * least. This applies to warmup and measurement
24 | * iterations.
25 | */
26 | case class Profile(
27 | warmUpIterations: Int,
28 | measurementIterations: Int,
29 | minimumDuration: Int,
30 | garbageCollection: Boolean
31 | )
32 |
33 | object BenchmarkRunner {
34 | val slowProfile = Profile(
35 | warmUpIterations = 1,
36 | measurementIterations = 3,
37 | minimumDuration = 2000,
38 | garbageCollection = true
39 | )
40 |
41 | val fastProfile = Profile(
42 | warmUpIterations = 0,
43 | measurementIterations = 2,
44 | minimumDuration = 500,
45 | garbageCollection = false
46 | )
47 |
48 | val benchmarkSuites = List(new SharedBench, new PlatformBench)
49 |
50 | case class MeasureResult(
51 | iterations: Int,
52 | units: Long,
53 | unitSize: Long,
54 | totalSize: Long,
55 | totalRunTime: Long
56 | )
57 |
58 | def measure(
59 | f: () => (Long, Any), profile: Profile, measureSize: Boolean
60 | ): MeasureResult = {
61 | if (profile.garbageCollection) System.gc()
62 |
63 | var runTime = 0L
64 | var iterations = 0
65 | var unitsCumulative = 0L
66 | var last: (Long, Any) = (0L, null) // (units, result)
67 |
68 | while (runTime < profile.minimumDuration) {
69 | val start = System.currentTimeMillis()
70 | last = f()
71 | val end = System.currentTimeMillis()
72 |
73 | runTime += end - start
74 | unitsCumulative += last._1
75 | iterations += 1
76 | }
77 |
78 | val (unitSize, totalSize) =
79 | if (!measureSize) (0L, 0L)
80 | else {
81 | val (units, result) = last
82 | val totalSize = PlatformUtil.measure(result)
83 | (totalSize / units, totalSize)
84 | }
85 |
86 | val units = unitsCumulative / iterations
87 | MeasureResult(iterations, units, unitSize, totalSize, runTime)
88 | }
89 |
90 | def main(jvmArgs: Array[String]): Unit = {
91 | val args = if (jvmArgs.isEmpty) PlatformUtil.cliArgs() else jvmArgs.toList
92 | val profile =
93 | if (args.headOption.contains("slow")) slowProfile else fastProfile
94 |
95 | import profile._
96 | println(f"Warm up iterations: $warmUpIterations")
97 | println(f"Measurement iterations: $measurementIterations")
98 | println(f"Minimum duration: $minimumDuration ms")
99 | println(f"Garbage collection: $garbageCollection")
100 |
101 | println()
102 |
103 | benchmarkSuites.foreach { b =>
104 | b.benchmarks.foreach { b_ =>
105 | val b = b_.asInstanceOf[Benchmark[Any,Any]]
106 | println(s"Benchmark: ${b.description}")
107 | val depthResults = b.values.map { v =>
108 | println(s"- depth=$v:")
109 | (0 until warmUpIterations)
110 | .foreach(_ => measure(() => b.f(v), profile, false))
111 |
112 | val results = (0 until measurementIterations)
113 | .map(_ => measure(() => b.f(v), profile, b.measureMemory))
114 |
115 | val units = results.last.units
116 | val iterations = results.last.iterations
117 | println(f" units: $units")
118 | println(f" iterations: $iterations")
119 |
120 | val memoryTotal = results.last.totalSize
121 | val memoryUnit = results.last.unitSize
122 |
123 | val runTimes = results.map(r =>
124 | r.totalRunTime.toDouble / r.iterations * 1000000)
125 | val unitTimeMean = runTimes.sum / results.length
126 | val unitTimeStdDev = math.sqrt(runTimes.map(x =>
127 | (x - unitTimeMean) * (x - unitTimeMean)).sum / results.length)
128 |
129 | println(s" run time: ${unitTimeMean.toLong} μs/it ± ${unitTimeStdDev.toLong}")
130 |
131 | if (b.measureMemory)
132 | println(s" memory: $memoryTotal bytes (≃ $memoryUnit bytes/unit)")
133 |
134 | (units, unitTimeMean, memoryTotal)
135 | }
136 |
137 | val unitGrowth = depthResults.zip(depthResults.tail)
138 | .map { case ((units1, _, _), (units2, _, _)) =>
139 | units2.toDouble / units1 }
140 |
141 | val timeGrowth = depthResults.zip(depthResults.tail)
142 | .map { case ((_, time1, _), (_, time2, _)) =>
143 | time2.toDouble / time1 }
144 |
145 | println()
146 | println("Summary:")
147 | println(s" Unit growth: " +
148 | unitGrowth
149 | .map(x => f"${PlatformUtil.format(x)}x")
150 | .mkString(", "))
151 | println(s" Run time growth: " +
152 | timeGrowth
153 | .map(x => f"${PlatformUtil.format(x)}x")
154 | .mkString(", "))
155 |
156 | if (b.measureMemory) {
157 | val memoryGrowth = depthResults
158 | .zip(depthResults.tail)
159 | .map { case ((_, _, memory1), (_, _, memory2)) =>
160 | memory2.toDouble / memory1 }
161 |
162 | println(s" Memory growth: " +
163 | memoryGrowth
164 | .map(x => f"${PlatformUtil.format(x)}x")
165 | .mkString(", "))
166 | }
167 |
168 | println()
169 | }
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/bench/shared/pine/bench/BenchUtil.scala:
--------------------------------------------------------------------------------
1 | package pine.bench
2 |
3 | import pine._
4 |
5 | object BenchUtil {
6 | def binaryTree(depth: Int): Node =
7 | if (depth == 0) tag.Span // empty leaf
8 | else tag.Div
9 | .append(binaryTree(depth - 1))
10 | .append(binaryTree(depth - 1))
11 |
12 | def binaryTreeMacro(depth: Int): Node =
13 | if (depth == 0) html"""""" // empty leaf
14 | else html"""