~rjarry/aerc-devel

Markup command for compose::review v1 REJECTED

Eric McConville: 1
 Markup command for compose::review

 5 files changed, 121 insertions(+), 0 deletions(-)
Hi Jarry,
Next
Hey there,
Next
Hi Ben,

Ben Lee-Cohen, Aug 03, 2022 at 19:27:
Next
Eric McConville, Aug 03, 2022 at 20:56:
Next
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~rjarry/aerc-devel/patches/34390/mbox | git am -3
Learn more about email & git

[PATCH] Markup command for compose::review Export this patch

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.                                                                      
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
Hi Eric,

Eric McConville, Aug 02, 2022 at 22:46: