Skip to content

Commit

Permalink
Specify the images path and configure the wrap limits
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasepe committed Sep 9, 2020
1 parent 36794b5 commit e2bd572
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 53 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2020-09-09
### Added
- 📝 more test cases
- 🎉 new flag `images-path` to specify the base images folder
- now when including images, you can specify just the filename
- 🎉 new flag `lim` to specify after how many characters to wrap the text

### Changed
- updated the README markdown file

## [0.1.0] - 2020-09-08
- 🎉 First release!
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# Crumbs

> Turn asterisk-indented text lines into mind maps.
Expand Down Expand Up @@ -55,6 +56,9 @@ Here the output:

You can, eventually, add images too (one for text line) using a special syntax: `[[path/to/image.png]]`

- starting from the release v0.2.0 you can specify the image path from the command line `-image-path` flag
- if you specify the flag `-image-path` you can wriate `[[image.png]]` instead of `[[path/to/image.png]]`

```text
* [[./png/bulb.png]] main idea
** topic 1
Expand Down
Binary file added banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.14

require (
github.com/emicklei/dot v0.14.0
github.com/stretchr/testify v1.6.1
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
)
3 changes: 2 additions & 1 deletion gv/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func newGraph(opts ...GraphOption) *dot.Graph {
res.Attr("fontname", "Fira Code")
res.Attr("fontsize", "14")
res.Attr("splines", "curved")
res.Attr("concentrate", "true")
res.Attrs("orientation", "portrait")

for _, opt := range opts {
Expand All @@ -71,7 +72,7 @@ func createEdge(gr *dot.Graph, fid, tid string, color string) error {

res.Attr("fontname", "Fira Code")
res.Attr("fontsize", "10")
res.Attr("penwidth", "3")
res.Attr("penwidth", "2.5")
//res.Attr("xlabels", strconv.Itoa(lvl))

if strings.TrimSpace(color) != "" {
Expand Down
45 changes: 20 additions & 25 deletions gv/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,39 @@ import (
"github.com/lucasepe/crumbs/text"
)

// RenderConfig defines some render parameters.
type RenderConfig struct {
VerticalLayout bool
WrapTextLimit uint
}

// Render translates the mind note tree to a
// graphviz dot language definition.
func Render(wr io.Writer, note *crumbs.Entry, opts ...GraphOption) error {
gr := newGraph(opts...)
renderTree(gr, note.Root(), sanitizer(30))
func Render(wr io.Writer, note *crumbs.Entry, cfg RenderConfig) error {
htmlize := htmlLabelMaker(cfg.WrapTextLimit)

gr := newGraph(Vertical(cfg.VerticalLayout))

renderTree(gr, note.Root(), htmlize)

_, err := io.WriteString(wr, gr.String())
return err
}

// render a tree node (the node, and its children)
func renderTree(gr *dot.Graph, el *crumbs.Entry, sanify func(string) string) {
func renderTree(gr *dot.Graph, el *crumbs.Entry, htmlize func(*crumbs.Entry) string) {
tintFor := colorSupplier()
asHTML := htmlLabelMaker(30)

if el.Level() > 0 {
createNode(gr, el.ID(), nodeLabel(asHTML(el), true))
createNode(gr, el.ID(), nodeLabel(htmlize(el), true))
}

if el.Parent() != nil {
createEdge(gr, el.Parent().ID(), el.ID(), tintFor(el.Level()))
}

for _, child := range el.Childrens() {
renderTree(gr, child, sanify)
}
}

// sanitizer wraps the given string within lim
// width in characters and does some sanification
func sanitizer(lim uint) func(string) string {
escaper := strings.NewReplacer(
`&`, "&",
`'`, "'",
`"`, """,
)

return func(txt string) string {
res := text.WrapString(txt, lim)
res = escaper.Replace(res)
res = strings.ReplaceAll(res, "\n", "<br/>")
return res
renderTree(gr, child, htmlize)
}
}

Expand Down Expand Up @@ -81,7 +73,10 @@ func htmlLabelMaker(lim uint) func(*crumbs.Entry) string {
)

return func(note *crumbs.Entry) string {
label := text.WrapString(note.Text(), lim)
label := strings.TrimSpace(note.Text())
if lim > 0 {
label = text.WrapString(label, lim)
}
label = escaper.Replace(label)
label = strings.ReplaceAll(label, "\n", "<br/>")

Expand Down
34 changes: 7 additions & 27 deletions parser.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package crumbs

import (
"path/filepath"
"regexp"
"strings"

"github.com/teris-io/shortid"
)

// FromString builds the tree parsing a string.
func FromString(src string) (*Entry, error) {
// ParseLines parses a slice of text lines and builds the tree.
func ParseLines(lines []string, iconspath string) (*Entry, error) {
mkID := idGenerator()
checkIcon := lookForIcon()

// splits the source at newlines
splits := strings.SplitAfter(src, "\n")
checkIcon := lookForIcon(iconspath)

// generate a short id for the root node
rootID, err := mkID()
Expand All @@ -26,7 +24,7 @@ func FromString(src string) (*Entry, error) {

node := root
nodeDepth := 0
for _, el := range splits {
for _, el := range lines {
// skip empty lines
if strings.TrimSpace(el) == "" {
continue
Expand Down Expand Up @@ -106,24 +104,6 @@ func newEmptyNote(id string) *Entry {
return f
}

/**** Uncomment to show the 'Global setup way'
// newID returns a new short id.
func newID() (string, error) {
return sid.Generate()
}
var sid *shortid.Shortid
// init() is called when the package is initialized.
func init() {
var err error
if sid, err = shortid.New(1, shortid.DefaultABC, 2342); err != nil {
panic(err)
}
}
****/

// idGenerator generates a new short id at each invocation.
func idGenerator() func() (string, error) {
sid, err := shortid.New(1, shortid.DefaultABC, 2342)
Expand All @@ -138,14 +118,14 @@ func idGenerator() func() (string, error) {
}
}

func lookForIcon() func(note *Entry) {
func lookForIcon(iconspath string) func(note *Entry) {
re := regexp.MustCompile(`^\[{2}(.*?)\]{2}`)

return func(note *Entry) {
str := note.text
res := re.FindStringSubmatch(str)
if len(res) > 0 {
note.icon = strings.TrimSpace(res[1])
note.icon = filepath.Join(iconspath, strings.TrimSpace(res[1]))
note.text = re.ReplaceAllString(str, "")
}
}
Expand Down
85 changes: 85 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package crumbs

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseLines(t *testing.T) {
test := `
* main idea
** topic 1
*** sub topic 1 1
*** sub topic 1 2
**** sub sub topic
** topic 2
*** sub topic 2 1
`
got, err := ParseLines(strings.SplitAfter(test, "\n"), "")
if err != nil {
t.Error(err)
}

assert.Equal(t, 1, len(got.childrens))
assert.Equal(t, got.childrens[0].text, "main idea")

assert.Equal(t, 2, len(got.childrens[0].childrens))
assert.Equal(t, got.childrens[0].childrens[0].text, "topic 1")
assert.Equal(t, got.childrens[0].childrens[1].text, "topic 2")

assert.Equal(t, 2, len(got.childrens[0].childrens[0].childrens))
assert.Equal(t, got.childrens[0].childrens[0].childrens[1].text, "sub topic 1 2")
}

func TestLookForIcon(t *testing.T) {

tests := []struct {
imagespath string
entry Entry
want string
}{
{
"./images/png",
Entry{text: "[[blob.png]] La vispa Teresa avea tra l'erbetta, a volo sorpresa"},
"images/png/blob.png",
},
{
"/home/lus/Pictures/fontawesome/PNG",
Entry{text: "[[blob.png]] La vispa Teresa avea tra l'erbetta, a volo sorpresa"},
"/home/lus/Pictures/fontawesome/PNG/blob.png",
},
}

for _, tt := range tests {
fn := lookForIcon(tt.imagespath)
fn(&tt.entry)

t.Run(tt.imagespath, func(t *testing.T) {
if got := tt.entry.icon; got != tt.want {
t.Errorf("got [%v] want [%v]", got, tt.want)
}
})
}

}

func TestDepth(t *testing.T) {
tests := []struct {
line string
want int
}{
{"* main idea LV.1", 1},
{"** topic 1 LV.2", 2},
{"*** sub topic LV.3", 3},
{"******* LV.7", 7},
}

for _, tt := range tests {

t.Run(tt.line, func(t *testing.T) {
assert.Equal(t, depth(tt.line), tt.want)
})
}
}

0 comments on commit e2bd572

Please sign in to comment.