diff --git a/README.md b/README.md index 87548d67..54aa2eae 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,34 @@ go install github.com/danielmiessler/yt@latest Be sure to add your `YOUTUBE_API_KEY` to `~/.config/fabric/.env`. +### `to_pdf` + +`to_pdf` is a helper command that converts LaTeX files to PDF format. You can use it like this: + +```bash +to_pdf input.tex +``` + +This will create a PDF file from the input LaTeX file in the same directory. + +You can also use it with stdin which works perfectly with the `write_latex` pattern: + +```bash +echo "ai security primer" | fabric --pattern write_latex | to_pdf +``` + +This will create a PDF file named `output.pdf` in the current directory. + +### `to_pdf` Installation + +To install `to_pdf`, install it the same way as you install Fabric, just with a different repo name. + +```bash +go install github.com/danielmiessler/fabric/to_pdf/to_pdf@latest +``` + +Make sure you have a LaTeX distribution (like TeX Live or MiKTeX) installed on your system, as `to_pdf` requires `pdflatex` to be available in your system's PATH. + ## Meta > [!NOTE] diff --git a/patterns/write_latex/system.md b/patterns/write_latex/system.md new file mode 100644 index 00000000..7602535f --- /dev/null +++ b/patterns/write_latex/system.md @@ -0,0 +1,22 @@ +You are an expert at outputting syntactically correct LaTeX for a new .tex document. Your goal is to produce a well-formatted and well-written LaTeX file that will be rendered into a PDF for the user. The LaTeX code you generate should not throw errors when pdflatex is called on it. + +Follow these steps to create the LaTeX document: + +1. Begin with the document class and preamble. Include necessary packages based on the user's request. + +2. Use the \begin{document} command to start the document body. + +3. Create the content of the document based on the user's request. Use appropriate LaTeX commands and environments to structure the document (e.g., \section, \subsection, itemize, tabular, equation). + +4. End the document with the \end{document} command. + +Important notes: +- Do not output anything besides the valid LaTeX code. Any additional thoughts or comments should be placed within \iffalse ... \fi sections. +- Do not use fontspec as it can make it fail to run. +- For sections and subsections, append an asterisk like this \section* in order to prevent everything from being numbered unless the user asks you to number the sections. +- Ensure all LaTeX commands and environments are properly closed. +- Use appropriate indentation for better readability. + +Begin your output with the LaTeX code for the requested document. Do not include any explanations or comments outside of the LaTeX code itself. + +The user's request for the LaTeX document will be included here. diff --git a/to_pdf/to_pdf.go b/to_pdf/to_pdf.go new file mode 100644 index 00000000..23b7d7cb --- /dev/null +++ b/to_pdf/to_pdf.go @@ -0,0 +1,105 @@ +package main + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + var input io.Reader + var outputFile string + if len(os.Args) > 1 { + // File input mode + file, err := os.Open(os.Args[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err) + os.Exit(1) + } + defer file.Close() + input = file + outputFile = strings.TrimSuffix(os.Args[1], filepath.Ext(os.Args[1])) + ".pdf" + } else { + // Stdin mode + input = os.Stdin + outputFile = "output.pdf" + } + + // Check if pdflatex is installed + if _, err := exec.LookPath("pdflatex"); err != nil { + fmt.Fprintln(os.Stderr, "Error: pdflatex is not installed or not in your PATH.") + fmt.Fprintln(os.Stderr, "Please install a LaTeX distribution (e.g., TeX Live or MiKTeX) and ensure pdflatex is in your PATH.") + os.Exit(1) + } + + // Create a temporary directory + tmpDir, err := os.MkdirTemp("", "latex_") + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating temporary directory: %v\n", err) + os.Exit(1) + } + defer os.RemoveAll(tmpDir) + + // Create a temporary .tex file + tmpFile, err := os.Create(filepath.Join(tmpDir, "input.tex")) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err) + os.Exit(1) + } + + // Copy input to the temporary file + _, err = io.Copy(tmpFile, input) + if err != nil { + fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err) + os.Exit(1) + } + tmpFile.Close() + + // Run pdflatex with nonstopmode + cmd := exec.Command("pdflatex", "-interaction=nonstopmode", "-output-directory", tmpDir, tmpFile.Name()) + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Fprintf(os.Stderr, "Error running pdflatex: %v\n", err) + fmt.Fprintf(os.Stderr, "pdflatex output:\n%s\n", output) + os.Exit(1) + } + + // Check if PDF was actually created + pdfPath := filepath.Join(tmpDir, "input.pdf") + if _, err := os.Stat(pdfPath); os.IsNotExist(err) { + fmt.Fprintln(os.Stderr, "Error: PDF file was not created. There might be an issue with your LaTeX source.") + fmt.Fprintf(os.Stderr, "pdflatex output:\n%s\n", output) + os.Exit(1) + } + + // Move the output PDF to the current directory + err = os.Rename(pdfPath, outputFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error moving output file: %v\n", err) + os.Exit(1) + } + + // Clean up temporary files + cleanupTempFiles(tmpDir) + + fmt.Printf("PDF created: %s\n", outputFile) +} + +func cleanupTempFiles(dir string) { + extensions := []string{".aux", ".log", ".out", ".toc", ".lof", ".lot", ".bbl", ".blg"} + for _, ext := range extensions { + files, err := filepath.Glob(filepath.Join(dir, "*"+ext)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error finding %s files: %v\n", ext, err) + continue + } + for _, file := range files { + if err := os.Remove(file); err != nil { + fmt.Fprintf(os.Stderr, "Error removing file %s: %v\n", file, err) + } + } + } +}