├── README.md ├── demo.txt ├── docs ├── _config.yml ├── compiler.html ├── index.md └── processor.html ├── drag.jpg ├── drag.ps ├── output.pdf ├── pusher.sh ├── pygroff ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-39.pyc │ ├── compiler.cpython-39.pyc │ └── processor.cpython-39.pyc ├── compiler.py └── processor.py ├── requirements.txt ├── runner.py ├── syntax.docx ├── syntax.pdf └── todo.md /README.md: -------------------------------------------------------------------------------- 1 | # pyGroff 2 | 3 | - A wrapper for groff using python to have a nicer syntax for groff documents 4 | - [DOCUMENTATION](https://subhadityamukherjee.github.io/pyGroff/) 5 | - Very similar to markdown. So if you know what that is. You will love this :) 6 | - We hate word -.- 7 | - Editing pdfs is a pain and please we are lazy 8 | - We love markdown. But we need pdfs and docx. So why not 9 | - LaTEX is amazing but it is tooo much work for small things. 10 | - Vim is love. What can we do without keyboard shortcuts ): 11 | 12 | ## What can you do 13 | - Write in a text file 14 | - Get a cover page as well :) 15 | - Get a Table of contents. (Due to limitations : its only on the last page for now.) 16 | - Add code. And get execution results directly! Default is python. You can use any other language in your system 17 | - Easy tables (Aint nobody got time for complicated ones) 18 | - You can get a word document too (you do need libreoffice for it) 19 | - Get auto generated, beautifully formatted pdfs and docs instantly 20 | - Not cry because you moved an image and now your document is in hieroglyphics 21 | - (You can also write in groff syntax in the file. It will work as well. Just in case you need something extra) 22 | 23 | ## Requirements 24 | - You need python of course. 25 | - For python dependencies, using pip install -r requirements.txt (Only PIL) 26 | - Almost every unix system has groff preinstalled. 27 | - If you want to convert to word, you need libreoffice. 28 | - If you want to get a table of contents you will need pdftk 29 | - yay pdftk #arch 30 | - sudo pacman -S pdftk #arch 31 | - sudo apt install pdftk 32 | 33 | ## Syntax 34 | - p runner.py -f "demo.txt" -o "syntax.pdf" (most basic) 35 | - p runner.py -f "demo.txt" -o "syntax.pdf" -c True -n "Subhaditya Mukherjee" -t "pyGroff" (with cover page) 36 | - Please please look at arguments 37 | - By default, it is assumed that you have images. If you wish to disable it (for more speed), just use -i False 38 | - Check syntax.pdf and demo.txt for an example 39 | - Refer to syntax.pdf for new, easier syntax :) 40 | - This was also generated by the program hehe 41 | 42 | ## Examples of langauge strings 43 | - python -c (default) 44 | - argument -l 45 | - R -e 46 | 47 | ## FAQ 48 | - I dont like the cover page 49 | - If you know a bit of groff (check the links below), you can use "-d False" and then edit whatever you want using groff itself. 50 | - Then run "groff -ms {infile} -Tpdf > {outfile}" replacing the infile and outfile respectively. 51 | 52 | ## Contribution guidelines 53 | - Can I contribute? 54 | - YES 55 | - What to do? 56 | - Check todo.md 57 | - Restrictions? 58 | - File an issue first, if it looks useful. Go for it 59 | - Spelling mistakes? 60 | - I mean sure why not xD 61 | 62 | ## References 63 | - [Markdown syntax](https://www.markdownguide.org/basic-syntax/) 64 | - [My groff tutorial](https://github.com/SubhadityaMukherjee/groffTutorial) 65 | - [More syntax](https://opensource.com/article/18/2/how-format-academic-papers-linux-groff-me) 66 | 67 | -------------------------------------------------------------------------------- /demo.txt: -------------------------------------------------------------------------------- 1 | %pyGroff, A tiny Syntax guide 2 | @Subhaditya Mukherjee 3 | # Intro 4 | pyGroff is a tiny wrapper around groff which will let you create professional pdfs and documents in almost markdown syntax. This document is an example as well as a syntax list for easy reference. As you can see, it is also being generated by pyGroff. 5 | 6 | # Arguments possible 7 | ## Required 8 | - -f : input file path 9 | - -o : output file name. (Dont give path!) 10 | ## Optional 11 | - -l : Language string. (Default is python) 12 | - -t : Add TOC (Default true) 13 | - -i : Are there images (Default true) 14 | - -d : Delete intermediates (Default true) 15 | - -df : Different date format (Default "%B %d, %Y") 16 | - -c : Add cover page (Default true) 17 | - -t : Title for cover page 18 | - -w : Convert to word (Default False) 19 | 20 | # Image 21 | !drag.jpg 22 | 23 | # Main syntax 24 | 25 | ## General 26 | *Note that you have to remove the <> 27 | ~% : Adds a title like the one in this document 28 | ~@ <author> : Author name 29 | ~< <text> : move to the center (left is default) 30 | ~> <text> : move to the right 31 | ~# <text> : Heading level 1 32 | ~## <text> : Heading level 2.. and so on 33 | ~- <text> : Lists 34 | ~ : if you want to use one of the above in a sentence but do not want it to be formatted. Like this document. 35 | ~* <text> : bold 36 | ~/ <text> : italics 37 | ~_ <text> : underline 38 | ~+ <text> : New page 39 | ~^: Superscript 40 | ^(Note that this should be in a new line) 41 | 42 | ## Advanced 43 | 44 | ~| <text> : Table. Format is |[title](1;3;4;5;6,1;3;4;8;9). Separate rows with , and lines with ; 45 | ~! <image name.jpg> : Make sure it is in the same directory. Or specify the full path. Note that it will be converted to .eps format. 46 | ~) <code> : Python code, in quotes like : "import numpy as np; z = np.random.rand(3,3);print(z)" . Please separate lines by ; 47 | ~= <equation> : One equation per line. For more examples refer below 48 | 49 | ## Need a cover page? 50 | *Use the arguments 51 | 52 | - -c True 53 | - -n "Your name" 54 | - -t "Project title" 55 | - -d This is optional. But it can be another date format 56 | 57 | # Limitations for now 58 | - If you have added a title and an author, you must add a body or you will get errors 59 | - Formatting can only be applied to the whole row 60 | - If you are using < or > , please leave a line gap 61 | - If you see any numbering/formatting not working, just leave a line gap. It should mostly work out. If not. File an issue 62 | 63 | # What about code 64 | 65 | )import numpy as np;z = np.random.rand(3,3); print(z) 66 | 67 | # What about tables 68 | |[Who is cool](me;me;me;you, 1;1;1;1) 69 | 70 | # What about equations 71 | ## Further syntax specification for easier equations 72 | - != : not equals 73 | - >= : greater than or equals (etc etc) 74 | - sup : superscript 75 | - sub : subscript 76 | - over : divided by 77 | - pi 78 | - for any greek letter : just spell it out. Put spaces before and after 79 | - cdot : circle dot 80 | - del : grad symbol 81 | - grad : grad symbol 82 | - sum : sum symbol { write as from {i=1} etc } 83 | - int : integral 84 | - inf : infinity 85 | - partial : partial differential symbol 86 | - half : 1/2 87 | - prod : product symbol 88 | - union 89 | - inter : intersection 90 | 91 | ## Some examples 92 | = a sup 3 over b sup 5 93 | = x=3+5x-=3 + gamma 94 | = a sup 3 sup 5 * 600 95 | = 7 gamma + 10 delta = 100 96 | = f ( theta ) = .8 pi r 97 | = sum from {i=1} to inf sub 1 = 1000x 98 | 99 | > Thank you 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /docs/compiler.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" /> 6 | <meta name="generator" content="pdoc 0.9.2" /> 7 | <title>pygroff.compiler API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module pygroff.compiler

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
import subprocess
 30 | from pathlib import Path
 31 | from pygroff.processor import *
 32 | 
 33 | """
 34 | This is the main module which calls all the required functions.
 35 | """
 36 | 
 37 | 
 38 | def clean_up(ag, tempfile, tempfile2, outfile):
 39 |     """
 40 |     Delete unneeded files
 41 |     Convert to word document if needed
 42 |     """
 43 |     if ag.d == True:  # For debugging, delete or not delete files
 44 |         subprocess.run(f"rm {str(tempfile)}", shell=True)
 45 |         if ag.i == True:
 46 |             subprocess.run(f"rm {str(tempfile2)}", shell=True)
 47 |     if ag.w == True:
 48 |         subprocess.run(
 49 |             f"libreoffice --headless --convert-to docx --infilter='writer_pdf_import' {str(outfile)}",
 50 |             shell=True,
 51 |         )
 52 | 
 53 | 
 54 | def decide_image(ag, tempfile, tempfile2, outfile):
 55 |     """
 56 |     Decide if the file has images. Default is true. Not much difference, except more images = more compile time
 57 |     """
 58 |     if ag.i == False:  # If there are images
 59 |         subprocess.run(
 60 |             f"tbl {str(tempfile)}|groff -e -mspdf -Tpdf > {outfile}", shell=True
 61 |         )
 62 |     else:  # Save compile time
 63 |         subprocess.run(
 64 |             f"tbl {str(tempfile)} | groff -e -mspdf -Tps > {str(tempfile2)} && ps2pdf {str(tempfile2)} {outfile}",
 65 |             shell=True,
 66 |         )
 67 |     if ag.toc == True:
 68 |         if ag.c == True:
 69 |             subprocess.run(
 70 |                 f"pdftk {str(outfile)} cat 1 end 2-r2 output temp-{str(outfile)}",
 71 |                 shell=True,
 72 |             )
 73 |         else:
 74 |             subprocess.run(
 75 |                 f"pdftk {str(outfile)} cat end 1-r2 output temp-{str(outfile)}",
 76 |                 shell=True,
 77 |             )
 78 |         Path.unlink(Path(str(outfile)))
 79 |         Path.rename(Path("temp-" + str(outfile)), outfile)
 80 | 
 81 | 
 82 | def main(ag):
 83 |     """
 84 |     This calls the required functions and cleans up after the program is done
 85 |     """
 86 |     fpath = Path(ag.f)
 87 |     outfile = Path(ag.f).parent / ag.o
 88 |     with open(fpath.with_suffix(".ms"), "w+") as f:
 89 |         if ag.c == True:  # Add cover
 90 |             f.write(
 91 |                 f".ad c\n.tp\n.sp 5\n.(c\n{ag.t}\n.)c\n.sp 2\n.(c\n{ag.n}\n.)c\n.sp 2\n.(c\n{get_date(ag.df)}\n.)c\n.bp\n.ad l\n"
 92 |             )
 93 |             f.flush()
 94 |         f.write(intermediary_creator(fpath=fpath, ag=ag))
 95 | 
 96 |         if ag.toc == True:  # Add table of contents
 97 |             f.write("\n.TC\n")
 98 | 
 99 |     tempfile, tempfile2 = fpath.with_suffix(".ms"), fpath.with_suffix(".ps")
