├── .gitignore ├── go.mod ├── LICENSE ├── ffh_test.go ├── README.md └── ffh.go /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Phillip-England/ffh 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Phillip England 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. -------------------------------------------------------------------------------- /ffh_test.go: -------------------------------------------------------------------------------- 1 | package ffh 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | // a type for testing only 10 | type FuncDef string 11 | 12 | // another testing type 13 | type Route struct { 14 | Method string 15 | } 16 | 17 | const PATH = "./ffh_test.go" 18 | 19 | func Test_ExtractPackageLine(t *testing.T) { 20 | str, _ := ReadFile(PATH) 21 | packageLine, _ := ExtractPackageLine(str) 22 | if packageLine != "package ffh" { 23 | t.Error("failed to extract package correctly") 24 | } 25 | } 26 | 27 | func Test_ExtractTypeBlocks(t *testing.T) { 28 | str, _ := ReadFile(PATH) 29 | goTypes, _ := ExtractTypeBlocks(str) 30 | if len(goTypes) != 2 { 31 | t.Errorf("expected 2 test types to be found in this file, instead found: %d", len(goTypes)) 32 | } 33 | } 34 | 35 | func Test_ExtractFuncBlocks(t *testing.T) { 36 | str, _ := ReadFile(PATH) 37 | goFuncs, _ := ExtractFuncBlocks(str) 38 | if len(goFuncs) != 5 { 39 | t.Error("expected 5 func blocks in this file") 40 | } 41 | } 42 | 43 | func Test_ExtractImportBlock(t *testing.T) { 44 | str, _ := ReadFile(PATH) 45 | block, _ := ExtractImportBlock(str) 46 | if !strings.Contains(block, "testing") { 47 | t.Error("failed to extract import block correctly") 48 | } 49 | } 50 | 51 | func Test_ExtractFuncByName(t *testing.T) { 52 | str, _ := ReadFile(PATH) 53 | fn, _ := ExtractFuncByName(str, "Test_ExtractFuncByName") 54 | fmt.Println(fn) 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ffh 2 | ffh is a minimal package for extracting details from `.go` files 3 | 4 | ## Installation 5 | ```bash 6 | go get github.com/Phillip-England/ffh 7 | ``` 8 | 9 | ## Import 10 | ```go 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | "github.com/Phillip-England/ffh" 16 | ) 17 | 18 | func main() { 19 | str, err := ffh.ReadFile("./main.go") 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | ``` 25 | 26 | ## Usage 27 | 28 | get the package line out of a `.go` file 29 | ```go 30 | func main() { 31 | str, err := ffh.ReadFile("./main.go") 32 | if err != nil { 33 | panic(err) 34 | } 35 | packageLine, err := ffh.ExtractPackageLine(str) 36 | if err != nil { 37 | panic(err) 38 | } 39 | fmt.Println(packageLine) 40 | } 41 | ``` 42 | 43 | get the import block out of a `.go` file 44 | ```go 45 | func main() { 46 | str, err := ffh.ReadFile("./main.go") 47 | if err != nil { 48 | panic(err) 49 | } 50 | importBlock, err := ffh.ExtractImportBlock(str) 51 | if err != nil { 52 | panic(err) 53 | } 54 | fmt.Println(importBlock) 55 | } 56 | ``` 57 | 58 | get the funcs out of a `.go` file 59 | ```go 60 | func main() { 61 | str, err := ffh.ReadFile("./main.go") 62 | if err != nil { 63 | panic(err) 64 | } 65 | goFuncs, err := ffh.ExtractFuncBlocks(str) 66 | if err != nil { 67 | panic(err) 68 | } 69 | for _, fn := range goFuncs { 70 | fmt.Println(fn) 71 | } 72 | } 73 | ``` 74 | 75 | get the type definitions out of a `.go` file 76 | ```go 77 | type TestType struct { 78 | HasPersonality bool // 🤩 79 | } 80 | 81 | 82 | func main() { 83 | str, err := ffh.ReadFile("./main.go") 84 | if err != nil { 85 | panic(err) 86 | } 87 | goTypes, err := ffh.ExtractTypeBlocks(str) 88 | if err != nil { 89 | panic(err) 90 | } 91 | for _, goType := range goTypes { 92 | fmt.Println(goType) 93 | } 94 | } 95 | ``` -------------------------------------------------------------------------------- /ffh.go: -------------------------------------------------------------------------------- 1 | package ffh 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | // mocks a range loop; if you return true in the loop, it will break 10 | func LoopLines(str string, fn func(i int, line string) bool) { 11 | lines := strings.Split(str, "\n") 12 | for i, ln := range lines { 13 | shouldBreak := fn(i, ln) 14 | if shouldBreak { 15 | break 16 | } 17 | } 18 | } 19 | 20 | // general loop with generics, if you return true, it will break 21 | func Loop[T any](items []T, fn func(i int, item T) bool) { 22 | for i, itm := range items { 23 | shouldBreak := fn(i, itm) 24 | if shouldBreak { 25 | break 26 | } 27 | } 28 | } 29 | 30 | // read a file to a string 31 | func ReadFile(path string) (string, error) { 32 | bytes, err := os.ReadFile(path) 33 | if err != nil { 34 | return "", err 35 | } 36 | return string(bytes), nil 37 | } 38 | 39 | // extracts all the funcs out of a .go file string 40 | func ExtractFuncBlocks(str string) ([]string, error) { 41 | funcs := []string{} 42 | currentFunc := "" 43 | LoopLines(str, func(i int, line string) bool { 44 | if strings.Contains(line, "func ") && strings.Contains(line, "(") && strings.Contains(line, ")") && strings.Contains(line, "{") { 45 | LoopLines(str, func(i2 int, line2 string) bool { 46 | if i2 >= i { 47 | currentFunc = currentFunc + line2 + "\n" 48 | if line2 == "}" { 49 | funcs = append(funcs, currentFunc) 50 | currentFunc = "" 51 | return true 52 | } 53 | } 54 | return false 55 | }) 56 | } 57 | return false 58 | }) 59 | return funcs, nil 60 | } 61 | 62 | // extracts the import statement out of a .go file string 63 | func ExtractImportBlock(str string) (string, error) { 64 | err := StrIsGoFile(str) 65 | if err != nil { 66 | return "", err 67 | } 68 | statement := "" 69 | LoopLines(str, func(i int, line string) bool { 70 | if strings.Contains(line, "import \"") { 71 | statement = line 72 | return true 73 | } 74 | if strings.Contains(line, "import (") { 75 | LoopLines(str, func(i2 int, line2 string) bool { 76 | if i2 >= i { 77 | statement = statement + line2 + "\n" 78 | if line2 == ")" { 79 | return true 80 | } 81 | } 82 | return false 83 | }) 84 | } 85 | return false 86 | }) 87 | return statement, nil 88 | } 89 | 90 | // extracts all the types out of a .go file string 91 | func ExtractTypeBlocks(str string) ([]string, error) { 92 | typeBlocks := []string{} 93 | err := StrIsGoFile(str) 94 | if err != nil { 95 | return typeBlocks, err 96 | } 97 | currentType := "" 98 | LoopLines(str, func(i int, line string) bool { 99 | if strings.Contains(line, "type ") && !strings.Contains(line, "//") { 100 | if !strings.Contains(line, "{") { 101 | typeBlocks = append(typeBlocks, line) 102 | return false 103 | } 104 | LoopLines(str, func(i2 int, line2 string) bool { 105 | if i2 >= i { 106 | currentType = currentType + line2 + "\n" 107 | if line2 == "}" { 108 | typeBlocks = append(typeBlocks, currentType) 109 | currentType = "" 110 | return true 111 | } 112 | } 113 | return false 114 | }) 115 | } 116 | return false 117 | }) 118 | return typeBlocks, nil 119 | } 120 | 121 | // extracts the package line out of a go file string 122 | func ExtractPackageLine(str string) (string, error) { 123 | err := StrIsGoFile(str) 124 | if err != nil { 125 | return "", err 126 | } 127 | packageLine := "" 128 | LoopLines(str, func(i int, line string) bool { 129 | if strings.Contains(line, "package ") { 130 | packageLine = line 131 | return true 132 | } 133 | return false 134 | }) 135 | if packageLine == "" { 136 | return packageLine, fmt.Errorf("package line not found in .go file string") 137 | } 138 | return packageLine, nil 139 | } 140 | 141 | // checks if a provided string is a .go file 142 | func StrIsGoFile(str string) error { 143 | foundTypeDef := false 144 | foundFuncDef := false 145 | foundImport := false 146 | foundPackage := false 147 | if strings.Contains(str, "package ") { 148 | foundPackage = true 149 | } 150 | if strings.Contains(str, "func ") { 151 | foundFuncDef = true 152 | } 153 | if strings.Contains(str, "type ") { 154 | foundTypeDef = true 155 | } 156 | if strings.Contains(str, "import ") { 157 | foundImport = true 158 | } 159 | if !foundPackage { 160 | return fmt.Errorf("provided str did not contain a package definition") 161 | } 162 | if !foundImport { 163 | return fmt.Errorf("provided str did not contain an import statement") 164 | } 165 | if !foundFuncDef && !foundTypeDef { 166 | return fmt.Errorf("provided str did not contain any type definitions or func definitions") 167 | } 168 | return nil 169 | } 170 | 171 | func ExtractFuncByName(str string, funcName string) (string, error) { 172 | funcBlocks, err := ExtractFuncBlocks(str) 173 | if err != nil { 174 | return "", err 175 | } 176 | for _, block := range funcBlocks { 177 | firstLine := strings.Split(block, "\n")[0] 178 | if strings.Contains(firstLine, funcName) { 179 | return block, nil 180 | } 181 | } 182 | return "", fmt.Errorf("failed to located func named: %s", funcName) 183 | } 184 | --------------------------------------------------------------------------------