├── .gitignore
├── ChangeLog
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── Screenshot-TreeLog_UI_v0.1.png
├── build.sbt
├── index.html
├── project
└── plugins.sbt
└── src
├── main
└── scala
│ ├── com
│ └── oranda
│ │ └── treelogui
│ │ ├── LogTreeItem.scala
│ │ ├── LogTreeItemWithoutAnnotations.scala
│ │ ├── LogTreePerson.scala
│ │ ├── LogTreeQuadratic.scala
│ │ ├── TreeLogUIExamples.scala
│ │ └── UIExample.scala
│ ├── demo
│ └── components
│ │ └── CodeHighlight.scala
│ └── treelog
│ ├── LogTreeLabel.scala
│ ├── LogTreeSyntax.scala
│ ├── LogTreeSyntaxWithoutAnnotations.scala
│ ├── QuadraticRootsExample.scala
│ ├── SerializableTree.scala
│ └── package.scala
└── test
└── scala
└── com
└── oranda
└── treelogui
├── LogTreeItemSpec.scala
├── LogTreeItemWithoutAnnotationsSpec.scala
└── package.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | # Java class files
2 | *.class
3 |
4 | # SBT generated files
5 | target/
6 | project/target/
7 | project/project/target
8 |
9 | # IntelliJ files
10 | *.iml
11 |
12 | # npm
13 | npm-debug.log
--------------------------------------------------------------------------------
/ChangeLog:
--------------------------------------------------------------------------------
1 | == 0.1 June 21 2016
2 |
3 | * Initial commit with two examples of TreeLog logs in ReactTreeView format:
4 | a success case, and an annotated case.
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 lancewalton
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | TreeLog-ScalaJS
2 | ===============
3 |
4 | This project brings together two other projects: the excellent
5 | [TreeLog](https://github.com/lancewalton/treelog) from Lance and Channing Walton,
6 | and the pretty ReactTreeView component from Chandra Sekhar Kode in the
7 | [scalajs-react-components](https://github.com/chandu0101/scalajs-react-components) project.
8 |
9 | **TreeLog** is a way of displaying a program's log in a tree structure instead of the hopelessly
10 | verbose sequential format we are usually cursed with. It is based on the Scalaz `Writer` monad.
11 | Although it is oriented towards algorithms with pure functions (like mathematical computations),
12 | it can annotate tree nodes with messages for side-effects. Read more about it
13 | [at typelevel](http://typelevel.org/blog/2013/10/18/treelog.html).
14 |
15 | **ReactTreeView** is the UI part: this component supports expanding nodes, highlighting, and other
16 | nice features. It existed first as a [normal ReactJS component](https://github.com/chenglou/react-treeview),
17 | then was [adapted into ScalaJS](
18 | https://github.com/chandu0101/scalajs-react-components/blob/master/core/src/main/scala/chandu0101/scalajs/react/components/ReactTreeView.scala),
19 | meaning you can write your tree in Scala and it will be
20 | converted automatically into JavaScript. See the screenshot lower down to see what it looks like.
21 |
22 | Implementation
23 | ==============
24 |
25 | So, two tree projects: a match made in heaven? All TreeLog-ScalaJS really needs to do
26 | is transform a `scalaz.Tree` into a UI `TreeItem`. The main code is in `com.oranda.treelogui`.
27 | There is some code here and in `demo.components` that is copied from the
28 | scalajs-react-components demo. The code in the `treelog` package is copied straight from
29 | the TreeLog project.
30 |
31 | Running
32 | =======
33 |
34 | 1. Clone this project (or download and unzip).
35 | 2. In the project's root, from the command-line run `sbt fastOptJS`. This will do all the Scala compilation and
36 | ScalaJS transpilation.
37 | 3. In your browser navigate to the project's index.html on the filesystem. (There is no need
38 | to run a server.) You should see:
39 |
40 | 
41 |
42 | Running the tests
43 | =================
44 |
45 | 1. Download [PhantomJS](http://phantomjs.org/download.html). This can run JavaScript outside a browser.
46 | 2. Make sure the `phantomjs` executable is in your `PATH` environment variable.
47 | 3. In this project's root, from the command-line run `sbt test`.
48 |
49 | License
50 | =======
51 |
52 | Licensed under
53 |
54 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
55 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
56 |
--------------------------------------------------------------------------------
/Screenshot-TreeLog_UI_v0.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oranda/treelog-scalajs/fc35d4ed3a91a49e3b915b84fae53dcd7bb1ff4c/Screenshot-TreeLog_UI_v0.1.png
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import sbt.Keys._
2 | import sbt._
3 |
4 | name := "treelog-scalajs"
5 |
6 | version := "0.1"
7 |
8 | enablePlugins(ScalaJSPlugin)
9 |
10 | scalaVersion := "2.11.8"
11 |
12 | scalacOptions ++= Seq("-unchecked", "-deprecation")
13 |
14 | resolvers ++= Seq(
15 | Resolver.sonatypeRepo("releases"),
16 | Resolver.sonatypeRepo("snapshots")
17 | )
18 |
19 | libraryDependencies ++= Seq(
20 | "com.github.japgolly.fork.scalaz" %%% "scalaz-core" % "7.2.0",
21 | "com.github.japgolly.scalajs-react" %% "core_sjs0.6" % "0.8.3",
22 | "com.github.japgolly.scalajs-react" %% "extra_sjs0.6" % "0.8.3",
23 | "com.github.chandu0101.scalajs-react-components" %% "core_sjs0.6" % "0.4.1",
24 | "org.scalatest" %%% "scalatest" % "3.0.0-M15" % "test"
25 | )
26 |
27 | scalaJSUseRhino in Global := false
28 |
29 | // React JS itself (Note the filenames, adjust as needed, eg. to remove addons.)
30 | jsDependencies ++= Seq(
31 | "org.webjars.bower" % "react" % "15.0.2"
32 | / "react-with-addons.js"
33 | minified "react-with-addons.min.js"
34 | commonJSName "React",
35 | "org.webjars.bower" % "react" % "15.0.2"
36 | / "react-dom.js"
37 | minified "react-dom.min.js"
38 | dependsOn "react-with-addons.js"
39 | commonJSName "ReactDOM",
40 | "org.webjars.bower" % "react" % "15.0.2"
41 | / "react-dom-server.js"
42 | minified "react-dom-server.min.js"
43 | dependsOn "react-dom.js"
44 | commonJSName "ReactDOMServer")
45 |
46 | // This causes tests to be run with the headless PhantomJS instead of Node
47 | jsDependencies += RuntimeDOM % "test"
48 |
49 |
50 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | scalajs-react components demo
6 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.9")
--------------------------------------------------------------------------------
/src/main/scala/com/oranda/treelogui/LogTreeItem.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import chandu0101.scalajs.react.components.TreeItem
4 | import treelog.{LogTreeLabel, LogTreeSyntax}
5 |
6 | import scalaz.Show
7 |
8 | /**
9 | * Provides a way of building a [[chandu0101.scalajs.react.components.TreeItem TreeItem]] from a
10 | * [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]]
11 | * (declared in [[treelog.LogTreeSyntax LogTreeSyntax]]).
12 | *
13 | * Classes that extend this trait should override `result` to form the `DescribedComputation`,
14 | * and `annotationShow` to specify what the annotation for a node should display.
15 | */
16 | trait LogTreeItem[Annotation, Result] extends LogTreeSyntax[Annotation] {
17 |
18 | /**
19 | * Forms the `DescribedComputation` used by this `LogTreeItem`.
20 | */
21 | protected def result: DescribedComputation[Result]
22 |
23 | /**
24 | * Specifies what the annotation for a node should display for this `LogTreeItem`.
25 | */
26 | protected def annotationShow: Show[Annotation]
27 |
28 | /**
29 | * Builds the UI `TreeItem` using the `DescribedComputation` and annotation messages specified
30 | * in the trait implementation.
31 | */
32 | final def treeItem: TreeItem = {
33 |
34 | def showSuccess(success: Boolean, s: String) = if (success) s else "Failed: " + s
35 |
36 | def showDescription(label: LogTreeLabel[Annotation]) =
37 | label.fold(_.description, _ ⇒ "No Description")
38 |
39 | def showAnnotations(
40 | annotations: Set[Annotation],
41 | line: String,
42 | annotationShow: Show[Annotation]) =
43 | if (annotations.isEmpty) line
44 | else line + " - [" + annotations.map(annotationShow.show).mkString(", ") + "]"
45 |
46 | def buildTreeItem(t: LogTree): TreeItem = {
47 | val label = t.rootLabel
48 | val description: String = showDescription(label)
49 | val subtrees: Seq[LogTree] = t.subForest
50 | val reactSubtrees = subtrees.map(buildTreeItem)
51 | val line = showAnnotations(label.annotations, showSuccess(label.success(), description), annotationShow)
52 |
53 | TreeItem(line, reactSubtrees:_*)
54 | }
55 |
56 | buildTreeItem(result.run.written)
57 | }
58 | }
--------------------------------------------------------------------------------
/src/main/scala/com/oranda/treelogui/LogTreeItemWithoutAnnotations.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import scalaz.Show
4 |
5 | /**
6 | * A [[com.oranda.treelogui.LogTreeItem LogTreeItem]] without annotations.
7 | *
8 | * Classes that extend this trait only need to override `result` to form
9 | * the `DescribedComputation`.
10 | */
11 | trait LogTreeItemWithoutAnnotations[Result] extends LogTreeItem[Nothing, Result] {
12 | override def annotationShow = new Show[Nothing] {
13 | override def shows(n: Nothing): String = ""
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/scala/com/oranda/treelogui/LogTreePerson.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import java.util.UUID
4 | import scalaz.Show
5 |
6 | case class PersonKey(uuid: UUID = UUID.randomUUID())
7 | case class Person(key: PersonKey, name: String)
8 |
9 | /**
10 | * An example of a [[com.oranda.treelogui.LogTreeItem LogTreeItem]] with annotations.
11 | * It greets two people. Each message is annotated with the UUID of the person greeted.
12 | */
13 | object LogTreePerson extends LogTreeItem[PersonKey, List[String]] {
14 | // The '~~' operator annotates the node on the left with the object on the right
15 | private def greet(person: Person) =
16 | s"Hello, ${person.name}" ~> s"Said hello to ${person.name}" ~~ person.key
17 |
18 | private def peopleToGreet() = Person(PersonKey(), "Lance") :: Person(PersonKey(), "Channing") :: Nil
19 |
20 | protected def annotationShow = new Show[PersonKey] {
21 | override def shows(k: PersonKey) = k.uuid.toString
22 | }
23 |
24 | import scalaz.Scalaz._
25 | override def result: DescribedComputation[List[String]] =
26 | peopleToGreet() ~>* ("Greeting everybody", greet)
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/com/oranda/treelogui/LogTreeQuadratic.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import treelog.QuadraticRootsExample._
4 |
5 | /**
6 | * A [[com.oranda.treelogui.LogTreeItem LogTreeItem]] that forms its `DescribedComputation`
7 | * using the algorithm in [[treelog.QuadraticRootsExample QuadraticRootsExample]].
8 | */
9 | object LogTreeQuadratic extends LogTreeItemWithoutAnnotations[Double] {
10 | override def result: DescribedComputation[Double] = root(Parameters(2, 5, 3))
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/scala/com/oranda/treelogui/TreeLogUIExamples.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import chandu0101.scalajs.react.components.ReactTreeView
4 |
5 | import japgolly.scalajs.react._
6 | import japgolly.scalajs.react.vdom.prefix_<^.{^, _}
7 | import org.scalajs.dom
8 | import org.scalajs.dom.document
9 |
10 | import scala.scalajs.js.annotation.JSExport
11 |
12 | /**
13 | * The ScalaJS layout for a page of [[com.oranda.treelogui.LogTreeItem LogTreeItem]] examples,
14 | * using a [[com.oranda.treelogui.UIExample UIExample]] for each one.
15 | */
16 | @JSExport
17 | object TreeLogUIExamples {
18 |
19 | object Style {
20 | def pageBody = Seq(^.marginLeft := "40px")
21 | def pageHeader = Seq(^.paddingBottom := "40px")
22 | def treeViewDemo = Seq(^.display := "flex")
23 |
24 | def selectedContent = Seq(^.alignSelf := "center", ^.margin := "0 40px")
25 | }
26 |
27 | case class State(content: String = "")
28 |
29 | class Backend(t: BackendScope[_, _]) {
30 |
31 | val quadraticTreeSidebarId = "quadraticTreeSidebar"
32 | val annotatedTreeSidebarId = "annotatedTreeSidebar"
33 |
34 | def onItemSelectQuadratic(item: String, parent: String, depth: Int): Callback =
35 | onItemSelect(item, parent, depth, quadraticTreeSidebarId)
36 |
37 | def onItemSelectAnnotated(item: String, parent: String, depth: Int): Callback =
38 | onItemSelect(item, parent, depth, annotatedTreeSidebarId)
39 |
40 | def onItemSelect(item: String, parent: String, depth: Int, elementId: String): Callback = {
41 | val content =
42 | s"""Selected Item: $item
43 | |Its Parent : $parent
44 | |Its depth: $depth
45 | """.stripMargin
46 | Callback(dom.document.getElementById(elementId).innerHTML = content)
47 | }
48 |
49 | def render = {
50 | <.div(Style.pageBody)(
51 | <.h2(Style.pageHeader)("TreeLog UI using ReactTreeView"),
52 | UIExample("Quadratic Roots Tree", "The log tree of calculating the roots of 2x² + 5x + 3.")(
53 | <.div(Style.treeViewDemo)(
54 | ReactTreeView(
55 | root = LogTreeQuadratic.treeItem,
56 | openByDefault = true,
57 | onItemSelect = onItemSelectQuadratic _,
58 | showSearchBox = true
59 | ),
60 | <.strong(^.id := quadraticTreeSidebarId, Style.selectedContent)
61 | )
62 | ),
63 | UIExample("Annotated Tree", "The log tree of sending greetings to two people. " +
64 | "Each log message is annotated with the UUID of the person greeted.")(
65 | <.div(Style.treeViewDemo)(
66 | ReactTreeView(
67 | root = LogTreePerson.treeItem,
68 | openByDefault = true,
69 | onItemSelect = onItemSelectAnnotated _,
70 | showSearchBox = true
71 | ),
72 | <.strong(^.id := annotatedTreeSidebarId, Style.selectedContent)
73 | )
74 | )
75 | )
76 | }
77 | }
78 |
79 | val component = ReactComponentB[Unit]("TreeLogUIExamples")
80 | .initialState(State())
81 | .renderBackend[Backend]
82 | .buildU
83 |
84 | def apply() = component()
85 |
86 | @JSExport
87 | def main(): Unit = ReactDOM.render(TreeLogUIExamples(), document.getElementById("container"))
88 | }
--------------------------------------------------------------------------------
/src/main/scala/com/oranda/treelogui/UIExample.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import japgolly.scalajs.react._
4 | import japgolly.scalajs.react.vdom.prefix_<^._
5 |
6 | import scala.scalajs.js
7 |
8 | /**
9 | * The ScalaJS UI layout for a single `LogTreeItem` example, including the tree
10 | * itself, a title, and a description.
11 | */
12 | object UIExample {
13 |
14 | object Style {
15 | val pageBodyContent = Seq(^.borderRadius := "2px",
16 | ^.boxShadow := "0 1px 4px rgba(223, 228, 228, 0.79)",
17 | ^.maxWidth := "1024px")
18 |
19 | val contentDemo = Seq(^.paddingBottom := "40px")
20 |
21 | val title = Seq(^.paddingBottom := "5px")
22 |
23 | val description = Seq(^.paddingBottom := "10px")
24 | }
25 |
26 | case class Backend($: BackendScope[Props, _]){
27 | def render(P: Props, C: PropsChildren) = {
28 | <.div(
29 | P.title.nonEmpty ?= <.h3(P.title, Style.title),
30 | P.description.nonEmpty ?= <.div(P.description, Style.description),
31 | <.div(Style.pageBodyContent)(
32 | <.div(Style.contentDemo, ^.key := "dan")(
33 | C
34 | )
35 | )
36 | )
37 | }
38 | }
39 |
40 | val component = ReactComponentB[Props]("treelogexample")
41 | .renderBackend[Backend]
42 | .build
43 |
44 | case class Props(title: String, description: String)
45 |
46 | def apply(
47 | title: String,
48 | description: String,
49 | ref: js.UndefOr[String] = "",
50 | key: js.Any = {})
51 | (children: ReactNode*) =
52 | component.set(key, ref)(Props(title, description), if (children.size == 1) children.head else children)
53 | }
--------------------------------------------------------------------------------
/src/main/scala/demo/components/CodeHighlight.scala:
--------------------------------------------------------------------------------
1 | package demo.components
2 |
3 | import japgolly.scalajs.react._
4 | import japgolly.scalajs.react.vdom.prefix_<^._
5 | import org.scalajs.dom
6 | import org.scalajs.dom.ext.PimpedNodeList
7 |
8 | object CodeHighlight {
9 |
10 | val component = ReactComponentB[String]("CodeHighLighter")
11 | .render_P(P => <.code(^.`class` := "scala", ^.padding := "20px", P))
12 | .configure(installSyntaxHighlighting)
13 | .build
14 |
15 | def installSyntaxHighlighting[P, S, B, N <: TopNode] =
16 | (_: ReactComponentB[P, S, B, N])
17 | .componentDidMount(_ => applySyntaxHighlight)
18 | .componentDidUpdate(_ => applySyntaxHighlight)
19 |
20 | def applySyntaxHighlight = Callback {
21 | import scala.scalajs.js.Dynamic.{global => g}
22 | val nodeList = dom.document.querySelectorAll("code").toArray
23 | nodeList.foreach(n => g.hljs.highlightBlock(n))
24 | }
25 |
26 | def apply(code: String) = component(code)
27 | }
--------------------------------------------------------------------------------
/src/main/scala/treelog/LogTreeLabel.scala:
--------------------------------------------------------------------------------
1 | package treelog
2 |
3 | import scalaz.Equal
4 |
5 | sealed trait LogTreeLabel[Annotation] extends Product with Serializable {
6 | def success(): Boolean
7 | def fold[T](f: DescribedLogTreeLabel[Annotation] ⇒ T, g: UndescribedLogTreeLabel[Annotation] ⇒ T): T
8 | def annotations: Set[Annotation]
9 | }
10 |
11 | final case class DescribedLogTreeLabel[Annotation](description: String, success: Boolean, annotations: Set[Annotation] = Set[Annotation]()) extends LogTreeLabel[Annotation] {
12 | def fold[T](f: DescribedLogTreeLabel[Annotation] ⇒ T, g: UndescribedLogTreeLabel[Annotation] ⇒ T) = f(this)
13 | }
14 |
15 | final case class UndescribedLogTreeLabel[Annotation](success: Boolean, annotations: Set[Annotation] = Set[Annotation]()) extends LogTreeLabel[Annotation] {
16 | def fold[T](f: DescribedLogTreeLabel[Annotation] ⇒ T, g: UndescribedLogTreeLabel[Annotation] ⇒ T) = g(this)
17 | }
18 |
19 | object LogTreeLabel {
20 | implicit def LogTreeLabelEqual[A]: Equal[LogTreeLabel[A]] = new Equal[LogTreeLabel[A]] {
21 | def equal(a1: LogTreeLabel[A], a2: LogTreeLabel[A]): Boolean = a1 == a2
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/scala/treelog/LogTreeSyntax.scala:
--------------------------------------------------------------------------------
1 | package treelog
2 |
3 | import scalaz.Tree.{Leaf, Node}
4 | import scalaz._
5 | import scalaz.syntax.traverse._
6 |
7 | /**
8 | * See the [[treelog]] package documentation for a brief introduction to treelog and also,
9 | * [[https://github.com/lancewalton/treelog#using-treelog---examples examples on GitHub]] to get started.
10 | *
11 | * This trait provides syntax for manipulating `DescribedComputations`. Either:
12 | *
13 | * - extend this trait, or
14 | * - define an object with the appropriate Annotation type and import on demand
15 | *
16 | */
17 | trait LogTreeSyntax[Annotation] {
18 | type LogTree = Tree[LogTreeLabel[Annotation]]
19 | type LogTreeWriter[V] = Writer[LogTree, V]
20 | type DescribedComputation[V] = EitherT[LogTreeWriter, String, V]
21 |
22 | private val NilTree: LogTree = Leaf(UndescribedLogTreeLabel(true))
23 |
24 | implicit val logTreeMonoid = new Monoid[LogTree] {
25 |
26 | val zero = NilTree
27 |
28 | def append(augend: LogTree, addend: ⇒ LogTree): LogTree =
29 | (augend, addend) match {
30 | case (NilTree, r) ⇒ r
31 |
32 | case (l, NilTree) ⇒ l
33 |
34 | case (Node(leftLabel: UndescribedLogTreeLabel[Annotation], leftChildren), Node(rightLabel: UndescribedLogTreeLabel[Annotation], rightChildren)) ⇒
35 | Node(UndescribedLogTreeLabel(leftLabel.success && rightLabel.success, leftLabel.annotations ++ rightLabel.annotations), leftChildren ++ rightChildren)
36 |
37 | case (Node(leftLabel: UndescribedLogTreeLabel[Annotation], leftChildren), rightNode @ Node(rightLabel, rightChildren)) ⇒
38 | Node(UndescribedLogTreeLabel(leftLabel.success && rightLabel.success, leftLabel.annotations), leftChildren :+ rightNode)
39 |
40 | case (leftNode @ Node(leftLabel, leftChildren), Node(rightLabel: UndescribedLogTreeLabel[Annotation], rightChildren)) ⇒
41 | Node(UndescribedLogTreeLabel(leftLabel.success && rightLabel.success, rightLabel.annotations), leftNode #:: rightChildren)
42 |
43 | case (leftNode: Tree[LogTreeLabel[Annotation]], rightNode: Tree[LogTreeLabel[Annotation]]) ⇒
44 | Node(UndescribedLogTreeLabel(leftNode.rootLabel.success && rightNode.rootLabel.success), Stream(leftNode, rightNode))
45 | }
46 | }
47 |
48 | private val eitherWriter = EitherT.monadListen[LogTreeWriter, LogTree, String]
49 |
50 | private def failure[V](description: String, tree: LogTree): DescribedComputation[V] =
51 | for {
52 | _ ← eitherWriter.tell(tree)
53 | err ← eitherWriter.left[V](description)
54 | } yield err
55 |
56 | private def success[V](value: V, tree: LogTree): DescribedComputation[V] =
57 | for {
58 | _ ← eitherWriter.tell(tree)
59 | res ← eitherWriter.right[V](value)
60 | } yield res
61 |
62 | def failureLog[V](dc: DescribedComputation[V]): DescribedComputation[V] = {
63 | val logTree = dc.run.written match {
64 | case Node(UndescribedLogTreeLabel(s, a), c) ⇒ Node(UndescribedLogTreeLabel(false, a), c)
65 | case Node(DescribedLogTreeLabel(d, s, a), c) ⇒ Node(DescribedLogTreeLabel(d, false, a), c)
66 | }
67 | dc.run.value match {
68 | case -\/(des) ⇒ failure(des, logTree)
69 | case \/-(a) ⇒ success(a, logTree)
70 | }
71 | }
72 |
73 | /**
74 | * Create a [[treelog.LogTreeSyntax.DescribedComputation]] representing a failure using the given `description` for both the log tree label and as
75 | * the content of the `value`, which will be a [[scalaz.-\/]].
76 | */
77 | def failure[V](description: String): DescribedComputation[V] = failure(description, Leaf(DescribedLogTreeLabel(description, false)))
78 |
79 | /**
80 | * Create a [[treelog.LogTreeSyntax.DescribedComputation]] representing a success with the given `value` (lifted into a [[scalaz.\/-]]) and the given
81 | * `description` in the log tree.
82 | */
83 | def success[V](value: V, description: String): DescribedComputation[V] =
84 | for {
85 | _ ← eitherWriter.tell(Leaf(DescribedLogTreeLabel(description, true, Set[Annotation]())))
86 | res ← eitherWriter.right[V](value)
87 | } yield res
88 |
89 | /**
90 | * Create a [[treelog.LogTreeSyntax.DescribedComputation]] representing a success with the given `value` (lifted into a [[scalaz.\/-]]) and no
91 | * description.
92 | */
93 | def success[V](value: V): DescribedComputation[V] = eitherWriter.right(value)
94 |
95 | /**
96 | * Syntax for lifting values into `DescribedComputations` and creating leaf nodes in the log tree.
97 | */
98 |
99 | implicit class LeafSyntax[V](value: V) {
100 |
101 | /**
102 | * Create a ''success'' [[treelog.LogTreeSyntax.DescribedComputation]] with `\/-(value)` as the value and
103 | * a success [[treelog.LogTreeLabel TreeNode]] with the given `description`.
104 | *
105 | * {{{
106 | * import treelog.LogTreeSyntaxWithoutAnnotations._
107 | * import scalaz.syntax.show._
108 | *
109 | * val leaf = 1 logSuccess "One"
110 | * println(result.run.value)
111 | * // Will print: \/-(1) - note that the 'right' means ''success''
112 | *
113 | * println(result.run.written.shows)
114 | * // Will print:
115 | * // One
116 | * }}}
117 | */
118 | def logSuccess(description: String): DescribedComputation[V] = success(value, description)
119 |
120 | /**
121 | * Sugar for [[treelog.LogTreeSyntax.LeafSyntax.logSuccess(String) logSuccess]]
122 | */
123 | def ~>(description: String): DescribedComputation[V] = logSuccess(description)
124 |
125 | /**
126 | * Create a ''success'' [[treelog.LogTreeSyntax.DescribedComputation]] with `\/-(value)` as the value and
127 | * a success [[treelog.LogTreeSyntax.DescribedComputation]] using the given `description` function to generate
128 | * a description for the tree node's label.
129 | *
130 | * {{{
131 | * import treelog.LogTreeSyntaxWithoutAnnotations._
132 | * import scalaz.syntax.show._
133 | *
134 | * val leaf = 1 logSuccess (x ⇒ s"One: $x")
135 | * println(result.run.value)
136 | * // Will print: \/-(1) - note that the 'right' means ''success''
137 | *
138 | * println(result.run.written.shows)
139 | * // Will print:
140 | * // One: 1
141 | * }}}
142 | */
143 | def logSuccess(description: V ⇒ String): DescribedComputation[V] = ~>(description(value))
144 |
145 | /**
146 | * Sugar for [[treelog.LogTreeSyntax.LeafSyntax.logSuccess(V ⇒ String) logSuccess]]
147 | */
148 | def ~>(description: V ⇒ String): DescribedComputation[V] = logSuccess(description(value))
149 |
150 | /**
151 | * Create a ''failure'' [[treelog.LogTreeSyntax.DescribedComputation]] with `-\/(description)` as the value and
152 | * a failure [[treelog.LogTreeSyntax.DescribedComputation]] with the given `description`.
153 | *
154 | * {{{
155 | * import treelog.LogTreeSyntaxWithoutAnnotations._
156 | * import scalaz.syntax.show._
157 | *
158 | * val leaf = 1 ~>! "One"
159 | * println(result.run.value)
160 | * // Will print: -\/("One") - note that the 'left' means ''failure'', and the contained value is the description, not the 1.
161 | *
162 | * println(result.run.written.shows)
163 | * // Will print:
164 | * // Failed: One
165 | * }}}
166 | */
167 | def logFailure(description: String): DescribedComputation[V] = failure(description)
168 |
169 | /**
170 | * Sugar for [[treelog.LogTreeSyntax.LeafSyntax.logFailure(String) logFailure]]
171 | */
172 | def ~>!(description: String): DescribedComputation[V] = logFailure(description)
173 |
174 | /**
175 | * Create a ''failure'' [[treelog.LogTreeSyntax.DescribedComputation]] using the given `description` function to
176 | * generate a description for the tree node's label and for the `DescribedComputations` value (i.e.
177 | * the value will be `\/-(description(value))`.
178 | *
179 | * {{{
180 | * import treelog.LogTreeSyntaxWithoutAnnotations._
181 | * import scalaz.syntax.show._
182 | *
183 | * val leaf = 1 logFailure (x ⇒ s"One - $x")
184 | * println(result.run.value)
185 | * // Will print: -\/("One") - note that the 'left' means ''failure'', and the contained value is the description, not the 1.
186 | *
187 | * println(result.run.written.shows)
188 | * // Will print:
189 | * // Failed: One - 1
190 | * }}}
191 | */
192 | def logFailure(description: V ⇒ String): DescribedComputation[V] = logFailure(description(value))
193 |
194 | /**
195 | * Sugar for [[treelog.LogTreeSyntax.LeafSyntax.logFailure(V ⇒ String) logFailure]]
196 | */
197 | def ~>!(description: V ⇒ String): DescribedComputation[V] = logFailure(description)
198 | }
199 |
200 | /**
201 | * Syntax for allowing annotations to be added to log tree nodes.
202 | *
203 | * The best way to see how this syntax works is to take a look at the
204 | * [[https://github.com/lancewalton/treelog#annotations annotations example]] on GitHub.
205 | *
206 | * Here is a short example:
207 | *
208 | * {{{
209 | * import scalaz.syntax.show._
210 | *
211 | * val syntax = new LogTreeSyntax[String] {}
212 | * import syntax._
213 | *
214 | * val result = 1 ~> "One" ~~ Set("Annotating with a string", "And another")
215 | * println(result.run.value)
216 | * // Will print: \/-(1) - note that the 'right' means ''success''
217 | *
218 | * println(result.run.written.shows)
219 | * // Will print:
220 | * // One - [Annotating with a string, And another]
221 | * }}}
222 | */
223 | implicit class AnnotationsSyntax[V](w: DescribedComputation[V]) {
224 |
225 | /**
226 | * Annotate a [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]].
227 | */
228 | def annotateWith(annotations: Set[Annotation]): DescribedComputation[V] = {
229 | val newTree = w.run.written match {
230 | case Node(l: DescribedLogTreeLabel[Annotation], c) ⇒ Node(l.copy(annotations = l.annotations ++ annotations), c)
231 | case Node(l: UndescribedLogTreeLabel[Annotation], c) ⇒ Node(l.copy(annotations = l.annotations ++ annotations), c)
232 | }
233 |
234 | w.run.value match {
235 | case -\/(error) ⇒ failure(error, newTree)
236 | case \/-(value) ⇒ success(value, newTree)
237 | }
238 | }
239 |
240 | /**
241 | * Sugar for [[treelog.LogTreeSyntax.AnnotationsSyntax.annotateWith(Set[Annotation]) annotateWith(annotations)]]
242 | */
243 | def ~~(annotations: Set[Annotation]): DescribedComputation[V] = annotateWith(annotations)
244 |
245 | /**
246 | * Equivalent to `~~ annotation`
247 | */
248 | def annotateWith(annotation: Annotation): DescribedComputation[V] = ~~(Set(annotation))
249 |
250 | /**
251 | * Syntactic sugar equivalent to [[treelog.LogTreeSyntax.AnnotationsSyntax.annotateWith(Annotation) annotateWith(annotation)]]
252 | */
253 | def ~~(annotation: Annotation): DescribedComputation[V] = annotateWith(annotation)
254 |
255 |
256 | /**
257 | * Get the union of all annotations in the log tree of the [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]].
258 | */
259 | def allAnnotations: Set[Annotation] = {
260 | def recurse(tree: LogTree, accumulator: Set[Annotation]): Set[Annotation] = tree.subForest.foldLeft(accumulator ++ tree.rootLabel.annotations)((acc, child) ⇒ recurse(child, acc))
261 | recurse(w.run.written, Set())
262 | }
263 | }
264 |
265 | /**
266 | * Syntax for treating booleans as indicators of success or failure in a computation.
267 | *
268 | * The simplest usage is something like: `myBoolean ~>? "Is my boolean true?"`. The 'value'
269 | * and log tree of the returned [[treelog.LogTreeSyntax.DescribedComputation]] will indicate success or failure
270 | * depending on the value of `myBoolean`.
271 | */
272 | implicit class BooleanSyntax(b: Boolean) {
273 |
274 | /**
275 | * Use the same description whether the boolean is `true` or `false`.
276 | * Equivalent to `~>?(description, description)`
277 | */
278 | def ~>?(description: String): DescribedComputation[Boolean] = ~>?(description, description)
279 |
280 | /**
281 | * Use different descriptions for the `true` and `false` cases. Note that unlike `'if'`
282 | * the `false` / failure description is the first parameter and the `true` / success
283 | * description is the second parameter. This is to maintain consistency with [[treelog.LogTreeSyntax.OptionSyntax OptionSyntax]]
284 | * and [[treelog.LogTreeSyntax.EitherSyntax EitherSyntax]].
285 | *
286 | * If the boolean is `true` the 'value' of the returned DescribedComputation will be `\/-(true)`,
287 | * otherwise, the 'value' will be `-\/(description)`.
288 | */
289 | def ~>?(failureDescription: ⇒ String, successDescription: ⇒ String): DescribedComputation[Boolean] =
290 | if (b) success(true, successDescription) else failure(failureDescription)
291 | }
292 |
293 | /**
294 | * Syntax for treating `Options` as indicators of success or failure in a computation.
295 | *
296 | * The simplest usage is something like: `myOption ~>? "Do I have Some?"`. The 'value'
297 | * and log tree of the returned [[treelog.LogTreeSyntax.DescribedComputation]] will indicate success or failure
298 | * depending on the value of `myOption`.
299 | */
300 | implicit class OptionSyntax[V](option: Option[V]) {
301 |
302 | /**
303 | * Use the same description whether the Option is `Some` or `None`.
304 | * Equivalent to `log(description, description)`
305 | */
306 | def log(description: String): DescribedComputation[V] = log(description, description)
307 |
308 | /**
309 | * Sugar for [[treelog.LogTreeSyntax.OptionSyntax.log(String) log(String)]]
310 | */
311 | def ~>?(description: String): DescribedComputation[V] = log(description)
312 |
313 | /**
314 | * Use different descriptions for the `Some` and `None` cases.
315 | *
316 | * If the option is `Some(x)` the 'value' of the returned DescribedComputation will be `\/-(x)`,
317 | * otherwise, the 'value' will be `-\/(noneDescription)`.
318 | */
319 | def log(noneDescription: ⇒ String, someDescription: ⇒ String): DescribedComputation[V] = ~>?(noneDescription, _ ⇒ someDescription)
320 |
321 | /**
322 | * Sugar for [[treelog.LogTreeSyntax.OptionSyntax.log(String, String) log(String, String)]]
323 | */
324 | def ~>?(noneDescription: ⇒ String, someDescription: ⇒ String): DescribedComputation[V] = log(noneDescription, someDescription)
325 |
326 | /**
327 | * Use different descriptions for the `Some` and `None` cases, providing the boxed `Some`
328 | * value to the function used to produce the description for the `Some` case, so that it can be included in the
329 | * description if you wish.
330 | *
331 | * If the option is `Some(x)` the 'value' of the returned DescribedComputation will be `\/-(x)`,
332 | * otherwise, the 'value' will be `-\/(noneDescription)`.
333 | */
334 | def log(noneDescription: ⇒ String, someDescription: V ⇒ String): DescribedComputation[V] =
335 | option map { a ⇒ success(a, someDescription(a)) } getOrElse failure(noneDescription)
336 |
337 | /**
338 | * Sugar for [[treelog.LogTreeSyntax.OptionSyntax.log() log(String, String)]]
339 | */
340 | def ~>?(noneDescription: ⇒ String, someDescription: V ⇒ String): DescribedComputation[V] = log(noneDescription, someDescription)
341 |
342 | /**
343 | * Return a default [[treelog.LogTreeSyntax.DescribedComputation]] if `option` is a `None`.
344 | *
345 | * If the option is `Some(x)` the 'value' of the returned DescribedComputation will be `\/-(Some(x))`,
346 | * otherwise, the returned [[treelog.LogTreeSyntax.DescribedComputation]] will be `dflt`.
347 | */
348 | def ~>|[B](f: V ⇒ DescribedComputation[B], dflt: ⇒ DescribedComputation[Option[B]]): DescribedComputation[Option[B]] =
349 | option.map(f).map((v: DescribedComputation[B]) ⇒ v.map(w ⇒ Option(w))) getOrElse dflt
350 | }
351 |
352 | /**
353 | * Syntax for treating `scalaz.\/` as signifiers of success or failure in a computation.
354 | *
355 | * The simplest usage is something like: `myEither ~>? "Do I have the right?"`. The 'value'
356 | * and log tree of the returned [[treelog.LogTreeSyntax.DescribedComputation]] will indicate success or failure
357 | * depending on the value of `myEither`.
358 | */
359 | implicit class EitherSyntax[V](either: \/[String, V]) {
360 |
361 | /**
362 | * Use different descriptions depending on whether `either` is a `\/-` or a `-\/`.
363 | */
364 | def ~>?(leftDescription: String ⇒ String, rightDescription: ⇒ String): DescribedComputation[V] =
365 | ~>?(leftDescription, _ ⇒ rightDescription)
366 |
367 | /**
368 | * Use the same description regardless of whether `either` is a `\/-` or a `-\/`.
369 | * Equivalent to: `~>?((error: String) ⇒ s"$description - $error", description)`
370 | */
371 | def ~>?(description: String): DescribedComputation[V] = ~>?((error: String) ⇒ s"$description - $error", description)
372 |
373 | /**
374 | * Use the given description if `either` is a `\/-`. If `either` is
375 | * `-\/(message)`, use `message` as the description.
376 | */
377 | def ~>?(description: V ⇒ String): DescribedComputation[V] = ~>?((error: String) ⇒ error, description)
378 |
379 | /**
380 | * Use the given functions to provide descriptions depending on whether `either` is a
381 | * `\/-` or `-\/`
382 | */
383 | def ~>?(leftDescription: String ⇒ String, rightDescription: V ⇒ String): DescribedComputation[V] =
384 | either.fold(error ⇒ failure(leftDescription(error)), a ⇒ success(a, rightDescription(a)))
385 | }
386 |
387 | /**
388 | * Syntax for labeling or creating new branches in a log tree given a description.
389 | */
390 | implicit class BranchLabelingSyntax(description: String) {
391 |
392 | /**
393 | * Create a new branch given a monadic, traversable 'container' `F[DescribedComputation[Value]]`, 'sequence' it
394 | * to create a `DescribedComputation[F[Value]]`, and give the new `DescribedComputation's` log tree a
395 | * new root node, with the given `description` and whose children are the trees in the
396 | * `F[DescribedComputation[Value]]`.
397 | *
398 | * For example, if we evaluate this method with `F` instantiated as `List`, we would turn a
399 | * `List[DescribedComputation[Value]]` into a `DescribedComputation[List[Value]]`, such that the
400 | * `List[Value]` which is the result's 'value' is obtained by extracting the 'value' from each
401 | * [[treelog.LogTreeSyntax.DescribedComputation]] in the `describedComputations` parameter. Likewise, the child nodes
402 | * of the returned log tree root node are obtained by extracting the log tree from each of the `describedComputations`.
403 | *
404 | * The ''success'' status of the returned `DescribedComputations` log tree is `true` if all of the children
405 | * are successful. It is `false` otherwise.
406 | */
407 | def ~<[F[_] : Monad : Traverse, V](describedComputations: F[DescribedComputation[V]]): DescribedComputation[F[V]] =
408 | ~<+(describedComputations, (x: F[V]) ⇒ x)
409 |
410 | /**
411 | * As ~< but folding over the resulting F[Value] to yield R and return a DescribedComputation[R] with all the logs.
412 | *
413 | * For example, given l = List[DescribedComputation[Int]], and f = List[Int] ⇒ Int (say summing the list), then
414 | * `"Sum" ~<+(l, f)` would return a DescribedComputation containing the sum of the elements of the list.
415 | */
416 | def ~<+[F[_] : Monad : Traverse, V, R](describedComputations: F[DescribedComputation[V]], f: F[V] ⇒ R): DescribedComputation[R] = {
417 | val monad = implicitly[Monad[F]]
418 | val parts = monad.map(describedComputations)(m ⇒ (m.run.value, m.run.written))
419 |
420 | val children = monad.map(parts)(_._2).toList
421 | val branch = Node(
422 | DescribedLogTreeLabel(
423 | description,
424 | allSuccessful(children),
425 | Set[Annotation]()),
426 | children.toStream)
427 |
428 | describedComputations.sequence.run.value match {
429 | case -\/(_) ⇒ failure(description, branch)
430 | case \/-(v) ⇒ success(f(v), branch)
431 | }
432 | }
433 |
434 | /**
435 | * If `dc` has a log tree with an undescribed root node, give the root node the `description` but otherwise
436 | * leave it unchanged. If the log tree has a described root node, create a new root node above the existing one and give the
437 | * new root node the `description`. In both cases preserve the `value` and success/failure status.
438 | */
439 | def ~<[V](dc: DescribedComputation[V]): DescribedComputation[V] =
440 | dc.run.value match {
441 | case -\/(_) ⇒ failure(description, branchHoister(dc.run.written, description))
442 | case \/-(value) ⇒ success(value, branchHoister(dc.run.written, description))
443 | }
444 |
445 | private def branchHoister(tree: LogTree, description: String): LogTree = tree match {
446 | case Node(l: UndescribedLogTreeLabel[Annotation], children) ⇒ Node(DescribedLogTreeLabel(description, allSuccessful(children), l.annotations), children)
447 | case Node(l: DescribedLogTreeLabel[Annotation], children) ⇒ Node(DescribedLogTreeLabel(description, allSuccessful(List(tree))), Stream(tree))
448 | }
449 |
450 | private def allSuccessful(trees: Iterable[LogTree]) = trees.forall(_.rootLabel.success())
451 | }
452 |
453 | implicit class FoldSyntax[V](values: Iterable[V]) {
454 |
455 | /**
456 | * Starting with a given value and description, foldleft over an Iterable of values and 'add' them, describing each 'addition'.
457 | */
458 | def ~>/[R](description: String, initial: DescribedComputation[R], f: (R, V) ⇒ DescribedComputation[R]): DescribedComputation[R] = {
459 | @scala.annotation.tailrec
460 | def recurse(remainingValues: Iterable[V], partialResult: DescribedComputation[R]): DescribedComputation[R] =
461 | if (remainingValues.isEmpty) partialResult
462 | else
463 | partialResult.run.value match {
464 | case -\/(m) ⇒ partialResult
465 | case \/-(_) ⇒ recurse(remainingValues.tail, partialResult.flatMap(p ⇒ f(p, remainingValues.head)))
466 | }
467 |
468 | description ~< recurse(values, initial)
469 | }
470 | }
471 |
472 | /**
473 | * Syntax for dealing with traversable monads
474 | */
475 | implicit class TraversableMonadSyntax[F[_]: Monad: Traverse, V](values: F[V]) {
476 |
477 | /**
478 | * This method is syntactic sugar for `description ~< monad.map(values)(f)`
479 | */
480 | def ~>*[B](description: String, f: V ⇒ DescribedComputation[B]): DescribedComputation[F[B]] = description ~< implicitly[Monad[F]].map(values)(f)
481 | }
482 |
483 | /**
484 | * Syntax for labeling root nodes of trees in `DescribedComputions`
485 | */
486 | implicit class LabellingSyntax[V](w: DescribedComputation[V]) {
487 |
488 | /**
489 | * This method is syntactic sugar for `description ~< w`
490 | */
491 | def ~>(description: String) = description ~< w
492 | }
493 |
494 | implicit def logTreeShow(implicit annotationShow: Show[Annotation]) = new Show[LogTree] {
495 |
496 | override def shows(t: LogTree) = toList(t).map(line ⇒ " " * line._1 + line._2).mkString(System.getProperty("line.separator"))
497 |
498 | private def toList(tree: LogTree, depth: Int = 0): List[(Int, String)] =
499 | line(depth, tree.rootLabel) :: tree.subForest.flatMap(toList(_, depth + 1)).toList
500 |
501 | private def line(depth: Int, label: LogTreeLabel[Annotation]) = (depth, showAnnotations(label.annotations, showSuccess(label.success(), showDescription(label))))
502 |
503 | private def showAnnotations(annotations: Set[Annotation], line: String) =
504 | if (annotations.isEmpty) line else line + " - [" + annotations.map(annotationShow.show).mkString(", ") + "]"
505 |
506 | private def showDescription(label: LogTreeLabel[Annotation]) = label.fold(_.description, _ ⇒ "No Description")
507 |
508 | private def showSuccess(success: Boolean, s: String) = if (success) s else "Failed: " + s
509 | }
510 |
511 | type SerializableDescribedComputation[V] = (\/[String, V], SerializableTree[Annotation])
512 |
513 | def toSerializableForm[V](dc: DescribedComputation[V]): SerializableDescribedComputation[V] = {
514 | def transform(tree: LogTree): SerializableTree[Annotation] = SerializableTree(tree.rootLabel, tree.subForest.map(transform).toList)
515 | Tuple2(dc.run.value, transform(dc.run.written))
516 | }
517 |
518 | def fromSerializableForm[V](sdc: SerializableDescribedComputation[V]): DescribedComputation[V] = {
519 | def transform(tree: SerializableTree[Annotation]): LogTree = Node(tree.label, tree.children.map(transform).toStream)
520 | sdc._1.fold(m ⇒ failure(m, transform(sdc._2)), v ⇒ success(v, transform(sdc._2)))
521 | }
522 | }
523 |
--------------------------------------------------------------------------------
/src/main/scala/treelog/LogTreeSyntaxWithoutAnnotations.scala:
--------------------------------------------------------------------------------
1 | package treelog
2 |
3 | import scalaz.Show
4 |
5 | object LogTreeSyntaxWithoutAnnotations extends LogTreeSyntax[Nothing] {
6 | implicit object NothingShow extends Show[Nothing] {
7 | override def shows(n: Nothing): String = ""
8 | }
9 | }
--------------------------------------------------------------------------------
/src/main/scala/treelog/QuadraticRootsExample.scala:
--------------------------------------------------------------------------------
1 | package treelog
2 |
3 | import treelog.LogTreeSyntaxWithoutAnnotations._
4 | import scalaz._
5 | import Scalaz._
6 |
7 | object QuadraticRootsExample extends App {
8 |
9 | private def showDescription(label: LogTreeLabel[Nothing]) = label.fold(_.description, _ ⇒ "No Description")
10 |
11 | case class Parameters(a: Double, b: Double, c: Double)
12 |
13 | // Roots are real
14 | println("Success case:")
15 |
16 | // Roots are complex
17 | println("Failure case:")
18 |
19 | def root(parameters: Parameters) = {
20 | "Extracting root" ~< {
21 | for {
22 | num ← numerator(parameters) ~> "Calculating Numerator"
23 | den ← denominator(parameters) ~> "Calculating Denominator"
24 | root ← (num / den) ~> ("Got root = numerator / denominator: " + _)
25 | } yield root
26 | }
27 | }
28 |
29 | private def numerator(parameters: Parameters) =
30 | for {
31 | det ← determinant(parameters)
32 | sqrtDet ← sqrtDeterminant(det)
33 | b ← parameters.b ~> ("Got b: " + _)
34 | minusB ← -b ~> ("Got -b: " + _)
35 | sum ← (minusB + sqrtDet) ~> ("Got -b + sqrt(determinant): " + _)
36 | } yield sum
37 |
38 | private def sqrtDeterminant(det: Double) =
39 | "Calculating sqrt(determinant)" ~< {
40 | for {
41 | _ ← if (det >= 0) det ~> (d ⇒ s"Determinant ($d) is >= 0") else det ~>! (d ⇒ s"Determinant ($d) is < 0")
42 | sqrtDet ← Math.sqrt(det) ~> ("Got sqrt(determinant): " + _)
43 | } yield sqrtDet
44 | }
45 |
46 | private def denominator(parameters: Parameters) =
47 | for {
48 | a ← parameters.a ~> ("Got a: " + _)
49 | twoA ← (2 * a) ~> ("Got 2a: " + _)
50 | } yield twoA
51 |
52 | private def determinant(parameters: Parameters) =
53 | "Calculating Determinant" ~< {
54 | for {
55 | bSquared ← bSquared(parameters)
56 | fourac ← fourac(parameters)
57 | determinant ← (bSquared - fourac) ~> ("Got b^2 - 4ac: " + _)
58 | } yield determinant
59 | }
60 |
61 | private def bSquared(parameters: Parameters) =
62 | "Calculating b^2" ~< {
63 | for {
64 | b ← parameters.b ~> ("Got b: " + _)
65 | bSquared ← (b * b) ~> ("Got b^2: " + _)
66 | } yield bSquared
67 | }
68 |
69 | private def fourac(parameters: Parameters) =
70 | "Calculating 4ac" ~< {
71 | for {
72 | a ← parameters.a ~> ("Got a: " + _)
73 | c ← parameters.c ~> ("Got c: " + _)
74 | fourac ← (4 * a * c) ~> ("Got 4ac: " + _)
75 | } yield fourac
76 | }
77 | }
--------------------------------------------------------------------------------
/src/main/scala/treelog/SerializableTree.scala:
--------------------------------------------------------------------------------
1 | package treelog
2 |
3 | final case class SerializableTree[Annotation](label: LogTreeLabel[Annotation], children: List[SerializableTree[Annotation]])
4 |
--------------------------------------------------------------------------------
/src/main/scala/treelog/package.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * =Introduction=
3 | *
4 | * TreeLog enables logging as a tree structure so that comprehensive logging does not become incomprehensible.
5 | *
6 | * It is often necessary to understand exactly what happened in a computation, not just that it succeeded or failed, but what was actually done
7 | * and with what data. TreeLog produces a description of a computation (along with a result) as a hierarchical log of
8 | * computations that led to the result. The tree could be logged as text or stored in a database so that users can see
9 | * a detailed audit trail of the processing that has occurred for particular entities.
10 | *
11 | * Note that in the remainder of this document, results of producing log trees will be shown by rendering
12 | * the tree textually, but that is only one possible way.
13 | *
14 | * Nodes in the log tree can be annotated with important information for your program to use later. This is useful, for example, when you want to audit
15 | * a process that affects multiple entities, and you want to ensure that the audit trail is associated with each of the modified entities.
16 | *
17 | * =DescribedComputation=
18 | *
19 | * All of this works by ''fting'' the intermediate and final results of computations and the description of the steps into a type called [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]]
20 | * (declared in [[treelog.LogTreeSyntax LogTreeSyntax]]).
21 |
22 | * ==Some Simple Lifting==
23 | *
24 | * You can produce a [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]] very simply with many of the methods in [[treelog.LogTreeSyntax LogTreeSyntax]]. The simplest few are:
25 | *
26 | * {{{
27 | * // This is a concrete implementation of LogTreeSyntax that is provided for you
28 | * // to use if you don't need to use annotations (see later)
29 | * import treelog.LogTreeSyntaxWithoutAnnotations._
30 | *
31 | * val result1 = success(2 * 3, "Calculated product")
32 | * // result1 is now a DescribedComputation and carries the successful result and
33 | * // a single node tree telling us that the product was calculated. See below for how to
34 | * // extract these things.
35 | *
36 | * val result2 = failure("It's all wrong")
37 | * // result2 is now a DescribedComputation whose value and tree both tell us that things
38 | * // went wrong
39 | *
40 | * val result3 = (2 * 3) ~> "Calculated product"
41 | * // The same as result1
42 | *
43 | * val result4 = (2 * 3) ~> (p ⇒ "Calculated product: " + p)
44 | * // The same as result1, except the description in the tree node will be "Calculated product: 6"
45 | * }}}
46 | *
47 | * ''result3'' and ''result4'' above introduce the first pieces of syntax related to producing
48 | * ''DescribedComputations''. In this case it lifts the value into the [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]]
49 | * and creates a leaf node in the associated log tree. See [[treelog.LogTreeSyntax.LeafSyntax LeafSyntax]] for
50 | * related simple syntax for leaves.
51 | *
52 | * == Extracting the Result and Log ==
53 | *
54 | * When a computation result is ''lifted'' into a [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]] by one of the many methods
55 | * in the [[treelog.LogTreeSyntax LogTreeSyntax]] trait, it is possible to retrieve the ''value'' of the computation like this:
56 | *
57 | * {{{
58 | * import treelog.LogTreeSyntaxWithoutAnnotations._
59 | * val foo = 1 ~> "Here's one"
60 | * val value = foo.run.value
61 | * // value will now be equal to scalaz.\/-(1), which represents a successful computation.
62 | * }}}
63 | *
64 | * The ''value'' is a scalaz ''Either'' (scalaz.\/). Following the usual convention:
65 | *
66 | * - If it a ''left'' (-\/) then the computation is a failure.
67 | * - If it is a ''right'' (\/-), then the computation is a success.
68 | *
69 | *
70 | * Likewise, it is possible to retrieve the log tree like this:
71 | *
72 | * {{{
73 | * import treelog.LogTreeSyntaxWithoutAnnotations._
74 | * val foo = 1 ~> "Here's one"
75 | * val logtree = foo.run.written
76 | * // logtree will now be an instance of LogTree which is a type alias which in this case expands to:
77 | * // Tree[LogTreeLabel[Nothing]](DescribedLogTreeLabel[Nothing]("Here's one", true, Set[Nothing]())
78 | * // Where:
79 | * // - "Here's one" is the description provided in the declaration of foo
80 | * // - true indicates that the computation represented by the node was successful
81 | * // - the empty set represents the annotations specified for this node
82 | * }}}
83 | *
84 | * It may seem strange that both the ''value'' and the log tree provide indications of success and failure (the ''value''
85 | * through the use of ''scalaz.\/'', and the log tree with a ''boolean'' property in the [[treelog.LogTreeLabel LogTreeLabel]] label).
86 | * The reason for this is that part of a computation may fail (which we want to indicate in the log tree), but then a different strategy
87 | * is tried which succeeds leading to a successful overall result.
88 | *
89 | * == More Comprehensive Computations ==
90 | *
91 | * (An extended example of this kind of thing is the
92 | * [[https://github.com/lancewalton/treelog/blob/master/src/test/scala/QuadraticRootsExample.scala quadratic roots example on GitHub]])
93 | *
94 | * Generally, once a value has been lifted, it is a good idea to keep working with it in that form for as long
95 | * as possible before accessing the ''value'' and ''written'' properties. Think monadically!
96 | * The examples above show a value being lifted into the DescribedComputation. To continue to work monadically,
97 | * for-comprehensions come into play:
98 | *
99 | * {{{
100 | * import treelog.LogTreeSyntaxWithoutAnnotations._
101 | * import scalaz.syntax.show._
102 | *
103 | * val result = for {
104 | * foo ← 1 ~> ("foo = " + _) // Using the overload of ~> that gives us the ''value''
105 | * bar ← 2 ~> ("bar = " + _) // so that we can include it in the log messages
106 | * foobar ← (foo + bar) ~> ("foobar = " + _)
107 | * } yield foobar
108 | *
109 | * println(result.run.value)
110 | * // Will print \/-(3) (i.e. a successful computation of 1 + 2)
111 | *
112 | * println(result.run.written.shows)
113 | * // Will print:
114 | * // No description
115 | * // foo = 1
116 | * // bar = 2
117 | * // foobar = 3
118 | * }}}
119 | *
120 | * (For those struggling with the full power of for-comprehensions, I suggest turning the above example into its unsugared flatmap/map form
121 | * to see what is going on. The central point is that ''foo'' will have the value 1, ''bar'' will have the value 2, and
122 | * ''foobar'' will have the value 3; the monadic stuff all happens in the background.)
123 | *
124 | * == Non-Leaf Nodes ==
125 | *
126 | * Non-leaf nodes (branches) are created explicitly by the developer or implicitly by the [[treelog.LogTreeSyntax LogTreeSyntax]] under
127 | * various conditions.
128 | *
129 | * The log tree above has a root node with 'No description' and three child (leaf) nodes with descriptions obviously obtained from the
130 | * arguments to the right of the ''~>'' operators in the for-comprehension. This is because the three leaf nodes explicitly
131 | * created in that for-comprehension need to be placed somewhere while the log tree is produced. An obvious thing to do was to make them
132 | * child nodes of a branch, which [[treelog.LogTreeSyntax LogTreeSyntax]] does, using some rules for when to create a new branch to
133 | * contain existing children and when to just add new children to an existing branch.
134 | *
135 | * However, at the time the branch is created there is no ready description available for it, hence the "No description" text when the tree is
136 | * shown using the ''scalaz.Show'' defined for it. Producing a hierarchical log isn't much use if we
137 | * can't describe the non-leaf elements. We can provide a description in two ways (this looks ugly, but read and it will get more elegant…):
138 | *
139 | * {{{
140 | * import treelog.LogTreeSyntaxWithoutAnnotations._
141 | * import scalaz.syntax.show._
142 | *
143 | * val result = for {
144 | * foo ← 1 ~> ("foo = " + _) // Using the overload of ~> that gives us the ''value''
145 | * bar ← 2 ~> ("bar = " + _) // so that we can include it in the log messages
146 | * foobar ← (foo + bar) ~> ("foobar = " + _)
147 | * } yield foobar
148 | *
149 | *
150 | * val resultWithDescription1 = result ~> "Adding up"
151 | * println(resultWithDescription1.run.written.shows)
152 | * // Will print:
153 | * // Adding up
154 | * // foo = 1
155 | * // bar = 2
156 | * // foobar = 3
157 | *
158 | * val resultWithDescription2 = "Adding up" ~< result
159 | * println(resultWithDescription2.run.written.shows)
160 | * // Will also print:
161 | * // Adding up
162 | * // foo = 1
163 | * // bar = 2
164 | * // foobar = 3
165 | * }}}
166 | *
167 | * The first approach (''resultWithDescription1'' using ''~gt;'') will generally be used when a method/function used to provide an intermediate result
168 | * in the middle of a for-comprehension returns an undescribed root node. Then the code flows quite nicely thus:
169 | *
170 | * {{{
171 | * val result = for {
172 | * something ← doSomething() ~> "Something has been done"
173 | * more ← doMore(something) ~> "More has been done"
174 | * } yield more
175 | * }}}
176 | *
177 | * Here, ''doSomething()'' and ''doMore(...)'' return DescribedComputations carrying a log tree with an undescribed root node.
178 | * They have been given descriptions in the above for-comprehension.
179 | *
180 | * The second approach (''resultWithDescription2'' using ''~<'') will generally be used when a for-comprehension yields a
181 | * [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]] (which will always have a log tree with an undescribed root node if the for-comprehension has more than
182 | * one generator), and you want to immediately give the root node a description. In this case, it is more natural to write:
183 | *
184 | * {{{
185 | * val result = "Adding up" ~< {
186 | * for {
187 | * foo ← 1 ~> ("foo = " + _)
188 | * bar ← 2 ~> ("bar = " + _)
189 | * foobar ← (foo + bar) ~> ("foobar = " + _)
190 | * } yield foobar
191 | * }
192 | * }}}
193 | *
194 | * Both of these approaches are demonstrated in the
195 | * [[https://github.com/lancewalton/treelog/blob/master/src/test/scala/QuadraticRootsExample.scala quadratic roots example]]. There is no good
196 | * reason for mixing the two approaches in that example, other than for purposes of demonstration.
197 | *
198 | * ''~<'' works not only for [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]], but for any ''F[DescribedComputation]'' as long as
199 | * ''F'' has a ''scalaz.Monad'' and a ''scalaz.Traverse'' defined and available in implicit scope.
200 | * See [[treelog.LogTreeSyntax.BranchLabelingSyntax BranchLabelingSyntax]].
201 | *
202 | * = Special Lifting =
203 | *
204 | * == Boolean, Option and \/ ==
205 | *
206 | * ''Boolean'', ''Option'' and ''\/'' (scalaz's ''Either''), have some special syntax ''~>?'' associated with
207 | * them to allow ''true'', ''Some(.)'' and ''\/-(.)'' to be treated as successful computational outcomes, and
208 | * ''false'', ''None'' and ''-\/(.)'' to be treated as failure conditions. For example:
209 | *
210 | * {{{
211 | * val result = false ~>? "Doing a thing with a Boolean"
212 | * println(result.run.value)
213 | * // Will print -\/(Doing a thing with a Boolean) (note that it's a ''left'')
214 | *
215 | * println(result.run.written.shows)
216 | * // Will print:
217 | * // Failed: Doing a thing with a Boolean
218 | * }}}
219 | *
220 | * ''~>?'' is overloaded for each of the three types above to allow either a simple description to be given (as in the example above)
221 | * or for different descriptions to be given in the success versus failure case. Also, in the case of ''Option'' and ''\/'',
222 | * overloads are provided to pass the values contained. See [[treelog.LogTreeSyntax.BooleanSyntax BooleanSyntax]],
223 | * [[treelog.LogTreeSyntax.OptionSyntax OptionSyntax]], [[treelog.LogTreeSyntax.EitherSyntax EitherSyntax]].
224 | *
225 | * Note that it is easy to get drawn into always using this syntax for these three types. But sometimes, for example, a ''Boolean''
226 | * ''false'' does not indicate a failure in a computation and so ''~>?'' is not appropriate. Keep in mind that ''failure''
227 | * means that the computation will stop, whereas ''success'' will mean that the computation will continue.
228 | *
229 | * == Traversable Monads ==
230 | *
231 | * Suppose you have a ''List[A]'' and a function ''f: A ⇒ DescribedComputation[B]'', and you want to apply ''f(.)''
232 | * to each element of the list to produce ''DescribedComputations'' for each element. That's easy enough. But suppose you now want
233 | * to take all of the 'values' (''vs'') contained in the list of ''DescribedComputations'' thus produced, and create a new
234 | * [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]] whose value is ''vs'' and whose log tree is a branch with a description and whose children
235 | * are the log trees resulting from each application of ''f(.)''.
236 | *
237 | * We needed to do precisely that very often, so we wrote some syntax for it:
238 | *
239 | * {{{
240 | * import treelog.LogTreeSyntaxWithoutAnnotations._
241 | * import scalaz.syntax.show._
242 | * import scalaz.std.list._
243 | *
244 | * val result = List(1, 2, 3) ~>* ("Double the values", x ⇒ (x * 2) ~> (y ⇒ s"Double $x = $y"))
245 | *
246 | * println(result.run.value)
247 | * // Will print \/-(List(2, 4, 6))
248 | *
249 | * println(result.run.written.shows)
250 | * // Will print:
251 | * // Double the values
252 | * // Double 1 = 2
253 | * // Double 2 = 4
254 | * // Double 3 = 6
255 | * }}}
256 | *
257 | * This is particularly useful if there is a possibility that ''f(.)'' can produce ''DescribedComputations'' that represent failures,
258 | * because hoisting children into a branch of a log tree gives the branch a successful status only if all of the children are successful (this is true
259 | * of all syntax that does this). Hence:
260 | *
261 | * {{{
262 | * val result = List(1, 2, 3) ~>* ("All even", x ⇒ (x % 2 == 0) ~>? s"Testing if $x is even")
263 | *
264 | * println(result.run.value)
265 | * // Will print -\/(All even) - Notice that it's a 'left', meaning failure
266 | *
267 | * println(result.run.written.shows)
268 | * // Will print:
269 | * // Failed: All even
270 | * // Failed: Testing if 1 is even
271 | * // Testing if 2 is even
272 | * // Failed: Testing if 3 is even
273 | * }}}
274 | *
275 | * ''~>*'' works not only for ''List'', but for all kinds that have a ''scalaz.Monad'' and a ''scalaz.Traverse''
276 | * defined and available in implicit scope. See [[treelog.LogTreeSyntax.TraversableMonadSyntax TraversableMonadSyntax]].
277 | *
278 | * Another common thing you might want to do with a collection of DescribedComputations is retain their logs as children of a parent whose value
279 | * is some function of the child values. Here is an example of summing the result of several computations:
280 | *
281 | * {{{
282 | * val parts = List(1 ~> "One", 2 ~> "Two")
283 | * val summed = "Sum" ~<+ (parts, (bits: List[Int]) ⇒ bits.sum)
284 | *
285 | * println(summed.run.written.shows)
286 | * // Will print:
287 | * Sum
288 | * One
289 | * Two
290 | *
291 | * println(summed.run.value)
292 | * // Will print \/-(3)
293 | * }}}
294 | *
295 | * == Annotations ==
296 | *
297 | * Nodes in the log tree can be annotated with important information for your program to use later. This is useful, for example, when you want to audit
298 | * a process that affects multiple entities, and you want to make sure that the audit trail is associated with each of the modified entities. You can use
299 | * the annotation facility to carry the key (or something richer) for each modified entity.
300 | *
301 | * The ''~~'' operator (see [[treelog.LogTreeSyntax.AnnotationsSyntax AnnotationsSyntax]]) is provided for this purpose. It can be applied to
302 | * any [[treelog.LogTreeSyntax.DescribedComputation DescribedComputation]] and it will add the given annotation to the set of annotations at the current root node of the log
303 | * tree. Annotations can be of any type, but must all be of the same type for a particular log tree. You choose the type of annotations by instantiating
304 | * the 'Annotation' type parameter of [[treelog.LogTreeSyntax LogTreeSyntax]]
305 | *
306 | * Here is a simple example using ''Strings'' as the annotations type:
307 | *
308 | * {{{
309 | * val stringAnnotateableLogTreeSyntax = new treelog.LogTreeSyntax[String] {}
310 | * import stringAnnotateableLogTreeSyntax._
311 | * import scalaz.syntax.show._
312 | * import scalaz.std.string._
313 | *
314 | * val result = 1 ~> "This is the description" ~~ "This is the annotation"
315 | *
316 | * println(result.run.value)
317 | * // Will print \/-(1)
318 | *
319 | * println(result.run.written.shows)
320 | * // Will print:
321 | * // This is the description - ["This is the annotation"]
322 | * }}}
323 | *
324 | * See the [[https://github.com/lancewalton/treelog/blob/master/src/test/scala/AnnotationsExample.scala annotations example]]
325 | * for a more comprehensive example.
326 | */
327 | package object treelog
--------------------------------------------------------------------------------
/src/test/scala/com/oranda/treelogui/LogTreeItemSpec.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 |
4 | import chandu0101.scalajs.react.components.TreeItem
5 | import org.scalatest._
6 | import scalaz._
7 | import Scalaz._
8 |
9 | import org.scalatest.FlatSpec
10 |
11 | class LogTreeItemSpec extends FlatSpec {
12 |
13 | object LogTreeSimpleSum extends LogTreeItem[String, Int] {
14 | override def result: DescribedComputation[Int] = simpleSum
15 |
16 | protected def annotationShow = new Show[String] {
17 | override def shows(text: String) = text
18 | }
19 |
20 | def simpleSum: DescribedComputation[Int] =
21 | "Calculating sum" ~< {
22 | for {
23 | x ← 11 ~> ("x = " + _) ~~ "assignment of x"
24 | y ← 2 ~> ("y = " + _) ~~ "assignment of y"
25 | sum ← (x + y) ~> ("Sum is " + _) ~~ "summing x and y"
26 | } yield sum
27 | }
28 | }
29 |
30 | "A LogTreeItem with annotations" should "produce the expected TreeItem when treeItem is called" in {
31 | val expectedTreeItem =
32 | TreeItem("Calculating sum",
33 | TreeItem("x = 11 - [assignment of x]",
34 | TreeItem("y = 2 - [assignment of y]",
35 | TreeItem("Sum is 13 - [summing x and y]"))))
36 | assert(treesAreEqual(LogTreeSimpleSum.treeItem, expectedTreeItem))
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/test/scala/com/oranda/treelogui/LogTreeItemWithoutAnnotationsSpec.scala:
--------------------------------------------------------------------------------
1 | package com.oranda.treelogui
2 |
3 | import chandu0101.scalajs.react.components.TreeItem
4 | import org.scalatest._
5 | import scalaz._
6 | import treelog._
7 |
8 |
9 | class LogTreeItemWithoutAnnotationsSpec extends FlatSpec {
10 |
11 | object LogTreeSimpleSum extends LogTreeItemWithoutAnnotations[Int] {
12 | override def result: DescribedComputation[Int] = simpleSum
13 |
14 | def simpleSum: DescribedComputation[Int] =
15 | "Calculating sum" ~< {
16 | for {
17 | x ← 11 ~> ("x = " + _)
18 | y ← 2 ~> ("y = " + _)
19 | sum ← (x + y) ~> ("Sum is " + _)
20 | } yield sum
21 | }
22 | }
23 |
24 | "A LogTreeItem without annotations" should "produce the expected TreeItem when treeItem is called" in {
25 | val expectedTreeItem =
26 | TreeItem("Calculating sum",
27 | TreeItem("x = 11",
28 | TreeItem("y = 2",
29 | TreeItem("Sum is 13"))))
30 | assert(treesAreEqual(LogTreeSimpleSum.treeItem, expectedTreeItem))
31 | }
32 | }
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/test/scala/com/oranda/treelogui/package.scala:
--------------------------------------------------------------------------------
1 | package com.oranda
2 |
3 | import chandu0101.scalajs.react.components.TreeItem
4 |
5 | package object treelogui {
6 | def treesAreEqual(tree1: TreeItem, tree2: TreeItem): Boolean =
7 | tree1.item == tree2.item &&
8 | (tree1.children.zip(tree2.children)).forall(cpair => treesAreEqual(cpair._1, cpair._2))
9 | }
10 |
--------------------------------------------------------------------------------