Emoji+Unicode utility
parent
5dc4035686
commit
2a80df16d9
|
@ -1 +1,4 @@
|
|||
/target
|
||||
emoji-test.txt
|
||||
emoji.txt
|
||||
UnicodeData.txt
|
||||
|
|
|
@ -26,3 +26,7 @@ path = "src/pass.rs"
|
|||
[[bin]]
|
||||
name = "todo"
|
||||
path = "src/todo.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "emoji"
|
||||
path = "src/emoji.rs"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
(
|
||||
name: "Emoji",
|
||||
description: "Syntax: e [keywords]",
|
||||
bin: (path: "emoji"),
|
||||
icon: Name("accessories-character-map"),
|
||||
query: (
|
||||
regex: "^(e|emoji)\\s.*",
|
||||
isolate: true,
|
||||
help: "emoji",
|
||||
)
|
||||
)
|
|
@ -1,11 +1,12 @@
|
|||
|
||||
PLUGINS="todo pass"
|
||||
PLUGINS="todo pass emoji"
|
||||
|
||||
cargo build
|
||||
|
||||
for plug in $PLUGINS; do
|
||||
DIR=~/.local/share/pop-launcher/plugins/$plug
|
||||
BIN=target/debug/$plug
|
||||
DATA=data
|
||||
RON=./$plug.ron
|
||||
|
||||
if [ ! -d $DIR ] ; then
|
||||
|
@ -16,4 +17,8 @@ for plug in $PLUGINS; do
|
|||
cp $RON $DIR/plugin.ron
|
||||
echo Installing $BIN "->" $DIR
|
||||
cp $BIN $DIR
|
||||
done
|
||||
if [ "$plug" = "emoji" ] ; then
|
||||
echo Installing $DATA/emoji.txt "->" $DIR/emoji.txt
|
||||
cp $DATA/emoji.txt $DIR/
|
||||
fi
|
||||
done
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ../data
|
||||
if [ ! -f UnicodeData.txt ] ; then
|
||||
wget https://www.unicode.org/Public/15.1.0/ucd/UnicodeData.txt
|
||||
fi
|
||||
if [ ! -f emoji-test.txt ] ; then
|
||||
wget https://www.unicode.org/Public/emoji/15.0/emoji-test.txt
|
||||
fi
|
||||
cd ../
|
||||
./scripts/gen_unicode.py data/UnicodeData.txt data/emoji-test.txt > data/emoji.txt
|
||||
cp data/emoji.txt target/debug/emoji.txt
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Liberally borrowed from Gnome Characters
|
||||
# https://gitlab.gnome.org/GNOME/gnome-characters/-/blob/main/lib/gen-names.py
|
||||
|
||||
# Input: https://www.unicode.org/Public/UNIDATA/UnicodeData.txt
|
||||
|
||||
import io
|
||||
import re
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
class Builder(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def read(self, ucdfile, emojifile):
|
||||
emoji = set()
|
||||
for line in emojifile:
|
||||
m = re.match('([0-9A-F ]+); fully-qualified\s+#.*E\d+.\d+ (.+)', line)
|
||||
|
||||
if not m:
|
||||
continue
|
||||
|
||||
cp = m.group(1).strip()
|
||||
if cp.find(' ') > 0:
|
||||
continue
|
||||
|
||||
emoji.add(int(cp, 16))
|
||||
|
||||
names = []
|
||||
for line in ucdfile:
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
line = line.strip()
|
||||
if len(line) == 0:
|
||||
continue
|
||||
(codepoint, name, _other) = line.split(';', 2)
|
||||
|
||||
# Names starting with < are signifying controls and special blocks,
|
||||
# they aren't useful for us
|
||||
if name[0] == '<':
|
||||
continue
|
||||
|
||||
cp = int(codepoint, 16)
|
||||
|
||||
names.append(
|
||||
(codepoint, cp in emoji, name)
|
||||
)
|
||||
|
||||
return names
|
||||
|
||||
def write(self, data):
|
||||
# data = data[:10]
|
||||
# json.dump([
|
||||
# {"cp": chr(int(codepoint, 16)), "name": name.lower()}
|
||||
# for codepoint, name in data
|
||||
# ], sys.stdout)
|
||||
|
||||
data.sort(key = lambda d: (not d[1], d[2]))
|
||||
|
||||
for codepoint, is_emoji, name in data:
|
||||
print("{0};{1}".format(codepoint, name.lower()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='build')
|
||||
parser.add_argument('ucdfile', type=argparse.FileType('r'),
|
||||
help='UnicodeData.txt')
|
||||
parser.add_argument('emojifile', type=argparse.FileType('r'),
|
||||
help='emoji-test.txt')
|
||||
args = parser.parse_args()
|
||||
|
||||
builder = Builder()
|
||||
# FIXME: argparse.FileType(encoding=...) is available since Python 3.4
|
||||
data = builder.read(io.open(args.ucdfile.name, encoding='utf_8_sig'), io.open(args.emojifile.name, encoding='utf_8_sig'))
|
||||
builder.write(data)
|
|
@ -0,0 +1,161 @@
|
|||
mod launcher;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fmt::Display;
|
||||
use std::process::Command;
|
||||
use launcher::{ Request, PluginResponse, PluginSearchResult };
|
||||
use std::result::Result;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, Write};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EmojiError
|
||||
{
|
||||
message: String
|
||||
}
|
||||
|
||||
impl Display for EmojiError
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("Error: ")?;
|
||||
f.write_str(&self.message)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EmojiError
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"description() is deprecated; use Display"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Emoji
|
||||
{
|
||||
cp: String,
|
||||
name: String
|
||||
|
||||
}
|
||||
|
||||
fn read_emoji() -> Result<Vec<Emoji>, io::Error>
|
||||
{
|
||||
let mut path = env::current_exe()?;
|
||||
path.pop();
|
||||
path.push("emoji.txt");
|
||||
|
||||
let file = File::open(path)?;
|
||||
let reader = io::BufReader::new(file);
|
||||
let result =
|
||||
reader.lines().flatten()
|
||||
.map(|line:String| {
|
||||
let mut fields = line.split(";");
|
||||
Emoji {
|
||||
cp: format!("{}", char::from_u32(u32::from_str_radix(fields.next().unwrap(), 16).unwrap()).unwrap()),
|
||||
name: fields.next().unwrap().to_string(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn respond(response: &PluginResponse) -> io::Result<()>
|
||||
{
|
||||
let encoded = serde_json::to_string(&response)?;
|
||||
print!("{}\n", encoded);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_emoji(emoji: &Emoji) -> Result<(), Box<dyn Error>>
|
||||
{
|
||||
let mut cmd =
|
||||
Command::new("xclip")
|
||||
.arg("-i")
|
||||
.arg("-selection").arg("clip")
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
cmd.stdin.as_ref().unwrap().write_all(&emoji.cp.as_bytes())?;
|
||||
|
||||
cmd.wait()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let emoji = read_emoji()?;
|
||||
|
||||
// println!("Read: {}", emoji.len());
|
||||
// for e in emoji.iter().take(10)
|
||||
// {
|
||||
// println!("{}: {}", e.name, e.cp);
|
||||
// }
|
||||
|
||||
let mut keep_going = true;
|
||||
|
||||
let mut buffer = String::new();
|
||||
let stdin = io::stdin();
|
||||
|
||||
let mut last_matches: Vec<&Emoji> = Vec::new();
|
||||
|
||||
while keep_going {
|
||||
if stdin.read_line(&mut buffer)? > 0
|
||||
{
|
||||
let request: Request = serde_json::from_str(&buffer)?;
|
||||
match request
|
||||
{
|
||||
Request::Activate(id) =>
|
||||
{
|
||||
copy_emoji(&last_matches[id as usize])?;
|
||||
respond(&PluginResponse::Close)?;
|
||||
}
|
||||
Request::Search(term) =>
|
||||
{
|
||||
let term =
|
||||
term.strip_prefix("e ")
|
||||
.unwrap_or_else(||{
|
||||
term.strip_prefix("emoji ").unwrap_or(term.as_str())
|
||||
});
|
||||
last_matches =
|
||||
emoji.iter()
|
||||
.filter(|e| e.name.contains(&term))
|
||||
.take(40)
|
||||
.collect();
|
||||
for (id, e) in last_matches.iter().enumerate()
|
||||
{
|
||||
respond(&PluginResponse::Append(
|
||||
PluginSearchResult {
|
||||
id: id as u32,
|
||||
name: e.cp.clone(),
|
||||
description: e.name.clone(),
|
||||
keywords: None,
|
||||
icon: None,
|
||||
exec: None,
|
||||
window: None
|
||||
}
|
||||
))?;
|
||||
}
|
||||
respond(&PluginResponse::Finished)?;
|
||||
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
buffer.clear();
|
||||
} else {
|
||||
keep_going = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -8,6 +8,7 @@ use std::io;
|
|||
use pass_menu::Menu;
|
||||
use launcher::{ Request, PluginResponse };
|
||||
use std::result::Result;
|
||||
use std::error::Error;
|
||||
|
||||
fn respond(response: &PluginResponse) -> io::Result<()>
|
||||
{
|
||||
|
@ -16,7 +17,7 @@ fn respond(response: &PluginResponse) -> io::Result<()>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn process_request(request_str: &String, menu: &Menu) -> Result<bool, String>
|
||||
fn process_request(request_str: &String, menu: &Menu) -> Result<bool, Box<dyn Error>>
|
||||
{
|
||||
let request: Request = serde_json::from_str(&request_str).map_err(|err| err.to_string())?;
|
||||
|
||||
|
|
|
@ -96,18 +96,18 @@ impl Menu
|
|||
ret
|
||||
}
|
||||
|
||||
pub fn activate(&self, id: u32) -> Result<(),String>
|
||||
pub fn activate(&self, id: u32) -> Result<(),Box<dyn std::error::Error>>
|
||||
{
|
||||
let idx = usize::try_from(id)
|
||||
.map_err(|_err| "Invalid index")?;
|
||||
if idx >= self.entries.len() { return Err(String::from("Invalid index")) }
|
||||
if idx >= self.entries.len() { return Ok(()); }
|
||||
let entry = &self.entries[idx];
|
||||
|
||||
Command::new("pass")
|
||||
.arg("-c")
|
||||
.arg(entry.full_name.as_str())
|
||||
.spawn()
|
||||
.map_err(|err| err.to_string())?;
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -143,16 +143,15 @@ impl Menu
|
|||
}
|
||||
|
||||
|
||||
pub fn activate_context(&self, id: u32, action: u32) -> Result<(), String>
|
||||
pub fn activate_context(&self, id: u32, action: u32) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
match num::FromPrimitive::from_u32(action)
|
||||
{
|
||||
Some(ContextAction::Copy) => self.activate(id)?,
|
||||
Some(ContextAction::Edit) => self.edit(id)?,
|
||||
None => return Err(String::from("Invalid context action"))
|
||||
None => ()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -188,7 +188,6 @@ fn main() -> Result<()> {
|
|||
// home_dir has a deprecation warning because it is broken on
|
||||
// windows... but PopOS is linux only.
|
||||
#[allow(deprecated)]
|
||||
|
||||
let mut config = env::home_dir().unwrap();
|
||||
config.push(".config");
|
||||
config.push("pop_todo.json");
|
||||
|
|
Loading…
Reference in New Issue