Hi there,
Someone was asking about generating HTML email with aerc, and so I
decided to share a solution I use.
This patch introduces `:markup <label>` command in `compose::review`
view. The command passes the plaintext email to an external script
defined in aerc.conf's `[markups]` section, and appends the output
as a text/html part to the email. A simple configuration could
look something like...
[markups]
markdown=pandoc -f markdown -t html --template=emailFlavor.tpl
The design goal was to keep anything and everything HTML external
from aerc, and to have the user opt-in to this feature.
Thanks for your contribution.
While I can appreciate the feature, I have to reject this. This is too
bad because your solution is somewhat elegant and it follows the design
principles of aerc. However, aerc has been created with plain text[1] in
mind. I would assume that the original author (Drew) will not approve
adding html support.
[1]: https://useplaintext.email/
If the initial goal is to make emails easier to read for non-technical
people on their narrow phones, it would be simpler to add support for
format=flowed[2].
[2]: https://datatracker.ietf.org/doc/html/rfc3676
Thoughts?
Any feedback welcome,
E. McConville
---
commands/compose/markup.go | 37 +++++++++++++++++++++++++++++++config/config.go | 17 ++++++++++++++doc/aerc-config.5.scd | 16 ++++++++++++++doc/aerc.1.scd | 6 +++++widgets/compose.go | 45 ++++++++++++++++++++++++++++++++++++++
5 files changed, 121 insertions(+)
create mode 100644 commands/compose/markup.go
diff --git a/commands/compose/markup.go b/commands/compose/markup.go
new file mode 100644
index 0000000..30297af
--- /dev/null+++ b/commands/compose/markup.go
@@ -0,0 +1,37 @@
+package compose++import (+ "fmt"++ "git.sr.ht/~rjarry/aerc/widgets"+)++type MarkUp struct{}++func init() {+ register(MarkUp{})+}++func (MarkUp) Aliases() []string {+ return []string{"markup"}+}++func (MarkUp) Complete(aerc *widgets.Aerc, args []string) []string {+ composer, _ := aerc.SelectedTabContent().(*widgets.Composer)+ return composer.GetMarkups()+}++func (MarkUp) Execute(aerc *widgets.Aerc, args []string) error {+ if len(args) != 2 {+ return fmt.Errorf("Usage: :markup <label>")+ }+ markupKey := args[1]+ composer, _ := aerc.SelectedTabContent().(*widgets.Composer)+ if err := composer.GenerateMarkup(markupKey); err != nil {+ aerc.PushError(err.Error())+ return err+ } else {+ aerc.PushSuccess(fmt.Sprintf("Markup generated"))+ }+ return nil+}
diff --git a/config/config.go b/config/config.go
index 233f2e0..47332aa 100644
--- a/config/config.go+++ b/config/config.go
@@ -194,6 +194,11 @@ type TemplateConfig struct {
Forwards string `ini:"forwards"`
}
+type MarkupConfig struct {+ Name string+ Cmd string+}+type AercConfig struct {
Bindings BindingConfig
ContextualBinds []BindingConfigContext
@@ -208,6 +213,7 @@ type AercConfig struct {
ContextualUis []UIConfigContext
General GeneralConfig
Templates TemplateConfig
+ Markups []MarkupConfig}
// Input: TimestampFormat
@@ -567,6 +573,17 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
}
}
+ if markupsSec, err := file.GetSection("markups"); err == nil {+ for _, markupKey := range markupsSec.KeyStrings() {+ markupCmd := markupsSec.KeysHash()[markupKey]+ mark := MarkupConfig{+ Name: markupKey,+ Cmd: markupCmd,+ }+ config.Markups = append(config.Markups, mark)+ }+ }+ // append default paths to template-dirs and styleset-dirs
for _, dir := range searchDirs {
config.Ui.StyleSetDirs = append(
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index c712c58..ee02f7a 100644
--- a/doc/aerc-config.5.scd+++ b/doc/aerc-config.5.scd
@@ -497,6 +497,22 @@ aerc ships with some default filters installed in the share directory (usually
_/usr/share/aerc/filters_). Note that these may have additional dependencies
that aerc does not have alone.
+## MARKUPS++Markups allows you to pipe the content of a message being composed through a+shell command to generate HTML markup from the plaintext message. Markups+can be defined in *[markups]* section within aerc.conf.++Example:+```+[markups]+work=pandoc -f markdown -t html --template=company.tpl+```++The command *:markup* becomes available during the compose-review stage, and+will only append text/html part to the email if the shell command exits+successfully and content is written to standard output.+## TRIGGERS
Triggers specify commands to execute when certain events occur.
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index b06dd43..f614307 100644
--- a/doc/aerc.1.scd+++ b/doc/aerc.1.scd
@@ -438,6 +438,12 @@ message list, the message in the message viewer, etc).
*edit*
(Re-) opens your text editor to edit the message in progress.
+*markup* <label>+ Passes the message to external command, and appends result as text/html+ message part. The label and command is defined under the *markups* section+ configuration. For details on configuring markup support consult+ *aerc-config*(5).+*next-field*, *prev-field*
Cycles between input fields in the compose window.
diff --git a/widgets/compose.go b/widgets/compose.go
index a4ed27c..823b04c 100644
--- a/widgets/compose.go+++ b/widgets/compose.go
@@ -434,6 +434,51 @@ func (c *Composer) readSignatureFromFile() []byte {
return signature
}
+func (c *Composer) GenerateMarkup(markupKey string) error {+ markupCmd, err := c.getMarkupCmdByName(markupKey)+ if err != nil {+ return err+ }+ proc := exec.Command("sh", "-c", markupCmd)+ stdin, err := proc.StdinPipe()+ if err != nil {+ return err+ }+ go func() {+ defer stdin.Close()+ io.Copy(stdin, c.email)+ }()+ outBytes, err := proc.Output()+ if err != nil {+ return err+ }+ if len(outBytes) < 1 {+ return fmt.Errorf("markup %s did not generate content", markupKey)+ }+ ioReader := bytes.NewReader(outBytes)+ return c.AppendPart("text/html", nil, ioReader)+}++func (c *Composer) GetMarkups() []string {+ if len(c.config.Markups) < 1 {+ return nil+ }+ markupKeys := []string{}+ for _, cmd := range c.config.Markups {+ markupKeys = append(markupKeys, cmd.Name)+ }+ return markupKeys+}++func (c *Composer) getMarkupCmdByName(markupKey string) (string, error) {+ for _, markupConfig := range c.config.Markups {+ if markupConfig.Name == markupKey {+ return markupConfig.Cmd, nil+ }+ }+ return "", fmt.Errorf("unknown markup label %s", markupKey)+}+func (c *Composer) FocusTerminal() *Composer {
if c.editor == nil {
return c
--
2.37.1
Hello,
Please disregard the previous patch email. It was intended to be
a RFC, but did not follow the patch submission guidelines defined
within the documentation. I'll resumit after reviewing guidelines.
Sorry for the clutter,
E. McConville