-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
1,838 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Description: This file contains the root command for the CLI application. | ||
package cmd | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"log" | ||
"os" | ||
"strings" | ||
"github.com/navnitms/csvtohtml/converter" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var title, delimiter, quotechar, height string | ||
var displayLength int | ||
var overwrite, serve, pagination, noHeader, export bool | ||
|
||
var rootCmd = &cobra.Command{ | ||
Use: "csvtohtml", | ||
Short: "CLI Application to convert CSV file to HTML table", | ||
Long: `csvtohtml is a CLI tool designed to convert CSV files into HTML tables with ease. | ||
It provides various customization options for generating the HTML table, including control | ||
over the table's appearance and features such as pagination, virtual scrolling, and export functionality. | ||
You can use this application by specifying the input CSV file and optionally the output HTML file. | ||
Examples: | ||
Convert a CSV file to an HTML table: | ||
csvtohtml input.csv output.html | ||
Convert and display the HTML table directly in a browser: | ||
csvtohtml input.csv --serve | ||
Flags for customization: | ||
-t, --title: Set a title for the HTML table. | ||
-d, --delimiter: Specify the delimiter used in the CSV file. Default is ','. | ||
-q, --quotechar: Specify the character used for quoting fields with special characters. Default is '"'. | ||
-dl, --display-length: Set the number of rows to display by default. Default is -1 (show all rows). | ||
-o, --overwrite: Overwrite the output file if it already exists. | ||
-s, --serve: Open the generated HTML in the browser instead of saving to a file. | ||
-h, --height: Set the table height (in px or %) for the generated HTML. | ||
-p, --pagination: Enable or disable pagination for the table. Default is enabled. | ||
-nh, --no-header: Disable treating the first row as headers. | ||
-e, --export: Enable export options for filtered rows. Default is enabled.`, | ||
|
||
Run: func(cmd *cobra.Command, args []string) { | ||
inputFile := args[0] | ||
outputFile := "" | ||
if len(args) > 1 { | ||
outputFile = args[1] | ||
} | ||
|
||
options := map[string]interface{}{ | ||
"title": title, | ||
"delimiter": delimiter, | ||
"quotechar": quotechar, | ||
"display_length": displayLength, | ||
"height": height, | ||
"pagination": pagination, | ||
"no_header": noHeader, | ||
"export": export, | ||
} | ||
|
||
// Convert CSV to HTML content | ||
content, err := converter.Convert(inputFile, options) | ||
if err != nil { | ||
log.Fatalf("Failed to convert: %v", err) | ||
} | ||
|
||
if serve { | ||
// Serve the HTML in the browser | ||
converter.Serve(content) | ||
} else if outputFile != "" { | ||
// Check if file should be overwritten | ||
if !overwrite && !promptOverwrite(outputFile) { | ||
log.Fatal("Operation aborted.") | ||
} | ||
// Save the output | ||
err := converter.Save(outputFile, content) | ||
if err != nil { | ||
log.Fatalf("Failed to save file: %v", err) | ||
} | ||
fmt.Printf("File converted successfully: %s\n", outputFile) | ||
} else { | ||
log.Fatal("Missing argument \"output_file\".") | ||
} | ||
}, | ||
} | ||
|
||
|
||
func init() { | ||
rootCmd.Flags().StringVarP(&title, "title", "t", "", "Table title") | ||
rootCmd.Flags().StringVarP(&delimiter, "delimiter", "d", ",", "CSV delimiter") | ||
rootCmd.Flags().StringVarP("echar, "quotechar", "q", `"`, "String used to quote fields containing special characters") | ||
rootCmd.Flags().IntVarP(&displayLength, "length", "l", -1, "Number of rows to show by default. Defaults to -1 (show all rows)") | ||
rootCmd.Flags().BoolVarP(&overwrite, "overwrite", "o", false, "Overwrite the output file if exists") | ||
rootCmd.Flags().BoolVarP(&serve, "serve", "s", false, "Open output HTML in browser instead of writing to file") | ||
rootCmd.Flags().StringVarP(&height, "height", "H", "", "Table height in px or %") // Changed shorthand to "H" | ||
rootCmd.Flags().BoolVarP(&pagination, "pagination", "p", true, "Enable/disable table pagination") | ||
rootCmd.Flags().BoolVar(&noHeader, "no-header", false, "Disable displaying first row as headers") | ||
rootCmd.Flags().BoolVarP(&export, "export", "e", true, "Enable filtered rows export options") | ||
} | ||
|
||
|
||
func promptOverwrite(fileName string) bool { | ||
if _, err := os.Stat(fileName); os.IsNotExist(err) { | ||
return true | ||
} | ||
|
||
reader := bufio.NewReader(os.Stdin) | ||
fmt.Printf("File (%s) already exists. Do you want to overwrite? (y/n): ", fileName) | ||
input, _ := reader.ReadString('\n') | ||
input = strings.TrimSpace(input) | ||
|
||
if input != "y" && input != "Y" { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func Execute() error { | ||
return rootCmd.Execute() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package converter | ||
|
||
import ( | ||
"os" | ||
"fmt" | ||
"time" | ||
"encoding/csv" | ||
"path/filepath" | ||
"regexp" | ||
"html/template" | ||
"bytes" | ||
"strings" | ||
"github.com/pkg/browser" | ||
) | ||
|
||
var ( | ||
packagePath, _ = os.Getwd() | ||
templatesDir = filepath.Join(packagePath, "templates") | ||
jsSrcPattern = regexp.MustCompile(`<script.*?src=\"(.*?)\".*?<\/script>`) | ||
jsFilesPath = filepath.Join(packagePath, "templates") | ||
templateFile = "template.txt" | ||
) | ||
|
||
func Convert(inputFileName string, options map[string]interface{}) (string, error) { | ||
delimiter := getStringOption(options, "delimiter", ",") | ||
// quotechar := getStringOption(options, "quotechar", "\"") | ||
|
||
file, err := os.Open(inputFileName) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to open input file: %v", err) | ||
} | ||
defer file.Close() | ||
|
||
reader := csv.NewReader(file) | ||
reader.Comma = rune(delimiter[0]) | ||
reader.LazyQuotes = true | ||
|
||
var csvHeaders []string | ||
var csvRows [][]string | ||
|
||
fmt.Println(options["title"]) | ||
if options["no_header"] == false { | ||
csvHeaders, err = reader.Read() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read CSV header: %v", err) | ||
} | ||
} | ||
|
||
csvRows, err = reader.ReadAll() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read CSV rows: %v", err) | ||
} | ||
html, err := renderTemplate(csvHeaders, csvRows, options) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to render template: %v", err) | ||
} | ||
|
||
return replaceScript(html) , nil | ||
} | ||
|
||
|
||
func renderTemplate(headers []string, rows [][]string, options map[string]interface{}) (string, error) { | ||
tmplPath := filepath.Join(templatesDir, templateFile) | ||
tmpl, err := template.ParseFiles(tmplPath) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to parse template: %v", err) | ||
} | ||
|
||
|
||
data := map[string]interface{}{ | ||
"Title": getStringOption(options, "title", "Table"), | ||
"Headers": headers, | ||
"Rows": rows, // Now rows is [][]string | ||
"Pagination": options["pagination"], | ||
"TableHeight": getStringOption(options, "height", "70vh"), | ||
"DisplayLength": getIntOption(options, "display_length", -1), | ||
} | ||
|
||
fmt.Print(data["Title"]) | ||
var buf bytes.Buffer | ||
if err := tmpl.Execute(&buf, data); err != nil { | ||
return "", fmt.Errorf("failed to render template: %v", err) | ||
} | ||
|
||
return buf.String(), nil | ||
} | ||
|
||
|
||
|
||
|
||
func replaceScript(html string) string { | ||
matches := jsSrcPattern.FindAllStringSubmatch(html, -1) | ||
|
||
if len(matches) == 0 { | ||
return html | ||
} | ||
|
||
for _, match := range matches { | ||
src := match[1] | ||
|
||
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { | ||
continue | ||
} | ||
|
||
filePath := filepath.Join(jsFilesPath, src) | ||
fileContent, err := os.ReadFile(filePath) | ||
if err != nil { | ||
fmt.Println(fmt.Errorf("failed to read JS file: %v", err)) | ||
} | ||
|
||
jsContent := fmt.Sprintf("<script type=\"text/javascript\">%s</script>", fileContent) | ||
html = strings.Replace(html, match[0], jsContent, 1) | ||
} | ||
|
||
return html | ||
} | ||
|
||
// Save saves content to a file | ||
func Save(fileName, content string) error { | ||
return os.WriteFile(fileName, []byte(content), 0644) | ||
} | ||
|
||
// Serve serves the content in a temporary HTML file and opens it in a browser | ||
func Serve(content string) { | ||
tmpFile, err := os.CreateTemp("", "csvtotable_*.html") | ||
if err != nil { | ||
fmt.Printf("Failed to create temporary file: %v", err) | ||
} | ||
defer os.Remove(tmpFile.Name()) | ||
|
||
if _, err := tmpFile.Write([]byte(content)); err != nil { | ||
fmt.Printf("Failed to write to temp file: %v", err) | ||
} | ||
|
||
browser.OpenFile(tmpFile.Name()) | ||
|
||
for { | ||
time.Sleep(time.Second) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package converter | ||
|
||
func getStringOption(options map[string]interface{}, key, defaultValue string) string { | ||
if val, ok := options[key]; ok { | ||
if str, ok := val.(string); ok { | ||
return str | ||
} | ||
} | ||
return defaultValue | ||
} | ||
|
||
func getIntOption(options map[string]interface{}, key string, defaultValue int) int { | ||
if val, ok := options[key]; ok { | ||
if i, ok := val.(int); ok { | ||
return i | ||
} | ||
} | ||
return defaultValue | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module github.com/navnitms/csvtohtml | ||
|
||
go 1.22.0 | ||
|
||
require ( | ||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c | ||
github.com/spf13/cobra v1.8.1 | ||
) | ||
|
||
require ( | ||
github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
github.com/spf13/pflag v1.0.5 // indirect | ||
golang.org/x/sys v0.1.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= | ||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= | ||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= | ||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= | ||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | ||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package main | ||
|
||
import "github.com/navnitms/csvtohtml/cmd" | ||
|
||
func main() { | ||
cmd.Execute() | ||
} |
Oops, something went wrong.