Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Click on parts of external_script module output #2243

Closed
syyyr opened this issue Mar 27, 2024 · 23 comments · Fixed by #2254
Closed

Click on parts of external_script module output #2243

syyyr opened this issue Mar 27, 2024 · 23 comments · Fixed by #2254

Comments

@syyyr
Copy link
Contributor

syyyr commented Mar 27, 2024

How can we help you today?
Hi, I have this kind of a external_script module:
image
It basically just shows which packages from the aur should be updated. Now, I thought it'd be handy if I could click on the package, and it'd open the AUR page for it. Right now, I think it is possible to assign a script to on_click and even pass the output of the module to it, but there's no way to know which part of the output was clicked. I see that there's a "composite" module feature, which when clicked exports the OUTPUT_PART variable to the script, but I found no way to output a composite module from an external_script. Is this something like this doable in py3status? Could it be implemented?

Thanks.

Your py3status version

$ py3status -v
py3status version 3.56 (python 3.11.8) on i3
@lasers
Copy link
Contributor

lasers commented Mar 28, 2024

Additional information requested.

  • What kind of external_script module is this?
  • What does your config look like with this module?
  • Et cetera.

It may be easier to write a new/custom module based on this kind of output instead.

@syyyr
Copy link
Contributor Author

syyyr commented Mar 28, 2024

What kind of external_script module is this?

I have a script that is able to print out available updates. It looks like this: https://github.com/syyyr/home-repo/blob/main/apps/py3status-aur.bash

What does your config look like with this module?

The config currently looks like this:

external_script updates {
    cache_timeout = 3600
    markup = 'pango'
    script_path = '$HOME/apps/py3status-aur.bash updates'
}

external_script out-of-date {
    cache_timeout = 3600
    markup = 'pango'
    script_path = '$HOME/apps/py3status-aur.bash out-of-date'
}

The full config is here: https://github.com/syyyr/home-repo/blob/main/.config/i3status/config

It may be easier to write a new/custom module based on this kind of output instead.

I'd say it is possible to write my own module for this, and AFAIK, I could make it clickable the way I want. However, it has some disadvantages. I wouldn't like to rewrite my simple script to Python, since it's such a simple one. But if I did, I probably couldn't contribute it to the repo, because it seems to me that it's such a niche. Although, there already is a script for Arch updates, so maybe it could be extended? I'm not sure.

Considering all this, I figured it would be nice, if I could make my external_script module into a composite module, or detect what part of the output has been clicked. But maybe that is something that can't be easily supported by py3status, or maybe it doesn't make sense.

@lasers
Copy link
Contributor

lasers commented Mar 28, 2024

Is this for external_script out-of-date only?

What does aur-out-of-date -user syyyr -json looks like? I think we can work with this one.

Although, there already is a script for Arch updates, so maybe it could be extended? I'm not sure.

I'm sure it could... if there are aur helpers that offers them... Gracefully adding them is probably tricky.

@syyyr
Copy link
Contributor Author

syyyr commented Mar 28, 2024

Is this for external_script out-of-date only?

No, it's mainly for external_script updates. My usecase: right now, I have an update available for mingw-w64-gettext, but unfortunately it doesn't build. When I package can't be built, I usually go to the associated AUR page, because sometimes people put fixes for build failures. I'd like to have a shortcut for this AUR page directly on my AUR updates module.

What does aur-out-of-date -user syyyr -json looks like? I think we can work with this one.

Like I said, it's not mainly for the out-of-update script, but here it is:

$ aur-out-of-date -user syyyr -json
{"type":"package","name":"mingw-w64-doctest","message":"matches upstream version 2.4.11","version":"2.4.11-1","upstream":"v2.4.11","status":"UP-TO-DATE"}
{"type":"package","name":"mingw-w64-marisa","message":"matches upstream version 0.2.6","version":"0.2.6-1","upstream":"v0.2.6","status":"UP-TO-DATE"}
{"type":"package","name":"mingw-w64-trompeloeil","message":"matches upstream version 47","version":"47-1","upstream":"v47","status":"UP-TO-DATE"}
{"type":"package","name":"qt5-opcua","message":"No release found for git+https://code.qt.io/qt/qtopcua.git#branch=5.15.2","version":"5.15.2-1","status":"UNKNOWN"}

This could probably be added in some way to the arch_updates, but as of now, arch_updates doesn't allow for a format I want, so that's why I went with the script. Although it can use auracle, which is what I use in my own script.

@lasers
Copy link
Contributor

lasers commented Mar 28, 2024

@syyyr
Copy link
Contributor Author

syyyr commented Mar 28, 2024

I tried it, and almost works: aur-out-of-date returns each line with a "record separator" character at the beginning, and these are recognized by .splitlines(). The result is that x contains empty strings, which aren't parsed successfully by loads. I edited the code with this patch:

diff --git a/py3status/modules/json_lines.py b/py3status/modules/json_lines.py
index dee84d0c..7f49dcf0 100644
--- a/py3status/modules/json_lines.py
+++ b/py3status/modules/json_lines.py
@@ -27,7 +27,7 @@ class Py3status:
     command = "aur-out-of-date -user syyyr -json"
 
     def json_lines(self):