100 |     decide_image(ag, tempfile, tempfile2, outfile)
101 |     clean_up(ag, tempfile, tempfile2, outfile)
102 | 
103 |     print(f"Done writing the file to -> {outfile}")
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |

Functions

112 |
113 |
114 | def clean_up(ag, tempfile, tempfile2, outfile) 115 |
116 |
117 |

Delete unneeded files 118 | Convert to word document if needed

119 |
120 | 121 | Expand source code 122 | 123 |
def clean_up(ag, tempfile, tempfile2, outfile):
124 |     """
125 |     Delete unneeded files
126 |     Convert to word document if needed
127 |     """
128 |     if ag.d == True:  # For debugging, delete or not delete files
129 |         subprocess.run(f"rm {str(tempfile)}", shell=True)
130 |         if ag.i == True:
131 |             subprocess.run(f"rm {str(tempfile2)}", shell=True)
132 |     if ag.w == True:
133 |         subprocess.run(
134 |             f"libreoffice --headless --convert-to docx --infilter='writer_pdf_import' {str(outfile)}",
135 |             shell=True,
136 |         )
137 |
138 |
139 |
140 | def decide_image(ag, tempfile, tempfile2, outfile) 141 |
142 |
143 |

Decide if the file has images. Default is true. Not much difference, except more images = more compile time

144 |
145 | 146 | Expand source code 147 | 148 |
def decide_image(ag, tempfile, tempfile2, outfile):
149 |     """
150 |     Decide if the file has images. Default is true. Not much difference, except more images = more compile time
151 |     """
152 |     if ag.i == False:  # If there are images
153 |         subprocess.run(
154 |             f"tbl {str(tempfile)}|groff -e -mspdf -Tpdf > {outfile}", shell=True
155 |         )
156 |     else:  # Save compile time
157 |         subprocess.run(
158 |             f"tbl {str(tempfile)} | groff -e -mspdf -Tps > {str(tempfile2)} && ps2pdf {str(tempfile2)} {outfile}",
159 |             shell=True,
160 |         )
161 |     if ag.toc == True:
162 |         if ag.c == True:
163 |             subprocess.run(
164 |                 f"pdftk {str(outfile)} cat 1 end 2-r2 output temp-{str(outfile)}",
165 |                 shell=True,
166 |             )
167 |         else:
168 |             subprocess.run(
169 |                 f"pdftk {str(outfile)} cat end 1-r2 output temp-{str(outfile)}",
170 |                 shell=True,
171 |             )
172 |         Path.unlink(Path(str(outfile)))
173 |         Path.rename(Path("temp-" + str(outfile)), outfile)
174 |
175 |
176 |
177 | def main(ag) 178 |
179 |
180 |

This calls the required functions and cleans up after the program is done

181 |
182 | 183 | Expand source code 184 | 185 |
def main(ag):
186 |     """
187 |     This calls the required functions and cleans up after the program is done
188 |     """
189 |     fpath = Path(ag.f)
190 |     outfile = Path(ag.f).parent / ag.o
191 |     with open(fpath.with_suffix(".ms"), "w+") as f:
192 |         if ag.c == True:  # Add cover
193 |             f.write(
194 |                 f".ad c\n.tp\n.sp 5\n.(c\n{ag.t}\n.)c\n.sp 2\n.(c\n{ag.n}\n.)c\n.sp 2\n.(c\n{get_date(ag.df)}\n.)c\n.bp\n.ad l\n"
195 |             )
196 |             f.flush()
197 |         f.write(intermediary_creator(fpath=fpath, ag=ag))
198 | 
199 |         if ag.toc == True:  # Add table of contents
200 |             f.write("\n.TC\n")
201 | 
202 |     tempfile, tempfile2 = fpath.with_suffix(".ms"), fpath.with_suffix(".ps")
203 |     decide_image(ag, tempfile, tempfile2, outfile)
204 |     clean_up(ag, tempfile, tempfile2, outfile)
205 | 
206 |     print(f"Done writing the file to -> {outfile}")
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | 234 |
235 | 238 | 239 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pygroff API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Package pygroff

23 |
24 |
25 |

Hello! This little library is a wrapper around groff. It will save your tears when you need to make any pdfs and word documents with more complex formatting done super easily for you. Do you know markdown already? Wow! you already know most of the syntax :)

26 |
    27 |
  1. Features of the library
  2. 28 |
  3. Write in a text file
  4. 29 |
  5. Add code. And get execution results directly! (No images. And no imports for now.)
  6. 30 |
  7. Get a cover page as well :)
  8. 31 |
  9. Get a Table of contents. (Due to limitations : its only on the last page for now.)
  10. 32 |
  11. Easy tables (Aint nobody got time for complicated ones)
  12. 33 |
  13. You can get a word document too (you do need libreoffice for it)
  14. 34 |
  15. Get auto generated, beautifully formatted pdfs and docs instantly
  16. 35 |
  17. Not cry because you moved an image and now your document is in hieroglyphics
  18. 36 |
  19. 37 |

    You can also write in groff syntax in the file. It will work as well. Just in case you need something extra

    38 |
  20. 39 |
  21. 40 |

    Arguments: 41 | -f : Input file path 42 | -o : Output file path 43 | -toc : Add table of contents : true/false 44 | -cov : Add cover page : true/false 45 | -w : Convert to word : true/false 46 | -n : Name for cover page 47 | -t : Title for cover page 48 | -e : Execute and output code : true/false 49 | -i : Are there images : true/false 50 | -df : Custom date format : python strftime format 51 | -d : Delete intermediates or not 52 | -lang : custom language execute script. eg : python -c

    53 |
  22. 54 |
  23. 55 |

    For syntax, refer to syntax.pdf or syntax.docx

    56 |
  24. 57 |
58 |
59 | 60 | Expand source code 61 | 62 |
"""Hello! This little library is a wrapper around groff. It will save your tears when you need to make any pdfs and word documents with more complex formatting done super easily for you. Do you know markdown already? Wow! you already know most of the syntax :)
 63 | 
 64 | 1. Features of the library
 65 | - Write in a text file
 66 | - Add code. And get execution results directly! (No images. And no imports for now.)
 67 | - Get a cover page as well :)
 68 | - Get a Table of contents. (Due to limitations : its only on the last page for now.)
 69 | - Easy tables (Aint nobody got time for complicated ones)
 70 | - You can get a word document too (you do need libreoffice for it)
 71 | - Get auto generated, beautifully formatted pdfs and docs instantly
 72 | - Not cry because you moved an image and now your document is in hieroglyphics
 73 | - You can also write in groff syntax in the file. It will work as well. Just in case you need something extra
 74 | 
 75 | 2. Arguments:
 76 |     -f : Input file path
 77 |     -o : Output file path
 78 |     -toc : Add table of contents : true/false
 79 |     -cov : Add cover page : true/false
 80 |     -w : Convert to word : true/false
 81 |     -n : Name for cover page 
 82 |     -t : Title for cover page
 83 |     -e : Execute and output code : true/false
 84 |     -i : Are there images : true/false
 85 |     -df : Custom date format : python strftime format
 86 |     -d : Delete intermediates or not
 87 |     -lang : custom language execute script. eg : python -c
 88 | 
 89 | 3. For syntax, refer to syntax.pdf or syntax.docx
 90 | """
91 |
92 |
93 |
94 |

Sub-modules

95 |
96 |
pygroff.compiler
97 |
98 |
99 |
100 |
pygroff.processor
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | 127 |
128 | 131 | 132 | -------------------------------------------------------------------------------- /docs/processor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | pygroff.processor API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module pygroff.processor

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
import subprocess
 30 | from pathlib import Path
 31 | from PIL import Image
 32 | 
 33 | #  import re
 34 | 
 35 | """
 36 | This module takes care of the bulk of processing etc
 37 | """
 38 | 
 39 | # adds ranges of headers
 40 | dict_hash = {"#" * x: f".NH {x}" for x in range(1, 6)}
 41 | dict_symbols = {
 42 |     "%": ".TL",
 43 |     "@": ".AU",
 44 |     "<": ".ad c",
 45 |     ">": ".ad r",
 46 |     "~": "",
 47 |     "-": ".IP ",
 48 |     "*": ".b ",
 49 |     "/": ".i ",
 50 |     "_": ".ul ",
 51 |     "!": ".PDFPIC",
 52 |     "+": ".bp",
 53 | }
 54 | full_list = list("#%@<>~*/_!+=")
 55 | 
 56 | 
 57 | def get_date(form):
 58 |     """
 59 |     Date time formate for cover page
 60 |     """
 61 |     import datetime
 62 | 
 63 |     return datetime.datetime.now().strftime(form)
 64 | 
 65 | 
 66 | def grab_image_and_convert(sentence):
 67 |     """
 68 |     Reads image, if not in .ps -> converts it to .ps and returns the required string
 69 |     """
 70 |     fpath = Path(sentence[1::].strip())
 71 |     if fpath.suffix != ".ps":
 72 |         Image.open(fpath).convert("RGB").save(
 73 |             str(fpath.with_suffix(".ps")), lossless=True
 74 |         )
 75 | 
 76 |         #
 77 |         #  subprocess.run(
 78 |         #      f"convert {str(fpath)} {str(fpath.with_suffix('.ps'))}", shell=True
 79 |         #  )
 80 |     return f".DS L\n\n.PSPIC {str(fpath.with_suffix('.ps'))}\n.DE\n"
 81 | 
 82 | 
 83 | def code_runner(x, ag):
 84 |     """
 85 |     Formats the code with newline separated by ;
 86 |     Also saves the output of the code
 87 |     """
 88 |     cod = "\n\n".join(x[1::].split(";")) + "\n"
 89 |     if ag.e == True:
 90 |         cod += (
 91 |             "\n Output : \n\n"
 92 |             + str(
 93 |                 subprocess.Popen(
 94 |                     f'{ag.l} "{x[1::]}"', shell=True, stdout=subprocess.PIPE
 95 |                 )
 96 |                 .communicate()[0]
 97 |                 .decode("utf-8")
 98 |             )
 99 |             + "\n"
100 |         )
101 |     return cod
102 | 
103 | 
104 | def table_creator(x):
105 |     """
106 |     Add tables very easily
107 |     """
108 |     tb = "\n.TS\ntab(;) allbox ;\n"
109 |     x = x[1::]
110 |     tb_vals = x[x.find("(") + 1 : -2]
111 |     temp_tb = tb_vals.split(",")
112 |     count_cols = len(temp_tb[0].split(";"))
113 |     tb += "c " + "s " * (count_cols - 1)
114 |     tb += "\n" + "c" * count_cols + " .\n"
115 |     if "[" in x:
116 |         tb += x[1 : x.find("]")] + "\n"
117 |     else:
118 |         tb += "Table\n"
119 |     tb += "\n".join(temp_tb) + "\n.TE\n"
120 |     return tb
121 | 
122 | 
123 | def subscript(sentence):
124 |     return "\*{" + sentence[1::]
125 | 
126 | 
127 | def eqn(sentence):
128 |     """
129 |     Returns a tag of equations. This is preprocessed by eqn (GNU troff)
130 |     """
131 |     return "\n.EQ\n" + sentence[1::] + "\n.EN\n"
132 | 
133 | 
134 | def ret_symbol(sentence, list_flag, ag):
135 |     """
136 |     This checks the dictionaries and identifies what to send to groff
137 |     """
138 | 
139 |     s0 = sentence[0]
140 |     if s0 == "!":
141 |         return grab_image_and_convert(sentence), list_flag
142 | 
143 |     if s0 == "^":
144 |         return subscript(sentence), list_flag
145 |     if s0 == "=":
146 |         return eqn(sentence), list_flag
147 | 
148 |     if s0 == ")":
149 |         return code_runner(sentence, ag), list_flag
150 |     if s0 == "|":
151 |         return table_creator(sentence), list_flag
152 | 
153 |     if s0 in list("*/"):
154 |         return (
155 |             "\n" + dict_symbols[s0] + "\n" + sentence[1::].lstrip() + "\n.LP",
156 |             list_flag,
157 |         )
158 |     if list_flag > 0 and s0 in full_list:
159 |         list_flag = 0
160 | 
161 |     ch1 = [x in sentence for x in dict_hash.keys()]
162 |     counthash = ch1.count(True)
163 |     if counthash > 0 and s0 != "~":
164 |         val = sentence[counthash::].lstrip()
165 |         return (
166 |             dict_hash["#" * counthash] + "\n.XN " + sentence[counthash::].lstrip(),
167 |             list_flag,
168 |         )
169 | 
170 |     else:
171 |         currentsym = dict_symbols[s0]
172 |         if s0 == "-" and list_flag == 0:
173 |             list_flag = 1
174 |             currentsym += str(list_flag) + ")"
175 |         elif s0 == "-" and list_flag > 0:
176 |             list_flag += 1
177 |             currentsym += str(list_flag) + ")"
178 |         currentsym = currentsym + "\n" + sentence[1::].lstrip()
179 | 
180 |         if s0 in list("<>"):
181 |             currentsym += "\n.ad l"
182 |         return currentsym, list_flag
183 | 
184 | 
185 | def intermediary_creator(fpath, ag):
186 |     """
187 |     Since groff needs an intermediary file, we create it. Dont worry! It will be deleted later :)
188 |     """
189 |     f_in = open(fpath, "r")
190 |     out_string = ""
191 |     flag = 0
192 |     list_flag = 0
193 |     for i, line in enumerate(f_in.readlines()):
194 |         try:
195 |             outs, list_flag = ret_symbol(line, list_flag=list_flag, ag=ag)
196 |             out_string += outs
197 |             flag = 0
198 |         except KeyError:  # Basically every other letter
199 |             flag = 1
200 |         if flag == 1:
201 |             out_string += f".LP\n{line}"
202 |             flag = 2
203 |         elif flag == 2:
204 |             out_string += line
205 | 
206 |     return out_string
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |

Functions

215 |
216 |
217 | def code_runner(x, ag) 218 |
219 |
220 |

Formats the code with newline separated by ; 221 | Also saves the output of the code

222 |
223 | 224 | Expand source code 225 | 226 |
def code_runner(x, ag):
227 |     """
228 |     Formats the code with newline separated by ;
229 |     Also saves the output of the code
230 |     """
231 |     cod = "\n\n".join(x[1::].split(";")) + "\n"
232 |     if ag.e == True:
233 |         cod += (
234 |             "\n Output : \n\n"
235 |             + str(
236 |                 subprocess.Popen(
237 |                     f'{ag.l} "{x[1::]}"', shell=True, stdout=subprocess.PIPE
238 |                 )
239 |                 .communicate()[0]
240 |                 .decode("utf-8")
241 |             )
242 |             + "\n"
243 |         )
244 |     return cod
245 |
246 |
247 |
248 | def eqn(sentence) 249 |
250 |
251 |

Returns a tag of equations. This is preprocessed by eqn (GNU troff)

252 |
253 | 254 | Expand source code 255 | 256 |
def eqn(sentence):
257 |     """
258 |     Returns a tag of equations. This is preprocessed by eqn (GNU troff)
259 |     """
260 |     return "\n.EQ\n" + sentence[1::] + "\n.EN\n"
261 |
262 |
263 |
264 | def get_date(form) 265 |
266 |
267 |

Date time formate for cover page

268 |
269 | 270 | Expand source code 271 | 272 |
def get_date(form):
273 |     """
274 |     Date time formate for cover page
275 |     """
276 |     import datetime
277 | 
278 |     return datetime.datetime.now().strftime(form)
279 |
280 |
281 |
282 | def grab_image_and_convert(sentence) 283 |
284 |
285 |

Reads image, if not in .ps -> converts it to .ps and returns the required string

286 |
287 | 288 | Expand source code 289 | 290 |
def grab_image_and_convert(sentence):
291 |     """
292 |     Reads image, if not in .ps -> converts it to .ps and returns the required string
293 |     """
294 |     fpath = Path(sentence[1::].strip())
295 |     if fpath.suffix != ".ps":
296 |         Image.open(fpath).convert("RGB").save(
297 |             str(fpath.with_suffix(".ps")), lossless=True
298 |         )
299 | 
300 |         #
301 |         #  subprocess.run(
302 |         #      f"convert {str(fpath)} {str(fpath.with_suffix('.ps'))}", shell=True
303 |         #  )
304 |     return f".DS L\n\n.PSPIC {str(fpath.with_suffix('.ps'))}\n.DE\n"
305 |
306 |
307 |
308 | def intermediary_creator(fpath, ag) 309 |
310 |
311 |

Since groff needs an intermediary file, we create it. Dont worry! It will be deleted later :)

312 |
313 | 314 | Expand source code 315 | 316 |
def intermediary_creator(fpath, ag):
317 |     """
318 |     Since groff needs an intermediary file, we create it. Dont worry! It will be deleted later :)
319 |     """
320 |     f_in = open(fpath, "r")
321 |     out_string = ""
322 |     flag = 0
323 |     list_flag = 0
324 |     for i, line in enumerate(f_in.readlines()):
325 |         try:
326 |             outs, list_flag = ret_symbol(line, list_flag=list_flag, ag=ag)
327 |             out_string += outs
328 |             flag = 0
329 |         except KeyError:  # Basically every other letter
330 |             flag = 1
331 |         if flag == 1:
332 |             out_string += f".LP\n{line}"
333 |             flag = 2
334 |         elif flag == 2:
335 |             out_string += line
336 | 
337 |     return out_string
338 |
339 |
340 |
341 | def ret_symbol(sentence, list_flag, ag) 342 |
343 |
344 |

