├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── PULL_REQUEST_TEMPLATE.md ├── contributing.md ├── go.mod ├── go.sum ├── license ├── parser.go ├── parser_test.go └── readme.md /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 🐞 3 | about: Something isn't working as expected? 4 | labels: bug 5 | --- 6 | 7 | ### Bug 🐞 8 | 9 | 10 | 11 | 12 | ### Steps to Reproduce: 13 | 14 | 1. 15 | 2. 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature ✨ 3 | about: Suggest new idea for the project 4 | labels: enhancement 5 | --- 6 | 7 | ### Feature ✨ 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 🤔 3 | about: Usage question or discussion 4 | labels: question 5 | --- 6 | 7 | ### Question 🤔 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | 4 | 5 | 6 | ### Changes 7 | 8 | - 9 | 10 | 11 | ### Notes 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for taking the time to contribute! ♥️ You can: 4 | 5 | - Submit [bug reports or feature requests](../../issues/new/choose). Contribute to discussions. Fix [open issues](../../issues). 6 | - Improve docs, the code and more! Any idea is welcome. 7 | 8 | ## Run project 9 | 10 | 1. Clone repo 11 | 2. If you use [VSCode](https://code.visualstudio.com) with [Go](https://github.com/microsoft/vscode-go) plugin, it will install all Go dependencies for you in the background when you open the project. 12 | 3. Edit the code & run it with `go run .`. 13 | 14 | I use [watchexec](https://github.com/watchexec/watchexec) to develop. 15 | 16 | Running `watchexec --exts go "echo -- && go run ."` will automatically rerun `go run .` for you on every Go file changed. 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nikitavoloboev/markdown-parser 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.4.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 7 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 11 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 12 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nikita (nikiv.dev) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | // Package parser provides methods to grab all links from markdown files. 2 | package parser 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | // ParseLink parses a line and grabs the Link, Title and the Description attached to it. 16 | // The format of the line should be as follows: `- [Title](Link) - Description`. 17 | // Description can be omitted. 18 | func ParseLink(line string) map[string]string { 19 | // Holds all the title, link, and description 20 | m := make(map[string]string) 21 | 22 | // Regex to extract title, link, and description 23 | re := regexp.MustCompile(`(?m)(^- \[([^]]+)\]\(([^)]+)\) ?-? ?(.*)?)?`) 24 | 25 | // Make regex 26 | match := re.FindStringSubmatch(line) 27 | 28 | m["Title"] = "" 29 | m["Link"] = "" 30 | m["Description"] = "" 31 | if len(match) == 5 { 32 | m["Title"] = match[2] 33 | m["Link"] = match[3] 34 | m["Description"] = match[4] 35 | } 36 | 37 | return m 38 | } 39 | 40 | // ParseImageLink parses an image line and grabs the Link attached to it. 41 | // The format of the line should be as follows: `![](Link)`. 42 | func ParseImageLink(line string) string { 43 | // Regex to extract title, link, and description 44 | re := regexp.MustCompile(`(?m)(^!\[\]\(([^)]+)\))?`) 45 | 46 | // Make regex 47 | match := re.FindStringSubmatch(line) 48 | 49 | link := "" 50 | if len(match) == 3 { 51 | link = match[2] 52 | } 53 | 54 | return link 55 | } 56 | 57 | // GetAllLinks returns all links and their names from a given markdown file. 58 | func GetAllLinks(markdown string) map[string]string { 59 | // Holds all the links and their corresponding values 60 | m := make(map[string]string) 61 | 62 | // Regex to extract link and text attached to link 63 | re := regexp.MustCompile(`\[([^\]]*)\]\(([^)]*)\)`) 64 | 65 | scanner := bufio.NewScanner(strings.NewReader(markdown)) 66 | // Scans line by line 67 | for scanner.Scan() { 68 | // Make regex 69 | matches := re.FindAllStringSubmatch(scanner.Text(), -1) 70 | 71 | // Only apply regex if there are links and the link does not start with # 72 | if matches != nil { 73 | if strings.HasPrefix(matches[0][2], "#") == false { 74 | // fmt.Println(matches[0][2]) 75 | m[matches[0][1]] = matches[0][2] 76 | } 77 | } 78 | } 79 | return m 80 | } 81 | 82 | // fileToString returns string representation of a file. 83 | func fileToString(file string) (string, error) { 84 | bytes, err := ioutil.ReadFile(file) 85 | if err != nil { 86 | return "", err 87 | } 88 | s := string(bytes) 89 | return s, nil 90 | } 91 | 92 | // ParseMarkdownFile parses a markdown file and returns all markdown links from it. 93 | func ParseMarkdownFile(fileName string) (map[string]string, error) { 94 | file, err := fileToString(fileName) 95 | if err != nil { 96 | log.Fatal() 97 | } 98 | return GetAllLinks(file), nil 99 | } 100 | 101 | // DownloadURL returns Body response from the URL. 102 | func DownloadURL(URL string) (string, error) { 103 | resp, err := http.Get(URL) 104 | if err != nil { 105 | return "", err 106 | } 107 | 108 | defer resp.Body.Close() 109 | buf := new(bytes.Buffer) 110 | buf.ReadFrom(resp.Body) 111 | return buf.String(), nil 112 | } 113 | 114 | // ParseMarkdownURL parses an URL and returns all markdown links from it. 115 | func ParseMarkdownURL(URL string) (map[string]string, error) { 116 | file, err := DownloadURL(URL) 117 | if err != nil { 118 | return make(map[string]string), err 119 | } 120 | return GetAllLinks(file), nil 121 | } 122 | 123 | // readFile returns contents of the file. 124 | func readFile(filename string) string { 125 | b, err := ioutil.ReadFile(filename) 126 | if err != nil { 127 | fmt.Print(err) 128 | } 129 | return string(b) 130 | } 131 | -------------------------------------------------------------------------------- /parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestParseLink(t *testing.T) { 10 | // Define assertion 11 | assert := assert.New(t) 12 | // 1. Link with description 13 | v := "- [Effective Go](https://golang.org/doc/effective_go.html) - Amazing doc." 14 | result := ParseLink(v) 15 | assert.Equal(result["Title"], "Effective Go", "1. Check to see if the title is correct") 16 | assert.Equal(result["Link"], "https://golang.org/doc/effective_go.html", "1. Check to see if the link is correct") 17 | assert.Equal(result["Description"], "Amazing doc.", "1. Check to see if the description is correct") 18 | // 2. Link without description 19 | v2 := "- [Effective Go](https://golang.org/doc/effective_go.html)" 20 | result2 := ParseLink(v2) 21 | assert.Equal(result2["Title"], "Effective Go", "2. Check to see if the title is correct") 22 | assert.Equal(result2["Link"], "https://golang.org/doc/effective_go.html", "2. Check to see if the link is correct") 23 | assert.Equal(result2["Description"], "", "2. Check to see if the description is correct") 24 | // 3. Random text 25 | v3 := "blargh" 26 | result3 := ParseLink(v3) 27 | assert.Equal(result3["Title"], "", "3. Check to see if the title is correct") 28 | assert.Equal(result3["Link"], "", "3. Check to see if the link is correct") 29 | assert.Equal(result3["Description"], "", "3. Check to see if the description is correct") 30 | } 31 | 32 | func TestParseImageLink(t *testing.T) { 33 | // Define assertion 34 | assert := assert.New(t) 35 | // 1. Image link 36 | v := "![](https://i.imgur.com/ZVPjkzh.png)" 37 | result := ParseImageLink(v) 38 | assert.Equal(result, "https://i.imgur.com/ZVPjkzh.png", "1. Check to see if the image link is correct") 39 | // 4. Random text 40 | v2 := "blargh" 41 | result2 := ParseImageLink(v2) 42 | assert.Equal(result2, "", "2. Check to see if the image link is correct") 43 | } 44 | 45 | func TestParseMarkdownFile(t *testing.T) { 46 | // result, err := ParseMarkdownFile("websites.md") 47 | // if err != nil { 48 | // log.Fatal(err) 49 | // } 50 | // fmt.Println("test") 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Markdown Parser [![Docs](https://img.shields.io/badge/-Docs-0a0a0a.svg?style=flat&colorA=0a0a0a)](https://pkg.go.dev/github.com/nikitavoloboev/markdown-parser?tab=doc) 2 | 3 | > Go library to parse markdown to grab various things 4 | 5 | This library will parse a given markdown file and grab all links from it and put it into a hashmap `map[string]string` to use. 6 | 7 | ## Install 8 | 9 | `go get -u github.com/nikitavoloboev/markdown-parser` 10 | 11 | ## Usage 12 | 13 | See [Docs](https://pkg.go.dev/github.com/nikitavoloboev/markdown-parser?tab=doc). 14 | 15 | You can see this library being used in [Alfred Awesome Lists](https://github.com/nikitavoloboev/alfred-awesome-lists) to grab all links from [Sindre's awesome lists](https://github.com/sindresorhus/awesome) and filter the results in Alfred. 16 | 17 | ## Contribute 18 | 19 | Always open to useful ideas or fixes in form of issues or PRs. 20 | 21 | Can [open new issue](../../issues/new/choose) (search [existing issues](../../issues) first) or [start discussion](../../discussions). 22 | 23 | It's okay to submit draft PR as you can get help along the way to make it merge ready. 24 | 25 | Join [Discord](https://discord.com/invite/TVafwaD23d) for more indepth discussions on this repo and [others](https://github.com/nikitavoloboev#src). 26 | 27 | ### 🖤 28 | 29 | [Support on GitHub](https://github.com/sponsors/nikitavoloboev) or look into [other projects](https://nikiv.dev/projects). 30 | 31 | [![Discord](https://img.shields.io/badge/Discord-100000?style=flat&logo=discord&logoColor=white&labelColor=black&color=black)](https://discord.com/invite/TVafwaD23d) [![X](https://img.shields.io/badge/nikitavoloboev-100000?logo=X&color=black)](https://twitter.com/nikitavoloboev) [![nikiv.dev](https://img.shields.io/badge/nikiv.dev-black)](https://nikiv.dev) 32 | --------------------------------------------------------------------------------