-        json_objects = [loads(x) for x in self.py3.command_output(self.command).splitlines()]
+        json_objects = [loads(x) for x in self.py3.command_output(self.command).splitlines() if x]
         new_json = [self.py3.safe_format(self.format_json, json) for json in json_objects]
 
         format_json_separator = self.py3.safe_format(self.format_json_separator)

which makes it ignore empty lines. After running it, the result looks like this:
image

This is pretty neat! I could definitely rewrite my script so that outputs a JSON instead of HTML. Tbh I'd rather do formatting inside the py3status config, instead of writing HTML in the script.

With json_lines (and the fact that it uses composite output), I could definitely do what I want.

@syyyr
Copy link
Contributor Author

syyyr commented Mar 28, 2024

The qt5-opcua is missing the upstream field, because the json output doesn't have that field for that particular package, but I guess that can be easily fixable by either filtering that in the command, or via some clever usage of py3status format string.

@lasers
Copy link
Contributor

lasers commented Mar 28, 2024

rewrite my script so that outputs a JSON instead of HTML. Tbh I'd rather do formatting inside the py3status config, instead of writing HTML in the script.

Just one thing. This prints json per line (package), not a list of JSON objects.
You can filter in this script or via py3status formatter.

via some clever usage of py3status format string.

This should work... as long as the value doesn't contain a space or such.

-    format_json = "[\?color=lightblue {name}] [\?color=red {version}] [\?color=lightgrey&show ->] [\?color=lime {upstream}]"
+    format_json = "[\if=status=UP-TO-DATE [\?color=lightblue {name}] [\?color=red {version}] [\?color=lightgrey&show ->] [\?color=lime {upstream}]]"

EDIT: Another way of filtering... Add shell=True to self.py3.command_output (to the code) and add | grep UP-TO-DATE (to the command).

@syyyr
Copy link
Contributor Author

syyyr commented Mar 28, 2024

Just one thing. This prints json per line (package), not a list of JSON objects.
You can filter in this script or via py3status formatter.

Yep, I'm aware of that :) hence "json_lines"

This should work... as long as the value doesn't contain a space or such.

Yeah... well, to be honest, I'd probably just assure that the script outputs what should be printed. I'm not a big fan of the py3status format string.

EDIT: Another way of filtering... Make it shell=True in command_output and add | grep UP-TO-DATE in your command.

Yeah, that's what I would probably do. Or just create a script of my own and exec that. Do you plan on making this module more AUR oriented? Now that I think of it, it could probably be just kind of merged into external_script as a different type of "mode". As in, allow external_script to output its output in a more structured way for py3status. Although I understand that you'd prefer people to write their own modules.

@syyyr
Copy link
Contributor Author

syyyr commented Mar 28, 2024

Also, for my specific usecase, I could just have an "external_script_lines" module, and it would do the job for me.

A flag could be added to external_script to interpret lines as parts of a composite.

@lasers
Copy link
Contributor

lasers commented Mar 28, 2024

I'm not planning on doing anything with json_lines because aur-out-of-date -json is invalid JSON.

I don't think a flag is needed for external_script... Just that external_script should be able to deal with multi-line output... format = "{format_line}" and format_line... stuff. Most users would be targeting one-liner output anyway.

This would need external_script to rename format config to format_line at most.

@syyyr
Copy link
Contributor Author

syyyr commented Mar 29, 2024

I don't think a flag is needed for external_script... Just that external_script should be able to deal with multi-line output... format = "{format_line}" and format_line... stuff. Most users would be targeting one-liner output anyway.

If external_script could deal with multiple lines and be able to differentiate which line was clicked, I'd be most satisfied.

Thank you for looking into this by the way.

@lasers
Copy link
Contributor

lasers commented Apr 2, 2024

Maybe this work...

diff --git a/py3status/modules/external_script.py b/py3status/modules/external_script.py
index f103c95..64bf6a4 100644
--- a/py3status/modules/external_script.py
+++ b/py3status/modules/external_script.py
@@ -105,7 +105,7 @@ class Py3status:
             output = ""
 
         response["full_text"] = self.py3.safe_format(
-            self.format, {"output": output, "lines": len(output_lines)}
+            self.format, {"output": self.py3.safe_format(output), "lines": len(output_lines)}
         )
         return response
 

and you use make_py3status function that outputs this.

echo "[\?color=lightblue&show $NAME] [\?color=red&show $OLD] ..."

@syyyr
Copy link
Contributor Author

syyyr commented Apr 2, 2024

That would definitely help me remove the need to format stuff myself through HTML. I would just need the inidividual [...] to be clickable. Or is that already the case? I haven't tested.

@lasers
Copy link
Contributor

lasers commented Apr 2, 2024

I assume $OUTPUT_PART would be (package) {name} because of different colorized composites. If you don't colorize anything, then I think you would be getting one whole thing (maybe just non-composite text, I don't recall).