This checks the dictionaries and identifies what to send to groff

345 |
346 | 347 | Expand source code 348 | 349 |
def ret_symbol(sentence, list_flag, ag):
350 |     """
351 |     This checks the dictionaries and identifies what to send to groff
352 |     """
353 | 
354 |     s0 = sentence[0]
355 |     if s0 == "!":
356 |         return grab_image_and_convert(sentence), list_flag
357 | 
358 |     if s0 == "^":
359 |         return subscript(sentence), list_flag
360 |     if s0 == "=":
361 |         return eqn(sentence), list_flag
362 | 
363 |     if s0 == ")":
364 |         return code_runner(sentence, ag), list_flag
365 |     if s0 == "|":
366 |         return table_creator(sentence), list_flag
367 | 
368 |     if s0 in list("*/"):
369 |         return (
370 |             "\n" + dict_symbols[s0] + "\n" + sentence[1::].lstrip() + "\n.LP",
371 |             list_flag,
372 |         )
373 |     if list_flag > 0 and s0 in full_list:
374 |         list_flag = 0
375 | 
376 |     ch1 = [x in sentence for x in dict_hash.keys()]
377 |     counthash = ch1.count(True)
378 |     if counthash > 0 and s0 != "~":
379 |         val = sentence[counthash::].lstrip()
380 |         return (
381 |             dict_hash["#" * counthash] + "\n.XN " + sentence[counthash::].lstrip(),
382 |             list_flag,
383 |         )
384 | 
385 |     else:
386 |         currentsym = dict_symbols[s0]
387 |         if s0 == "-" and list_flag == 0:
388 |             list_flag = 1
389 |             currentsym += str(list_flag) + ")"
390 |         elif s0 == "-" and list_flag > 0:
391 |             list_flag += 1
392 |             currentsym += str(list_flag) + ")"
393 |         currentsym = currentsym + "\n" + sentence[1::].lstrip()
394 | 
395 |         if s0 in list("<>"):
396 |             currentsym += "\n.ad l"
397 |         return currentsym, list_flag
398 |
399 |
400 |
401 | def subscript(sentence) 402 |
403 |
404 |
405 |
406 | 407 | Expand source code 408 | 409 |
def subscript(sentence):
410 |     return "\*{" + sentence[1::]
411 |
412 |
413 |
414 | def table_creator(x) 415 |
416 |
417 |

Add tables very easily

418 |
419 | 420 | Expand source code 421 | 422 |
def table_creator(x):
423 |     """
424 |     Add tables very easily
425 |     """
426 |     tb = "\n.TS\ntab(;) allbox ;\n"
427 |     x = x[1::]
428 |     tb_vals = x[x.find("(") + 1 : -2]
429 |     temp_tb = tb_vals.split(",")
430 |     count_cols = len(temp_tb[0].split(";"))
431 |     tb += "c " + "s " * (count_cols - 1)
432 |     tb += "\n" + "c" * count_cols + " .\n"
433 |     if "[" in x:
434 |         tb += x[1 : x.find("]")] + "\n"
435 |     else:
436 |         tb += "Table\n"
437 |     tb += "\n".join(temp_tb) + "\n.TE\n"
438 |     return tb
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 | 471 |
472 | 475 | 476 | -------------------------------------------------------------------------------- /drag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/drag.jpg -------------------------------------------------------------------------------- /output.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/output.pdf -------------------------------------------------------------------------------- /pusher.sh: -------------------------------------------------------------------------------- 1 | black "." 2 | python runner.py -f "demo.txt" -o "syntax.pdf" -c -n "Subhaditya Mukherjee" -t "pyGroff" -w True 3 | pdoc --force --html -o docs pygroff 4 | mv docs/pygroff/index.html docs/index.md 5 | mv docs/pygroff/* docs/ 6 | if [[ ! -z $1 ]]; then 7 | git add . && git commit -m $1 && git push 8 | fi 9 | -------------------------------------------------------------------------------- /pygroff/__init__.py: -------------------------------------------------------------------------------- 1 | """Hello! This little library is a wrapper around groff. It will save your tears when you need to make any pdfs and word documents with more complex formatting done super easily for you. Do you know markdown already? Wow! you already know most of the syntax :) 2 | 3 | 1. Features of the library 4 | - Write in a text file 5 | - Add code. And get execution results directly! (No images. And no imports for now.) 6 | - Get a cover page as well :) 7 | - Get a Table of contents. (Due to limitations : its only on the last page for now.) 8 | - Easy tables (Aint nobody got time for complicated ones) 9 | - You can get a word document too (you do need libreoffice for it) 10 | - Get auto generated, beautifully formatted pdfs and docs instantly 11 | - Not cry because you moved an image and now your document is in hieroglyphics 12 | - You can also write in groff syntax in the file. It will work as well. Just in case you need something extra 13 | 14 | 2. Arguments: 15 | -f : Input file path 16 | -o : Output file path 17 | -toc : Add table of contents : true/false 18 | -cov : Add cover page : true/false 19 | -w : Convert to word : true/false 20 | -n : Name for cover page 21 | -t : Title for cover page 22 | -e : Execute and output code : true/false 23 | -i : Are there images : true/false 24 | -df : Custom date format : python strftime format 25 | -d : Delete intermediates or not 26 | -lang : custom language execute script. eg : python -c 27 | 28 | 3. For syntax, refer to syntax.pdf or syntax.docx 29 | """ 30 | -------------------------------------------------------------------------------- /pygroff/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/pygroff/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /pygroff/__pycache__/compiler.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/pygroff/__pycache__/compiler.cpython-39.pyc -------------------------------------------------------------------------------- /pygroff/__pycache__/processor.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/pygroff/__pycache__/processor.cpython-39.pyc -------------------------------------------------------------------------------- /pygroff/compiler.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from pathlib import Path 3 | from pygroff.processor import * 4 | 5 | """ 6 | This is the main module which calls all the required functions. 7 | """ 8 | 9 | 10 | def clean_up(ag, tempfile, tempfile2, outfile): 11 | """ 12 | Delete unneeded files 13 | Convert to word document if needed 14 | """ 15 | if ag.d == True: # For debugging, delete or not delete files 16 | subprocess.run(f"rm {str(tempfile)}", shell=True) 17 | if ag.i == True: 18 | subprocess.run(f"rm {str(tempfile2)}", shell=True) 19 | if ag.w == True: 20 | subprocess.run( 21 | f"libreoffice --headless --convert-to docx --infilter='writer_pdf_import' {str(outfile)}", 22 | shell=True, 23 | ) 24 | 25 | 26 | def decide_image(ag, tempfile, tempfile2, outfile): 27 | """ 28 | Decide if the file has images. Default is true. Not much difference, except more images = more compile time 29 | """ 30 | if ag.i == False: # If there are images 31 | subprocess.run( 32 | f"tbl {str(tempfile)}|groff -e -mspdf -Tpdf > {outfile}", shell=True 33 | ) 34 | else: # Save compile time 35 | subprocess.run( 36 | f"tbl {str(tempfile)} | groff -e -mspdf -Tps > {str(tempfile2)} && ps2pdf {str(tempfile2)} {outfile}", 37 | shell=True, 38 | ) 39 | if ag.toc == True: 40 | if ag.c == True: 41 | subprocess.run( 42 | f"pdftk {str(outfile)} cat 1 end 2-r2 output temp-{str(outfile)}", 43 | shell=True, 44 | ) 45 | else: 46 | subprocess.run( 47 | f"pdftk {str(outfile)} cat end 1-r2 output temp-{str(outfile)}", 48 | shell=True, 49 | ) 50 | Path.unlink(Path(str(outfile))) 51 | Path.rename(Path("temp-" + str(outfile)), outfile) 52 | 53 | 54 | def main(ag): 55 | """ 56 | This calls the required functions and cleans up after the program is done 57 | """ 58 | fpath = Path(ag.f) 59 | outfile = Path(ag.f).parent / ag.o 60 | with open(fpath.with_suffix(".ms"), "w+") as f: 61 | if ag.c == True: # Add cover 62 | f.write( 63 | f".ad c\n.tp\n.sp 5\n.(c\n{ag.t}\n.)c\n.sp 2\n.(c\n{ag.n}\n.)c\n.sp 2\n.(c\n{get_date(ag.df)}\n.)c\n.bp\n.ad l\n" 64 | ) 65 | f.flush() 66 | f.write(intermediary_creator(fpath=fpath, ag=ag)) 67 | 68 | if ag.toc == True: # Add table of contents 69 | f.write("\n.TC\n") 70 | 71 | tempfile, tempfile2 = fpath.with_suffix(".ms"), fpath.with_suffix(".ps") 72 | decide_image(ag, tempfile, tempfile2, outfile) 73 | clean_up(ag, tempfile, tempfile2, outfile) 74 | 75 | print(f"Done writing the file to -> {outfile}") 76 | -------------------------------------------------------------------------------- /pygroff/processor.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from pathlib import Path 3 | from PIL import Image 4 | 5 | # import re 6 | 7 | """ 8 | This module takes care of the bulk of processing etc 9 | """ 10 | 11 | # adds ranges of headers 12 | dict_hash = {"#" * x: f".NH {x}" for x in range(1, 6)} 13 | dict_symbols = { 14 | "%": ".TL", 15 | "@": ".AU", 16 | "<": ".ad c", 17 | ">": ".ad r", 18 | "~": "", 19 | "-": ".IP ", 20 | "*": ".b ", 21 | "/": ".i ", 22 | "_": ".ul ", 23 | "!": ".PDFPIC", 24 | "+": ".bp", 25 | } 26 | full_list = list("#%@<>~*/_!+=") 27 | 28 | 29 | def get_date(form): 30 | """ 31 | Date time formate for cover page 32 | """ 33 | import datetime 34 | 35 | return datetime.datetime.now().strftime(form) 36 | 37 | 38 | def grab_image_and_convert(sentence): 39 | """ 40 | Reads image, if not in .ps -> converts it to .ps and returns the required string 41 | """ 42 | fpath = Path(sentence[1::].strip()) 43 | if fpath.suffix != ".ps": 44 | Image.open(fpath).convert("RGB").save( 45 | str(fpath.with_suffix(".ps")), lossless=True 46 | ) 47 | 48 | # 49 | # subprocess.run( 50 | # f"convert {str(fpath)} {str(fpath.with_suffix('.ps'))}", shell=True 51 | # ) 52 | return f".DS L\n\n.PSPIC {str(fpath.with_suffix('.ps'))}\n.DE\n" 53 | 54 | 55 | def code_runner(x, ag): 56 | """ 57 | Formats the code with newline separated by ; 58 | Also saves the output of the code 59 | """ 60 | cod = "\n\n".join(x[1::].split(";")) + "\n" 61 | if ag.e == True: 62 | cod += ( 63 | "\n Output : \n\n" 64 | + str( 65 | subprocess.Popen( 66 | f'{ag.l} "{x[1::]}"', shell=True, stdout=subprocess.PIPE 67 | ) 68 | .communicate()[0] 69 | .decode("utf-8") 70 | ) 71 | + "\n" 72 | ) 73 | return cod 74 | 75 | 76 | def table_creator(x): 77 | """ 78 | Add tables very easily 79 | """ 80 | tb = "\n.TS\ntab(;) allbox ;\n" 81 | x = x[1::] 82 | tb_vals = x[x.find("(") + 1 : -2] 83 | temp_tb = tb_vals.split(",") 84 | count_cols = len(temp_tb[0].split(";")) 85 | tb += "c " + "s " * (count_cols - 1) 86 | tb += "\n" + "c" * count_cols + " .\n" 87 | if "[" in x: 88 | tb += x[1 : x.find("]")] + "\n" 89 | else: 90 | tb += "Table\n" 91 | tb += "\n".join(temp_tb) + "\n.TE\n" 92 | return tb 93 | 94 | 95 | def subscript(sentence): 96 | return "\*{" + sentence[1::] 97 | 98 | 99 | def eqn(sentence): 100 | """ 101 | Returns a tag of equations. This is preprocessed by eqn (GNU troff) 102 | """ 103 | return "\n.EQ\n" + sentence[1::] + "\n.EN\n" 104 | 105 | 106 | def ret_symbol(sentence, list_flag, ag): 107 | """ 108 | This checks the dictionaries and identifies what to send to groff 109 | """ 110 | 111 | s0 = sentence[0] 112 | if s0 == "!": 113 | return grab_image_and_convert(sentence), list_flag 114 | 115 | if s0 == "^": 116 | return subscript(sentence), list_flag 117 | if s0 == "=": 118 | return eqn(sentence), list_flag 119 | 120 | if s0 == ")": 121 | return code_runner(sentence, ag), list_flag 122 | if s0 == "|": 123 | return table_creator(sentence), list_flag 124 | 125 | if s0 in list("*/"): 126 | return ( 127 | "\n" + dict_symbols[s0] + "\n" + sentence[1::].lstrip() + "\n.LP", 128 | list_flag, 129 | ) 130 | if list_flag > 0 and s0 in full_list: 131 | list_flag = 0 132 | 133 | ch1 = [x in sentence for x in dict_hash.keys()] 134 | counthash = ch1.count(True) 135 | if counthash > 0 and s0 != "~": 136 | val = sentence[counthash::].lstrip() 137 | return ( 138 | dict_hash["#" * counthash] + "\n.XN " + sentence[counthash::].lstrip(), 139 | list_flag, 140 | ) 141 | 142 | else: 143 | currentsym = dict_symbols[s0] 144 | if s0 == "-" and list_flag == 0: 145 | list_flag = 1 146 | currentsym += str(list_flag) + ")" 147 | elif s0 == "-" and list_flag > 0: 148 | list_flag += 1 149 | currentsym += str(list_flag) + ")" 150 | currentsym = currentsym + "\n" + sentence[1::].lstrip() 151 | 152 | if s0 in list("<>"): 153 | currentsym += "\n.ad l" 154 | return currentsym, list_flag 155 | 156 | 157 | def intermediary_creator(fpath, ag): 158 | """ 159 | Since groff needs an intermediary file, we create it. Dont worry! It will be deleted later :) 160 | """ 161 | f_in = open(fpath, "r") 162 | out_string = "" 163 | flag = 0 164 | list_flag = 0 165 | for i, line in enumerate(f_in.readlines()): 166 | try: 167 | outs, list_flag = ret_symbol(line, list_flag=list_flag, ag=ag) 168 | out_string += outs 169 | flag = 0 170 | except KeyError: # Basically every other letter 171 | flag = 1 172 | if flag == 1: 173 | out_string += f".LP\n{line}" 174 | flag = 2 175 | elif flag == 2: 176 | out_string += line 177 | 178 | return out_string 179 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow==8.2.0 2 | -------------------------------------------------------------------------------- /runner.py: -------------------------------------------------------------------------------- 1 | import pygroff as pg 2 | from pygroff import compiler 3 | import argparse as ap 4 | 5 | arg = ap.ArgumentParser() 6 | arg.add_argument( 7 | "-l", 8 | type=str, 9 | help="Default language string if not python", 10 | required=False, 11 | default="python -c", 12 | ) 13 | arg.add_argument("-f", type=str, help="Input file path", required=True) 14 | arg.add_argument("-o", type=str, help="Output file path", required=True) 15 | arg.add_argument( 16 | "-toc", type=str, help="Add table of contents", required=False, default=True 17 | ) 18 | arg.add_argument("-c", help="Add cover page", action="store_true") 19 | arg.add_argument( 20 | "-w", type=bool, help="Convert to word?", required=False, default=False 21 | ) 22 | arg.add_argument("-n", type=str, help="Name for cover page", required=False) 23 | arg.add_argument("-t", type=str, help="Title for cover page", required=False) 24 | arg.add_argument( 25 | "-e", 26 | type=bool, 27 | help="Execute python code and return output", 28 | required=False, 29 | default=True, 30 | ) 31 | arg.add_argument("-i", type=bool, help="Are there images", required=False, default=True) 32 | arg.add_argument( 33 | "-df", type=str, help="Different date format", required=False, default="%B %d ,%Y" 34 | ) 35 | arg.add_argument( 36 | "-d", type=str, help="Delete intermediates", required=False, default=True 37 | ) 38 | ag = arg.parse_args() 39 | 40 | compiler.main(ag) 41 | -------------------------------------------------------------------------------- /syntax.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/syntax.docx -------------------------------------------------------------------------------- /syntax.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utility-code/pyGroff/46a490cd71d9f6f90805c5c9734a7c681c8eeded/syntax.pdf -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | ## To Do 2 | 3 | 4 | ## Doing 5 | 6 | 7 | ## Done 8 | 9 | - basic function 10 | - cover page 11 | - pdf gen 12 | - indents 13 | - titles 14 | - author 15 | - delete temporary 16 | - bold, italic 17 | - lists 18 | - cover page 19 | - formatting 20 | - word document gen 21 | - images 22 | - codeblocks 23 | - python outputs 24 | - toc 25 | - detect if table -> tbl 26 | - format code 27 | 28 | ## Ideas 29 | 30 | --------------------------------------------------------------------------------