It should be possible to make whole thing [...] clickable with custom aur-out-of-date module no matter what format you give it... This is just what I think may make this work with external_script.

@syyyr
Copy link
Contributor Author

syyyr commented Apr 2, 2024

As far as I understand, I have these options:

  1. write my own py3status module, then I can write whatever functionality I want
  2. improve external_script by making it accept more input lines, and making each of them one composite module
  3. improve external_script by making each [...] into a part of a composite module (e.g. [\?color=lightblue&show $NAME] [\?color=red&show $OLD] would make two separate composite modules) etc. and that would make them clickable separately.
  4. (not sure if possible) improve the arch_updates module to include the functionality I want

For number 1, I probably couldn't write anything I could contribute to the repo, for the others, I'm willing to contribute, if needed. Which one of these do you think is the best option for my usecase?

@syyyr
Copy link
Contributor Author

syyyr commented Apr 2, 2024

Okay, I didn't realize that number 3 can just be solved with your patch, and that safe_format already splits stuff into composites. So, if the patch from here #2243 (comment) makes it into external_script, that would be enough for me.

@syyyr
Copy link
Contributor Author

syyyr commented Apr 2, 2024

I have reworked my script to output py3status format strings and added an on_click action:

on_click 1 = 'exec echo $OUTPUT_PART | sed "s/ $//" | pacman -Q - && google-chrome-stable https://aur.archlinux.org/packages/$OUTPUT_PART'

and with this, I got my functionality. The on_click action checks if I clicked on a package (by checking if the package is installed), and then opens the associated AUR page. Unfortunately OUTPUT_PART includes a space at the end, which I remove by sed, but I can live with that.

edit:
Even if I print the format string like this:

echo "[\?color=lightblue&show $NAME][\?color=black&show  ][\?color=red&show $OLD][\?color=black&show  ][\?color=lightgrey&show -][\?color=black&show  ][\?color=lime&show $NEW]"

The NAME composite still includes the space. But like I said, I don't mind the space as long as it works.

@lasers
Copy link
Contributor

lasers commented Apr 9, 2024

Even if I print the format string like this:

You can use [\?soft ] or just a space instead of [\?color=black&show ].

@syyyr
Copy link
Contributor Author

syyyr commented Jun 19, 2024

Hi. For the past two months I've been using:

  1. the patch that adds self.py3.safe_format to external_scripts
  2. this patch events: strip whitespaces from OUTPUT, OUTPUT_PART #2245

with great success.

My config currently looks like this:

external_script updates {
    cache_timeout = 3600
    script_path = '$HOME/apps/py3status-aur.bash updates'
    on_click 1 = 'exec pacman -Q $OUTPUT_PART && google-chrome-stable https://aur.archlinux.org/packages/$OUTPUT_PART'
}

external_script out-of-date {
    cache_timeout = 3600
    script_path = '$HOME/apps/py3status-aur.bash out-of-date'
}

and the py3status-aur.bash script is this: https://github.com/syyyr/home-repo/blob/main/apps/py3status-aur.bash but basically, it's a script that outputs a AUR updates in the format I want.

Clicking the individual parts in the string works flawlessly, and really helps me lookup issues in AUR packages (for example, if the package fails to build).

@lasers
Copy link
Contributor

lasers commented Jun 20, 2024

Added [if=$NEW ...] untested example to illustrate how the lightgrey dash can be skipped due to empty upstream in case you're not familiar with the formatter.

echo "[\?color=lightblue&show $NAME] [\?color=red&show $OLD][\?if=$NEW  [\?color=lightgrey&show -] [\?color=lime&show $NEW]]"

Clicking the individual parts in the string works flawlessly, and really helps me lookup issues in AUR packages (for example, if the package fails to build).

👍

@ultrabug
Copy link
Owner

ultrabug commented Aug 2, 2024

thx got both in for the next release!

@jumperfly
Copy link

jumperfly commented Sep 12, 2024

Hi, I'd first just like to say thanks for contributing this project. I've been using it for some time and it generally works great.

Unfortunately, as a side effect, this change has caused issues referencing output when overriding format.

For example, consider a script, example.sh:

#!/usr/bin/env bash
echo abc

Previously, the following configuration could be used:

external_script example {
  format = "[\?if=output=abc YES|NO]"
  script_path = "~/example.sh"
}

The above now returns NO but I would expect YES to be returned. I believe due to the fact output is now a composite, rather than a string so the comparison always fails.

Edit: perhaps something like this would serve the needs of both composite and simple text?

diff --git a/py3status/modules/external_script.py b/py3status/modules/external_script.py
index 64bf6a45..cfc1b214 100644
--- a/py3status/modules/external_script.py
+++ b/py3status/modules/external_script.py
@@ -105,7 +105,7 @@ class Py3status:
             output = ""
 
         response["full_text"] = self.py3.safe_format(
-            self.format, {"output": self.py3.safe_format(output), "lines": len(output_lines)}
+            self.format, {"output": self.py3.safe_format(output) if output.startswith("[") else output, "lines": len(output_lines)}
         )
         return response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants