Merge branch 'dmitmel-master'

This commit is contained in:
Keanu Timmermans 2021-04-26 12:14:31 +02:00
commit 34fb04a9d2
Signed by: keanucode
GPG Key ID: A7431C0D513CA93B
42 changed files with 1361 additions and 1042 deletions

View File

@ -4,22 +4,22 @@
base16_name = "eighties" base16_name = "eighties"
name = "base16-" + base16_name name = "base16-" + base16_name
base16_colors = [ base16_colors = [
"#2d2d2d", # 0 "#2d2d2d", # 0
"#393939", # 1 "#393939", # 1
"#515151", # 2 "#515151", # 2
"#747369", # 3 "#747369", # 3
"#a09f93", # 4 "#a09f93", # 4
"#d3d0c8", # 5 "#d3d0c8", # 5
"#e8e6df", # 6 "#e8e6df", # 6
"#f2f0ec", # 7 "#f2f0ec", # 7
"#f2777a", # 8 "#f2777a", # 8
"#f99157", # 9 "#f99157", # 9
"#ffcc66", # a "#ffcc66", # a
"#99cc99", # b "#99cc99", # b
"#66cccc", # c "#66cccc", # c
"#6699cc", # d "#6699cc", # d
"#cc99cc", # e "#cc99cc", # e
"#d27b53", # f "#d27b53", # f
] ]
bg = base16_colors[0x0] bg = base16_colors[0x0]
@ -32,19 +32,18 @@ selection_bg = base16_colors[0x2]
selection_fg = fg selection_fg = fg
ansi_colors = [ ansi_colors = [
base16_colors[int(i, 16)] base16_colors[int(i, 16)] for i in "0 8 B A D E C 5 3 8 B A D E C 7 9 F 1 2 4 6".split()
for i in "0 8 B A D E C 5 3 8 B A D E C 7 9 F 1 2 4 6".split()
] ]
link_color = ansi_colors[0xC] link_color = ansi_colors[0xC]
css_variables_prefix = "dotfiles-colorscheme-" css_variables_prefix = "dotfiles-colorscheme-"
css_variables = { css_variables = {
"bg": bg, "bg": bg,
"fg": fg, "fg": fg,
"selection-bg": selection_bg, "selection-bg": selection_bg,
"selection-fg": selection_fg, "selection-fg": selection_fg,
"cursor-bg": cursor_bg, "cursor-bg": cursor_bg,
"cursor-fg": cursor_fg, "cursor-fg": cursor_fg,
**{"base-{:02X}".format(index): color for index, color in enumerate(base16_colors)}, **{"base-{:02X}".format(index): color for index, color in enumerate(base16_colors)},
} }

View File

@ -2,8 +2,9 @@
import _theme as theme import _theme as theme
print( print(
"""\ """\
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@ -13,9 +14,9 @@ print(
def print_color(key_name, color): def print_color(key_name, color):
r, g, b = [float(int(color[2 * i + 1 : 2 * i + 3], 16)) / 255 for i in range(3)] r, g, b = [float(int(color[2 * i + 1:2 * i + 3], 16)) / 255 for i in range(3)]
print( print(
"""\ """\
<key>{} Color</key> <key>{} Color</key>
<dict> <dict>
<key>Color Space</key> <key>Color Space</key>
@ -27,10 +28,8 @@ def print_color(key_name, color):
<key>Blue Component</key> <key>Blue Component</key>
<real>{}</real> <real>{}</real>
</dict>\ </dict>\
""".format( """.format(key_name, r, g, b)
key_name, r, g, b )
)
)
print_color("Background", theme.bg) print_color("Background", theme.bg)
@ -41,12 +40,10 @@ print_color("Cursor Text", theme.cursor_fg)
print_color("Selection Color", theme.selection_bg) print_color("Selection Color", theme.selection_bg)
print_color("Selected Text Color", theme.selection_fg) print_color("Selected Text Color", theme.selection_fg)
for index, color in enumerate(theme.ansi_colors[:16]): for index, color in enumerate(theme.ansi_colors[:16]):
print_color("Ansi " + str(index), color) print_color("Ansi " + str(index), color)
print_color("Link", theme.link_color) print_color("Link", theme.link_color)
print( print("""\
"""\
</dict> </dict>
</plist>\ </plist>\
""" """)
)

View File

@ -4,7 +4,7 @@ import _theme as theme
def print_color(key_name, color): def print_color(key_name, color):
print("{} {}".format(key_name, color)) print("{} {}".format(key_name, color))
print_color("background", theme.bg) print_color("background", theme.bg)
@ -14,7 +14,7 @@ print_color("cursor_text_color", theme.cursor_fg)
print_color("selection_background", theme.selection_bg) print_color("selection_background", theme.selection_bg)
print_color("selection_foreground", theme.selection_fg) print_color("selection_foreground", theme.selection_fg)
for index, color in enumerate(theme.ansi_colors[:16]): for index, color in enumerate(theme.ansi_colors[:16]):
print_color("color" + str(index), color) print_color("color" + str(index), color)
print_color("url_color", theme.link_color) print_color("url_color", theme.link_color)
print_color("active_border_color", theme.ansi_colors[2]) print_color("active_border_color", theme.ansi_colors[2])

View File

@ -4,11 +4,9 @@ import _theme as theme
import os import os
with open(os.path.join(os.path.dirname(__file__), "prismjs-theme-src.css")) as f: with open(os.path.join(os.path.dirname(__file__), "prismjs-theme-src.css")) as f:
css_src = f.read() css_src = f.read()
for var_name, color in theme.css_variables.items(): for var_name, color in theme.css_variables.items():
css_src = css_src.replace( css_src = css_src.replace("var(--{}{})".format(theme.css_variables_prefix, var_name), color)
"var(--{}{})".format(theme.css_variables_prefix, var_name), color
)
print(css_src) print(css_src)

View File

@ -8,11 +8,4 @@ import _theme as theme
# 0,0,0,0,170,170,170,170,85,85,85,85,255,255,255,255 # 0,0,0,0,170,170,170,170,85,85,85,85,255,255,255,255
for i in range(3): for i in range(3):
print( print(",".join([str(int(color[2 * i + 1:2 * i + 3], 16)) for color in theme.ansi_colors[:16]]))
",".join(
[
str(int(color[2 * i + 1 : 2 * i + 3], 16))
for color in theme.ansi_colors[:16]
]
)
)

View File

@ -4,11 +4,11 @@ import _theme as theme
def print_color(key_name, color): def print_color(key_name, color):
print("{}={}".format(key_name, color)) print("{}={}".format(key_name, color))
print_color("background", theme.bg) print_color("background", theme.bg)
print_color("foreground", theme.fg) print_color("foreground", theme.fg)
print_color("cursor", theme.cursor_bg) print_color("cursor", theme.cursor_bg)
for index, color in enumerate(theme.ansi_colors[:16]): for index, color in enumerate(theme.ansi_colors[:16]):
print_color("color" + str(index), color) print_color("color" + str(index), color)

View File

@ -2,7 +2,8 @@
import _theme as theme import _theme as theme
print(":root {") print(":root {")
for var_name, color in theme.css_variables.items(): for var_name, color in theme.css_variables.items():
print(" --{}{}: {};".format(theme.css_variables_prefix, var_name, color)) print(" --{}{}: {};".format(theme.css_variables_prefix, var_name, color))
print("}") print("}")

View File

@ -2,20 +2,21 @@
import _theme as theme import _theme as theme
print("let dotfiles_colorscheme_name = '{}'".format(theme.name)) print("let dotfiles_colorscheme_name = '{}'".format(theme.name))
print("let dotfiles_colorscheme_base16_name = '{}'".format(theme.base16_name)) print("let dotfiles_colorscheme_base16_name = '{}'".format(theme.base16_name))
print("let dotfiles_colorscheme_base16_colors = [") print("let dotfiles_colorscheme_base16_colors = [")
gui_to_cterm_mapping = [0, 18, 19, 8, 20, 7, 21, 15, 1, 16, 3, 2, 6, 4, 5, 17] gui_to_cterm_mapping = [0, 18, 19, 8, 20, 7, 21, 15, 1, 16, 3, 2, 6, 4, 5, 17]
for colors_pair in zip(theme.base16_colors[:16], gui_to_cterm_mapping): for colors_pair in zip(theme.base16_colors[:16], gui_to_cterm_mapping):
print("\\ {{'gui': '{}', 'cterm': '{:>02}'}},".format(*colors_pair)) print("\\ {{'gui': '{}', 'cterm': '{:>02}'}},".format(*colors_pair))
print("\\ ]") print("\\ ]")
def print_terminal_color(key_name, color): def print_terminal_color(key_name, color):
print("let terminal_color_{} = '{}'".format(key_name, color)) print("let terminal_color_{} = '{}'".format(key_name, color))
print_terminal_color("background", theme.bg) print_terminal_color("background", theme.bg)
print_terminal_color("foreground", theme.fg) print_terminal_color("foreground", theme.fg)
for index, color in enumerate(theme.ansi_colors[:16]): for index, color in enumerate(theme.ansi_colors[:16]):
print_terminal_color(str(index), color) print_terminal_color(str(index), color)

View File

@ -5,31 +5,27 @@ import json
ANSI_COLOR_NAMES = [ ANSI_COLOR_NAMES = [
"Black", "Black",
"Red", "Red",
"Green", "Green",
"Yellow", "Yellow",
"Blue", "Blue",
"Magenta", "Magenta",
"Cyan", "Cyan",
"White", "White",
] ]
colors = { colors = {
"terminal.background": theme.bg, "terminal.background": theme.bg,
"terminal.foreground": theme.fg, "terminal.foreground": theme.fg,
"terminal.selectionBackground": theme.selection_bg, "terminal.selectionBackground": theme.selection_bg,
"terminalCursor.background": theme.cursor_fg, "terminalCursor.background": theme.cursor_fg,
"terminalCursor.foreground": theme.cursor_bg, "terminalCursor.foreground": theme.cursor_bg,
} }
for color_brightness in [False, True]: for color_brightness in [False, True]:
for color_index, color_name in enumerate(ANSI_COLOR_NAMES): for color_index, color_name in enumerate(ANSI_COLOR_NAMES):
color = theme.ansi_colors[ color = theme.ansi_colors[color_index + int(color_brightness) * len(ANSI_COLOR_NAMES)]
color_index + int(color_brightness) * len(ANSI_COLOR_NAMES) colors["terminal.ansi" + ("Bright" if color_brightness else "") + color_name] = color
]
colors[
"terminal.ansi" + ("Bright" if color_brightness else "") + color_name
] = color
print(json.dumps(colors, ensure_ascii=False, indent=2)) print(json.dumps(colors, ensure_ascii=False, indent=2))

View File

@ -2,6 +2,7 @@
import _theme as theme import _theme as theme
print("[Scheme]") print("[Scheme]")
print("Name=dmitmel's dotfiles colorscheme") print("Name=dmitmel's dotfiles colorscheme")
print("ColorForeground={}".format(theme.fg)) print("ColorForeground={}".format(theme.fg))

View File

@ -2,19 +2,18 @@
import _theme as theme import _theme as theme
for attr in [ for attr in [
"bg", "bg",
"fg", "fg",
"cursor_bg", "cursor_bg",
"cursor_fg", "cursor_fg",
"selection_bg", "selection_bg",
"selection_fg", "selection_fg",
"link_color", "link_color",
]: ]:
color = getattr(theme, attr) color = getattr(theme, attr)
print("colorscheme_{}={}".format(attr, color[1:])) print("colorscheme_{}={}".format(attr, color[1:]))
print("colorscheme_ansi_colors=(") print("colorscheme_ansi_colors=(")
for color in theme.ansi_colors: for color in theme.ansi_colors:
print(" {}".format(color[1:])) print(" {}".format(color[1:]))
print(")") print(")")

View File

@ -0,0 +1 @@
setlocal nofoldenable foldmethod=manual

View File

@ -1,3 +1 @@
let g:haskall_test = 1 setlocal foldmethod< foldtext<
setlocal foldmethod<
setlocal foldtext<

View File

@ -1,2 +1 @@
setlocal foldmethod< setlocal foldmethod< foldtext<
setlocal foldtext<

View File

@ -1,2 +1 @@
setlocal foldmethod< setlocal foldmethod< foldtext<
setlocal foldtext<

View File

@ -4,5 +4,4 @@
" sourced in `syntax/nginx.vim` in vim-polyglot, which resets the `commentstring` " sourced in `syntax/nginx.vim` in vim-polyglot, which resets the `commentstring`
" set in `ftplugin/nginx.vim` and sets `comments` to some garbage. This script " set in `ftplugin/nginx.vim` and sets `comments` to some garbage. This script
" undoes that damage. " undoes that damage.
setlocal comments< setlocal comments< commentstring=#%s
setlocal commentstring=#%s

View File

@ -1,12 +1,15 @@
let g:coc_global_extensions += ['coc-pyright'] let g:coc_global_extensions += ['coc-pyright']
let g:coc_filetypes += ['python'] let g:coc_filetypes += ['python']
" let g:coc_user_config['pyls.plugins.pycodestyle.ignore'] = ['E501']
" let g:coc_user_config['python.autocomplete.showAdvancedMembers'] = v:false " let g:coc_user_config['python.autocomplete.showAdvancedMembers'] = v:false
let g:coc_user_config['python'] = { let g:coc_user_config['python'] = {
\ 'formatting': { 'provider': 'black' }, \ 'formatting': {
\ 'provider': 'yapf',
\ 'yapfArgs': ['--style=' . simplify(g:nvim_dotfiles_dir.'/../python/yapf.ini')]
\ },
\ 'linting': { \ 'linting': {
\ 'pylintEnabled': v:false, \ 'pylintEnabled': v:false,
\ 'flake8Enabled': v:true, \ 'flake8Enabled': v:true,
\ 'flake8Args': ['--ignore', 'E501'], \ 'flake8Args': ['--config=' . simplify(g:nvim_dotfiles_dir.'/../python/flake8.ini')],
\ }, \ },
\ } \ }

View File

@ -16,6 +16,7 @@
Plug 'tpope/vim-repeat' Plug 'tpope/vim-repeat'
" if g:vim_ide " if g:vim_ide
Plug 'tomtom/tcomment_vim' Plug 'tomtom/tcomment_vim'
Plug 'glts/vim-textobj-comment'
" else " else
" Plug 'tpope/vim-commentary' " Plug 'tpope/vim-commentary'
" endif " endif
@ -44,9 +45,6 @@
Plug 'vim-airline/vim-airline' Plug 'vim-airline/vim-airline'
Plug 'tpope/vim-obsession' Plug 'tpope/vim-obsession'
Plug 'romainl/vim-qf' Plug 'romainl/vim-qf'
if g:vim_ide
Plug 'dyng/ctrlsf.vim'
endif
" }}} " }}}
" Git {{{ " Git {{{

View File

@ -58,23 +58,17 @@ set commentstring=//%s
" Cursor and Scrolling {{{ " Cursor and Scrolling {{{
set number relativenumber cursorline
set number
set relativenumber
set cursorline
" remember cursor position " remember cursor position
augroup vimrc-editing-remember-cursor-position augroup vimrc-editing-remember-cursor-position
autocmd! autocmd!
autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exec "normal! g`\"" | endif autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exec "normal! g`\"" | endif
augroup END augroup END
" }}} " }}}
" Wrapping {{{ " Wrapping {{{
set nowrap set nowrap colorcolumn=81,101,121
set colorcolumn=81,101,121
" }}} " }}}
@ -236,6 +230,7 @@ set commentstring=//%s
" Remove the mappings that I won't use " Remove the mappings that I won't use
let g:tcomment_maps = 0 let g:tcomment_maps = 0
" Closely replicate the behavior of tpope/vim-commentary
nmap <silent> gc <Plug>TComment_gc nmap <silent> gc <Plug>TComment_gc
nmap <silent> gcc <Plug>TComment_gcc nmap <silent> gcc <Plug>TComment_gcc
nmap <silent> gC <Plug>TComment_gcb nmap <silent> gC <Plug>TComment_gcb
@ -244,6 +239,8 @@ set commentstring=//%s
nmap <silent> gCC m'o<Esc>''<Plug>TComment_gcb+ nmap <silent> gCC m'o<Esc>''<Plug>TComment_gcb+
xnoremap <silent> gc :TCommentMaybeInline<CR> xnoremap <silent> gc :TCommentMaybeInline<CR>
xnoremap <silent> gC :TCommentBlock<CR> xnoremap <silent> gC :TCommentBlock<CR>
" Make an alias for the comment text object
omap <silent> gc ac
" }}} " }}}

View File

@ -20,6 +20,23 @@ nnoremap <silent><expr> <CR> empty(&buftype) ? ":write<bar>wall\<CR>" : "\<CR>"
command! -bang -nargs=* Rg call fzf#vim#grep(s:rg_cmd . ' --column --line-number --no-heading --fixed-strings --smart-case --color always ' . shellescape(<q-args>), 1, <bang>0) command! -bang -nargs=* Rg call fzf#vim#grep(s:rg_cmd . ' --column --line-number --no-heading --fixed-strings --smart-case --color always ' . shellescape(<q-args>), 1, <bang>0)
command! -bang -nargs=* Find Rg<bang> <args> command! -bang -nargs=* Find Rg<bang> <args>
endif endif
nnoremap <leader>/ :<C-u>grep<space>
function! s:grep_mapping_star_normal()
let word = expand("<cword>")
if !empty(word)
call feedkeys(":\<C-u>grep " . shellescape('\b' . word . '\b'), 'n')
endif
endfunction
function! s:grep_mapping_star_visual()
let tmp = @"
normal! y
call feedkeys(":\<C-u>grep " . shellescape(@"), 'n')
let @" = tmp
endfunction
nnoremap <leader>* <Cmd>call <SID>grep_mapping_star_normal()<CR>
xnoremap <leader>* <Cmd>call <SID>grep_mapping_star_visual()<CR>
" }}} " }}}
@ -29,7 +46,10 @@ nnoremap <silent><expr> <CR> empty(&buftype) ? ":write<bar>wall\<CR>" : "\<CR>"
let g:loaded_netrwPlugin = 1 let g:loaded_netrwPlugin = 1
" re-add Netrw's gx mappings since we've disabled them " re-add Netrw's gx mappings since we've disabled them
nnoremap <silent> gx <Cmd>call netrw#BrowseX(expand('<cfile>'),netrw#CheckIfRemote())<CR> nnoremap <silent> gx <Cmd>call netrw#BrowseX(expand('<cfile>'),netrw#CheckIfRemote())<CR>
xnoremap <silent> gx <Cmd>call netrw#BrowseXVis()<CR> " This one can be rewritten in a way to not clobber the yank register...
" Most notably, the built-in mapping, which uses netrw#BrowseXVis(), doesn't
" work and breaks the editor, at least for me.
xnoremap <silent> gx y:<C-u>call netrw#BrowseX(@",netrw#CheckIfRemote())<CR>
" }}} " }}}
@ -176,10 +196,3 @@ nnoremap <silent><expr> <CR> empty(&buftype) ? ":write<bar>wall\<CR>" : "\<CR>"
augroup END augroup END
" }}} " }}}
" CtrlSF {{{
nmap <leader>/ <Plug>CtrlSFPrompt
nmap <leader>* <Plug>CtrlSFCwordPath
xmap <leader>* <Plug>CtrlSFVwordPath
" }}}

12
python/flake8.ini Normal file
View File

@ -0,0 +1,12 @@
[flake8]
ignore =
# Indent is not a multiple of 4
E111
# Indent is not a multiple of 4 for comments
E114
# Indent for continuation lines is smaller than expected
E121
# Import not at the top of the file
E402
# Line too long
E501

9
python/yapf.ini Normal file
View File

@ -0,0 +1,9 @@
[style]
based_on_style = google
column_limit = 99
indent_width = 2
continuation_indent_width = 2
blank_lines_between_top_level_imports_and_variables = 2
dedent_closing_brackets = true
coalesce_brackets = true
spaces_around_power_operator = true

View File

@ -4,95 +4,90 @@ import subprocess
from pathlib import Path from pathlib import Path
from typing import Iterable, NoReturn from typing import Iterable, NoReturn
if os.name == "posix": if os.name == "posix":
DOTFILES_CONFIG_DIR: Path = Path.home() / ".config" / "dotfiles" DOTFILES_CONFIG_DIR: Path = Path.home() / ".config" / "dotfiles"
DOTFILES_CACHE_DIR: Path = Path.home() / ".cache" / "dotfiles" DOTFILES_CACHE_DIR: Path = Path.home() / ".cache" / "dotfiles"
def platform_not_supported_error() -> NoReturn: def platform_not_supported_error() -> NoReturn:
raise Exception("platform '{}' is not supported!".format(sys.platform)) raise Exception("platform '{}' is not supported!".format(sys.platform))
def run_chooser( def run_chooser(choices: Iterable[str], prompt: str = None, async_read: bool = False) -> int:
choices: Iterable[str], prompt: str = None, async_read: bool = False supports_result_index = True
) -> int: if os.isatty(sys.stderr.fileno()):
supports_result_index = True process_args = [
if os.isatty(sys.stderr.fileno()): "fzf",
process_args = [ "--with-nth=2..",
"fzf", "--height=50%",
"--with-nth=2..", "--reverse",
"--height=50%", "--tiebreak=index",
"--reverse", ]
"--tiebreak=index", supports_result_index = False
] elif sys.platform == "darwin":
supports_result_index = False process_args = ["choose", "-i"]
elif sys.platform == "darwin": elif os.name == "posix":
process_args = ["choose", "-i"] process_args = ["rofi", "-dmenu", "-i", "-format", "i"]
elif os.name == "posix": if prompt is not None:
process_args = ["rofi", "-dmenu", "-i", "-format", "i"] process_args += ["-p", prompt]
if prompt is not None: if async_read:
process_args += ["-p", prompt] process_args += ["-async-pre-read", "0"]
if async_read: else:
process_args += ["-async-pre-read", "0"] platform_not_supported_error()
else:
platform_not_supported_error()
chooser_process = subprocess.Popen( chooser_process = subprocess.Popen(process_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
process_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
with chooser_process.stdin as pipe: with chooser_process.stdin as pipe:
for index, choice in enumerate(choices): for index, choice in enumerate(choices):
assert "\n" not in choice assert "\n" not in choice
if not supports_result_index: if not supports_result_index:
pipe.write(str(index).encode()) pipe.write(str(index).encode())
pipe.write(b" ") pipe.write(b" ")
pipe.write(choice.encode()) pipe.write(choice.encode())
pipe.write(b"\n") pipe.write(b"\n")
exit_code: int = chooser_process.wait() exit_code: int = chooser_process.wait()
if exit_code != 0: if exit_code != 0:
raise Exception("chooser process failed with exit code {}".format(exit_code)) raise Exception("chooser process failed with exit code {}".format(exit_code))
chosen_index = int(chooser_process.stdout.read().strip().split()[0]) chosen_index = int(chooser_process.stdout.read().strip().split()[0])
return chosen_index return chosen_index
def send_notification(title: str, message: str, url: str = None) -> None: def send_notification(title: str, message: str, url: str = None) -> None:
if sys.platform == "darwin": if sys.platform == "darwin":
process_args = [ process_args = [
"terminal-notifier", "terminal-notifier",
"-title", "-title",
title, title,
"-message", "-message",
message, message,
"-open", "-open",
] ]
if url is not None: if url is not None:
process_args += [url] process_args += [url]
elif os.name == "posix": elif os.name == "posix":
process_args = [ process_args = [
"notify-send", "notify-send",
"--icon=utilities-terminal", "--icon=utilities-terminal",
"--expire-time=3000", "--expire-time=3000",
title, title,
message, message,
] ]
else: else:
platform_not_supported_error() platform_not_supported_error()
subprocess.run(process_args, check=True) subprocess.run(process_args, check=True)
def set_clipboard(text: str) -> None: def set_clipboard(text: str) -> None:
# TODO: somehow merge program selection with the logic in `zsh/functions.zsh` # TODO: somehow merge program selection with the logic in `zsh/functions.zsh`
if sys.platform == "darwin": if sys.platform == "darwin":
process_args = ["pbcopy"] process_args = ["pbcopy"]
elif os.name == "posix": elif os.name == "posix":
process_args = ["xsel", "--clipboard", "--input"] process_args = ["xsel", "--clipboard", "--input"]
# process_args = ["xclip", "-in", "-selection", "clipboard"] # process_args = ["xclip", "-in", "-selection", "clipboard"]
else: else:
platform_not_supported_error() platform_not_supported_error()
subprocess.run(process_args, input=text.encode(), check=True) subprocess.run(process_args, input=text.encode(), check=True)

View File

@ -9,58 +9,58 @@ from typing import Any, IO
def read_bool(buf: IO[bytes]) -> bool: def read_bool(buf: IO[bytes]) -> bool:
return buf.read(1)[0] == 1 return buf.read(1)[0] == 1
def read_number(buf: IO[bytes]) -> float: def read_number(buf: IO[bytes]) -> float:
return struct.unpack("<d", buf.read(8))[0] return struct.unpack("<d", buf.read(8))[0]
def _read_length(buf: IO[bytes]) -> int: def _read_length(buf: IO[bytes]) -> int:
return struct.unpack("<I", buf.read(4))[0] return struct.unpack("<I", buf.read(4))[0]
def read_string(buf: IO[bytes]) -> str: def read_string(buf: IO[bytes]) -> str:
is_empty = read_bool(buf) is_empty = read_bool(buf)
if is_empty: if is_empty:
return "" return ""
len_ = buf.read(1)[0] len_ = buf.read(1)[0]
if len_ == 0xFF: if len_ == 0xFF:
len_ = _read_length(buf) len_ = _read_length(buf)
return buf.read(len_).decode("utf8") return buf.read(len_).decode("utf8")
def read_dictionary(buf: IO[bytes]) -> dict[str, Any]: def read_dictionary(buf: IO[bytes]) -> dict[str, Any]:
len_ = _read_length(buf) len_ = _read_length(buf)
value: dict[str, Any] = {} value: dict[str, Any] = {}
for _ in range(len_): for _ in range(len_):
key = read_string(buf) key = read_string(buf)
value[key] = read(buf) value[key] = read(buf)
return value return value
def read_list(buf: IO[bytes]) -> list[Any]: def read_list(buf: IO[bytes]) -> list[Any]:
len_ = _read_length(buf) len_ = _read_length(buf)
value: list[Any] = [] value: list[Any] = []
for _ in range(len_): for _ in range(len_):
read_string(buf) read_string(buf)
value.append(read(buf)) value.append(read(buf))
return value return value
def read(buf: IO[bytes]) -> Any: def read(buf: IO[bytes]) -> Any:
type_, _any_type_flag = buf.read(2) type_, _any_type_flag = buf.read(2)
if type_ == 0: if type_ == 0:
return None return None
elif type_ == 1: elif type_ == 1:
return read_bool(buf) return read_bool(buf)
elif type_ == 2: elif type_ == 2:
return read_number(buf) return read_number(buf)
elif type_ == 3: elif type_ == 3:
return read_string(buf) return read_string(buf)
elif type_ == 4: elif type_ == 4:
return read_list(buf) return read_list(buf)
elif type_ == 5: elif type_ == 5:
return read_dictionary(buf) return read_dictionary(buf)
else: else:
raise Exception("unknown property tree type 0x{:02x}".format(type_)) raise Exception("unknown property tree type 0x{:02x}".format(type_))

View File

@ -3,29 +3,29 @@ from fractions import Fraction
def factors(n): def factors(n):
result = set() result = set()
for i in range(1, int(sqrt(n)) + 1): for i in range(1, int(sqrt(n)) + 1):
if n % i == 0: if n % i == 0:
result.add(i) result.add(i)
result.add(n // i) result.add(n // i)
return result return result
def solve_quadratic(a, b, c): def solve_quadratic(a, b, c):
if a == 0: if a == 0:
raise Exception("not a quadratic equation") raise Exception("not a quadratic equation")
else:
d = b ** 2 - 4 * a * c
print("D = " + str(d))
if d < 0:
print("no solutions")
elif d > 0:
sd = sqrt(d)
print("sqrt(D) = " + str(sd))
print("x1 = " + str((-b + sd) / (2 * a)))
print("x2 = " + str((-b - sd) / (2 * a)))
else: else:
d = b ** 2 - 4 * a * c print("x = " + str(-b / (2 * a)))
print("D = " + str(d))
if d < 0:
print("no solutions")
elif d > 0:
sd = sqrt(d)
print("sqrt(D) = " + str(sd))
print("x1 = " + str((-b + sd) / (2 * a)))
print("x2 = " + str((-b - sd) / (2 * a)))
else:
print("x = " + str(-b / (2 * a)))
print("loaded Python calculator") print("loaded Python calculator")

View File

@ -1,21 +1,22 @@
from colorama import Fore, Style, ansi from colorama import Fore, Style, ansi
COLORS = [ansi.code_to_chars(30 + color_index) for color_index in range(0, 8)] COLORS = [ansi.code_to_chars(30 + color_index) for color_index in range(0, 8)]
def colored(string, *colors): def colored(string, *colors):
return "".join(colors + (string, Style.RESET_ALL)) return "".join(colors + (string, Style.RESET_ALL))
def bright_colored(string, *colors): def bright_colored(string, *colors):
return "".join(colors + (Style.BRIGHT, string, Style.RESET_ALL)) return "".join(colors + (Style.BRIGHT, string, Style.RESET_ALL))
def colorize_percent(percent, warning, critical, inverse=False): def colorize_percent(percent, warning, critical, inverse=False):
COLORS = [Fore.GREEN, Fore.YELLOW, Fore.RED] COLORS = [Fore.GREEN, Fore.YELLOW, Fore.RED]
color_index = 0 if percent < warning else 1 if percent < critical else 2 color_index = 0 if percent < warning else 1 if percent < critical else 2
if inverse: if inverse:
color_index = 2 - color_index color_index = 2 - color_index
return colored("%.2f%%" % percent, COLORS[color_index]) return colored("%.2f%%" % percent, COLORS[color_index])

View File

@ -1,34 +1,34 @@
def humanize_timedelta(timedelta): def humanize_timedelta(timedelta):
result = [] result = []
days = timedelta.days days = timedelta.days
mm, ss = divmod(timedelta.seconds, 60) mm, ss = divmod(timedelta.seconds, 60)
hh, mm = divmod(mm, 60) hh, mm = divmod(mm, 60)
def plural(n): def plural(n):
return n, "s" if abs(n) != 1 else "" return n, "s" if abs(n) != 1 else ""
if days > 0: if days > 0:
result.append("%d day%s" % plural(days)) result.append("%d day%s" % plural(days))
if hh > 0 or result: if hh > 0 or result:
result.append("%d hour%s" % plural(hh)) result.append("%d hour%s" % plural(hh))
if mm > 0 or result: if mm > 0 or result:
result.append("%d min%s" % plural(mm)) result.append("%d min%s" % plural(mm))
if len(result) <= 1: if len(result) <= 1:
result.append("%d sec%s" % plural(ss)) result.append("%d sec%s" % plural(ss))
return ", ".join(result) return ", ".join(result)
def humanize_bytes(bytes): def humanize_bytes(bytes):
units = ["B", "kB", "MB", "GB"] units = ["B", "kB", "MB", "GB"]
factor = 1 factor = 1
unit = "" unit = ""
for unit in units: for unit in units:
next_factor = factor << 10 next_factor = factor << 10
if bytes < next_factor: if bytes < next_factor:
break break
factor = next_factor factor = next_factor
return "%.2f %s" % (float(bytes) / factor, unit) return "%.2f %s" % (float(bytes) / factor, unit)

View File

@ -5,6 +5,7 @@ import re
from colors import COLORS, Style from colors import COLORS, Style
from system_info import get_system_info from system_info import get_system_info
print("") print("")
logo_lines, info_lines = get_system_info() logo_lines, info_lines = get_system_info()
@ -12,24 +13,24 @@ logo_line_widths = [len(re.sub(r"{\d}", "", line)) for line in logo_lines]
logo_width = max(logo_line_widths) logo_width = max(logo_line_widths)
for line_index in range(0, max(len(logo_lines), len(info_lines))): for line_index in range(0, max(len(logo_lines), len(info_lines))):
line = "" line = ""
logo_line_width = 0 logo_line_width = 0
if line_index < len(logo_lines): if line_index < len(logo_lines):
logo_line = logo_lines[line_index] logo_line = logo_lines[line_index]
logo_line_width = logo_line_widths[line_index] logo_line_width = logo_line_widths[line_index]
line += Style.BRIGHT line += Style.BRIGHT
line += logo_line.format(*COLORS) line += logo_line.format(*COLORS)
line += Style.RESET_ALL line += Style.RESET_ALL
line += " " * (logo_width - logo_line_width + 3) line += " " * (logo_width - logo_line_width + 3)
if line_index < len(info_lines): if line_index < len(info_lines):
info_line = info_lines[line_index] info_line = info_lines[line_index]
line += info_line line += info_line
print(line) print(line)
print("") print("")

View File

@ -11,206 +11,200 @@ from humanize import humanize_bytes, humanize_timedelta
def get_system_info(): def get_system_info():
info_lines = [] info_lines = []
def info(name, value, *format_args): def info(name, value, *format_args):
line = bright_colored(name + ":", Fore.YELLOW) + " " + value line = bright_colored(name + ":", Fore.YELLOW) + " " + value
if format_args: if format_args:
line = line % format_args line = line % format_args
info_lines.append(line) info_lines.append(line)
username = getuser() username = getuser()
hostname = _get_hostname() hostname = _get_hostname()
info_lines.append( info_lines.append(bright_colored(username, Fore.BLUE) + "@" + bright_colored(hostname, Fore.RED))
bright_colored(username, Fore.BLUE) + "@" + bright_colored(hostname, Fore.RED) info_lines.append("")
)
info_lines.append("")
distro_id, distro_name, distro_version, distro_codename = _get_distro_info() distro_id, distro_name, distro_version, distro_codename = _get_distro_info()
info("OS", " ".join([distro_name, distro_version, distro_codename])) info("OS", " ".join([distro_name, distro_version, distro_codename]))
logo_path = os.path.join(os.path.dirname(__file__), "logos", distro_id) logo_path = os.path.join(os.path.dirname(__file__), "logos", distro_id)
with open(logo_path) as logo_file: with open(logo_path) as logo_file:
logo_lines = logo_file.read().splitlines() logo_lines = logo_file.read().splitlines()
info("Kernel", "%s %s", platform.system(), platform.release()) info("Kernel", "%s %s", platform.system(), platform.release())
info("Uptime", humanize_timedelta(_get_uptime())) info("Uptime", humanize_timedelta(_get_uptime()))
users_info = _get_users() users_info = _get_users()
if users_info: if users_info:
info("Users", users_info) info("Users", users_info)
shell = _get_shell() shell = _get_shell()
if shell is not None: if shell is not None:
info("Shell", shell) info("Shell", shell)
info_lines.append("") info_lines.append("")
cpu_usage_info = _get_cpu_usage() cpu_usage_info = _get_cpu_usage()
if cpu_usage_info is not None: if cpu_usage_info is not None:
info("CPU Usage", "%s", cpu_usage_info) info("CPU Usage", "%s", cpu_usage_info)
info("Memory", "%s / %s (%s)", *_get_memory()) info("Memory", "%s / %s (%s)", *_get_memory())
for disk_info in _get_disks(): for disk_info in _get_disks():
info("Disk (%s)", "%s / %s (%s)", *disk_info) info("Disk (%s)", "%s / %s (%s)", *disk_info)
battery_info = _get_battery() battery_info = _get_battery()
if battery_info is not None: if battery_info is not None:
info("Battery", "%s (%s)", *battery_info) info("Battery", "%s (%s)", *battery_info)
info_lines.append("") info_lines.append("")
for local_ip_address in _get_local_ipv4_addresses(): for local_ip_address in _get_local_ipv4_addresses():
info("Local IPv4 Address (%s)", "%s", *local_ip_address) info("Local IPv4 Address (%s)", "%s", *local_ip_address)
return logo_lines, info_lines return logo_lines, info_lines
def _get_hostname(): def _get_hostname():
hostname = socket.gethostname() hostname = socket.gethostname()
return hostname return hostname
def _get_uptime(): def _get_uptime():
return datetime.now() - datetime.fromtimestamp(psutil.boot_time()) return datetime.now() - datetime.fromtimestamp(psutil.boot_time())
def _get_users(): def _get_users():
users = {} users = {}
for user in psutil.users(): for user in psutil.users():
name = user.name name = user.name
terminal = user.terminal terminal = user.terminal
if name in users: if name in users:
users[name].append(terminal) users[name].append(terminal)
else: else:
users[name] = [terminal] users[name] = [terminal]
result = [] result = []
for name in users: for name in users:
terminals = users[name] terminals = users[name]
colored_name = bright_colored(name, Fore.BLUE) colored_name = bright_colored(name, Fore.BLUE)
colored_terminals = [ colored_terminals = [colored(str(term), Style.DIM, Fore.WHITE) for term in terminals]
colored(str(term), Style.DIM, Fore.WHITE) for term in terminals
]
terminals_str = ", ".join(colored_terminals) terminals_str = ", ".join(colored_terminals)
if len(colored_terminals) > 1: if len(colored_terminals) > 1:
terminals_str = "(%s)" % terminals_str terminals_str = "(%s)" % terminals_str
result.append(colored_name + "@" + terminals_str) result.append(colored_name + "@" + terminals_str)
return ", ".join(result) return ", ".join(result)
def _get_shell(): def _get_shell():
return os.environ.get("SHELL") return os.environ.get("SHELL")
def _get_cpu_usage(): def _get_cpu_usage():
try: try:
percent = psutil.cpu_percent() percent = psutil.cpu_percent()
except Exception as e: except Exception as e:
print("Error in _get_cpu_usage:", e) print("Error in _get_cpu_usage:", e)
return None return None
return colorize_percent(percent, warning=60, critical=80) return colorize_percent(percent, warning=60, critical=80)
def _get_memory(): def _get_memory():
memory = psutil.virtual_memory() memory = psutil.virtual_memory()
return ( return (
humanize_bytes(memory.used), humanize_bytes(memory.used),
humanize_bytes(memory.total), humanize_bytes(memory.total),
colorize_percent(memory.percent, warning=60, critical=80), colorize_percent(memory.percent, warning=60, critical=80),
) )
def _get_disks(): def _get_disks():
result = [] result = []
for disk in psutil.disk_partitions(all=False): for disk in psutil.disk_partitions(all=False):
if psutil.WINDOWS and ("cdrom" in disk.opts or disk.fstype == ""): if psutil.WINDOWS and ("cdrom" in disk.opts or disk.fstype == ""):
# skip cd-rom drives with no disk in it on Windows; they may raise # skip cd-rom drives with no disk in it on Windows; they may raise
# ENOENT, pop-up a Windows GUI error for a non-ready partition or # ENOENT, pop-up a Windows GUI error for a non-ready partition or
# just hang # just hang
continue continue
usage = psutil.disk_usage(disk.mountpoint) usage = psutil.disk_usage(disk.mountpoint)
result.append( result.append((
( disk.mountpoint,
disk.mountpoint, humanize_bytes(usage.used),
humanize_bytes(usage.used), humanize_bytes(usage.total),
humanize_bytes(usage.total), colorize_percent(usage.percent, warning=70, critical=85),
colorize_percent(usage.percent, warning=70, critical=85), ))
)
)
return result return result
def _get_battery(): def _get_battery():
if not hasattr(psutil, "sensors_battery"): if not hasattr(psutil, "sensors_battery"):
return None return None
try: try:
battery = psutil.sensors_battery() battery = psutil.sensors_battery()
except Exception as e: except Exception as e:
print("Error in _get_battery:", e) print("Error in _get_battery:", e)
return None return None
if battery is None: if battery is None:
return None return None
percent = battery.percent percent = battery.percent
if battery.power_plugged: if battery.power_plugged:
status = "charging" if percent < 100 else "fully charged" status = "charging" if percent < 100 else "fully charged"
else: else:
status = "%s left" % humanize_timedelta(timedelta(seconds=battery.secsleft)) status = "%s left" % humanize_timedelta(timedelta(seconds=battery.secsleft))
return colorize_percent(percent, critical=10, warning=20, inverse=True), status return colorize_percent(percent, critical=10, warning=20, inverse=True), status
def _get_local_ipv4_addresses(): def _get_local_ipv4_addresses():
result = [] result = []
for interface, addresses in psutil.net_if_addrs().items(): for interface, addresses in psutil.net_if_addrs().items():
for address in addresses: for address in addresses:
if address.family != socket.AF_INET: if address.family != socket.AF_INET:
# allow only IPv4 addresses (skip IPv6 and MAC, for example) # allow only IPv4 addresses (skip IPv6 and MAC, for example)
continue continue
if interface.startswith("lo"): if interface.startswith("lo"):
# skip loopback interfaces # skip loopback interfaces
continue continue
result.append((interface, address.address)) result.append((interface, address.address))
return result return result
def _get_distro_info(): def _get_distro_info():
if psutil.WINDOWS: if psutil.WINDOWS:
return "windows", platform.system(), platform.release(), "" return "windows", platform.system(), platform.release(), ""
elif psutil.OSX: elif psutil.OSX:
import plistlib import plistlib
with open("/System/Library/CoreServices/SystemVersion.plist", "rb") as f: with open("/System/Library/CoreServices/SystemVersion.plist", "rb") as f:
sw_vers = plistlib.load(f) sw_vers = plistlib.load(f)
return "mac", sw_vers["ProductName"], sw_vers["ProductVersion"], "" return "mac", sw_vers["ProductName"], sw_vers["ProductVersion"], ""
elif _is_android(): elif _is_android():
from subprocess import check_output from subprocess import check_output
android_version = check_output(["getprop", "ro.build.version.release"]) android_version = check_output(["getprop", "ro.build.version.release"])
return "android", "Android", android_version.decode().strip(), "" return "android", "Android", android_version.decode().strip(), ""
elif psutil.LINUX: elif psutil.LINUX:
import distro import distro
return distro.id(), distro.name(), distro.version(), distro.codename() return distro.id(), distro.name(), distro.version(), distro.codename()
raise NotImplementedError("unsupported OS") raise NotImplementedError("unsupported OS")
def _is_android(): def _is_android():
return os.path.isdir("/system/app") and os.path.isdir("/system/priv-app") return os.path.isdir("/system/app") and os.path.isdir("/system/priv-app")

View File

@ -9,88 +9,76 @@ from typing import Any, Generator, Optional, Union
import urllib.parse import urllib.parse
import urllib.request import urllib.request
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources")) sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
import common_script_utils import common_script_utils
DEFAULT_REGISTRY_DUMP_URL = "https://stronghold.crosscode.ru/~ccbot/emote-registry.json" DEFAULT_REGISTRY_DUMP_URL = "https://stronghold.crosscode.ru/~ccbot/emote-registry.json"
if os.name == "posix": if os.name == "posix":
config_path: Path = ( config_path: Path = (common_script_utils.DOTFILES_CONFIG_DIR / "copy-crosscode-emoji-url.ini")
common_script_utils.DOTFILES_CONFIG_DIR / "copy-crosscode-emoji-url.ini" default_registry_dump_file: Path = (common_script_utils.DOTFILES_CACHE_DIR / "dotfiles")
)
default_registry_dump_file: Path = (
common_script_utils.DOTFILES_CACHE_DIR / "dotfiles"
)
else: else:
common_script_utils.platform_not_supported_error() common_script_utils.platform_not_supported_error()
config = ConfigParser(interpolation=None) config = ConfigParser(interpolation=None)
config.read(config_path) config.read(config_path)
emotes: list[dict[str, Any]] = [] emotes: list[dict[str, Any]] = []
def emote_downloader_and_iterator() -> Generator[str, None, None]: def emote_downloader_and_iterator() -> Generator[str, None, None]:
global emotes global emotes
registry_dump_file: Optional[Union[str, Path]] = config.get( registry_dump_file: Optional[Union[
"default", "ccbot_emote_registry_dump_file", fallback=None str, Path]] = config.get("default", "ccbot_emote_registry_dump_file", fallback=None)
) if registry_dump_file is not None:
if registry_dump_file is not None: registry_dump_file = os.path.expanduser(registry_dump_file)
registry_dump_file = os.path.expanduser(registry_dump_file) else:
else: registry_dump_file = default_registry_dump_file
registry_dump_file = default_registry_dump_file
registry_dump_url = config.get( registry_dump_url = config.get(
"default", "ccbot_emote_registry_dump_url", fallback=DEFAULT_REGISTRY_DUMP_URL "default", "ccbot_emote_registry_dump_url", fallback=DEFAULT_REGISTRY_DUMP_URL
) )
emote_registry_data: dict[str, Any] emote_registry_data: dict[str, Any]
try: try:
with open(registry_dump_file, "r") as f: with open(registry_dump_file, "r") as f:
emote_registry_data = json.load(f) emote_registry_data = json.load(f)
except FileNotFoundError: except FileNotFoundError:
with urllib.request.urlopen(registry_dump_url, timeout=10) as response: with urllib.request.urlopen(registry_dump_url, timeout=10) as response:
emote_registry_data = json.load(response) emote_registry_data = json.load(response)
assert emote_registry_data["version"] == 1 assert emote_registry_data["version"] == 1
emotes = [emote for emote in emote_registry_data["list"] emotes = [emote for emote in emote_registry_data["list"]]
# if emote["safe"]
]
for emote in emotes: for emote in emotes:
yield "{emote[ref]} [{emote[guild_name]}]".format(emote=emote) yield "{emote[ref]} [{emote[guild_name]}]".format(emote=emote)
chosen_index = common_script_utils.run_chooser( chosen_index = common_script_utils.run_chooser(
emote_downloader_and_iterator(), prompt="emote", async_read=True emote_downloader_and_iterator(), prompt="emote", async_read=True
) )
if chosen_index >= 0: if chosen_index >= 0:
chosen_emote = emotes[chosen_index] chosen_emote = emotes[chosen_index]
emote_url: urllib.parse.ParseResult = urllib.parse.urlparse(chosen_emote["url"]) emote_url: urllib.parse.ParseResult = urllib.parse.urlparse(chosen_emote["url"])
emote_url_query: dict[str, list[str]] = urllib.parse.parse_qs(emote_url.query) emote_url_query: dict[str, list[str]] = urllib.parse.parse_qs(emote_url.query)
if config.getboolean("default", "add_emote_name_to_url", fallback=False): if config.getboolean("default", "add_emote_name_to_url", fallback=False):
emote_url_query["name"] = [chosen_emote["name"]] emote_url_query["name"] = [chosen_emote["name"]]
default_emote_image_size = config.getint( default_emote_image_size = config.getint("default", "default_emote_image_size", fallback=None)
"default", "default_emote_image_size", fallback=None if default_emote_image_size is not None:
) emote_url_query["size"] = [str(default_emote_image_size)]
if default_emote_image_size is not None:
emote_url_query["size"] = [str(default_emote_image_size)]
emote_url_query_str = urllib.parse.urlencode(emote_url_query, doseq=True) emote_url_query_str = urllib.parse.urlencode(emote_url_query, doseq=True)
emote_url_str = urllib.parse.urlunparse( emote_url_str = urllib.parse.urlunparse(emote_url._replace(query=emote_url_query_str))
emote_url._replace(query=emote_url_query_str)
)
common_script_utils.set_clipboard(emote_url_str) common_script_utils.set_clipboard(emote_url_str)
common_script_utils.send_notification( common_script_utils.send_notification(
os.path.basename(__file__), os.path.basename(__file__),
"copied URL of {} to clipboard!".format(chosen_emote["ref"]), "copied URL of {} to clipboard!".format(chosen_emote["ref"]),
) )

View File

@ -6,17 +6,16 @@ import sys
import colorama import colorama
import time import time
DISCORD_EPOCH = 1420070400000 # milliseconds DISCORD_EPOCH = 1420070400000 # milliseconds
user_snowflake = int(sys.argv[1]) user_snowflake = int(sys.argv[1])
def print_field(name, value): def print_field(name, value):
print( print(
"{}{}:{} {}".format( "{}{}:{} {}".format(colorama.Style.BRIGHT, name.rjust(21), colorama.Style.RESET_ALL, value)
colorama.Style.BRIGHT, name.rjust(21), colorama.Style.RESET_ALL, value )
)
)
creation_time = (user_snowflake >> 22) + DISCORD_EPOCH creation_time = (user_snowflake >> 22) + DISCORD_EPOCH
@ -25,11 +24,11 @@ internal_process_id = (user_snowflake >> 12) & 0x1F
increment = user_snowflake & 0xFFF increment = user_snowflake & 0xFFF
print_field( print_field(
"Created at", "Created at",
"{}.{}".format( "{}.{}".format(
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(creation_time // 1000)), time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(creation_time // 1000)),
creation_time % 1000, creation_time % 1000,
), ),
) )
print_field("Internal worker ID", internal_worker_id) print_field("Internal worker ID", internal_worker_id)
print_field("Internal process ID", internal_process_id) print_field("Internal process ID", internal_process_id)

View File

@ -4,35 +4,33 @@ import discord
import sys import sys
import os import os
guild_id = int(sys.argv[1]) guild_id = int(sys.argv[1])
voice_channel_id = int(sys.argv[2]) voice_channel_id = int(sys.argv[2])
pulseaudio_device = sys.argv[3] pulseaudio_device = sys.argv[3]
with open(os.path.expanduser("~/.config/dotfiles/discord-tools-user-token.txt")) as f: with open(os.path.expanduser("~/.config/dotfiles/discord-tools-user-token.txt")) as f:
bot_token = f.read().strip() bot_token = f.read().strip()
bot = discord.Client() bot = discord.Client()
@bot.event @bot.event
async def on_ready(): async def on_ready():
print("logged in as {0} ({0.id})".format(bot.user)) print("logged in as {0} ({0.id})".format(bot.user))
guild: discord.Guild = bot.get_guild(guild_id) guild: discord.Guild = bot.get_guild(guild_id)
if guild is None: if guild is None:
raise Exception("guild not found") raise Exception("guild not found")
voice_channel: discord.VoiceChannel = guild.get_channel(voice_channel_id) voice_channel: discord.VoiceChannel = guild.get_channel(voice_channel_id)
if voice_channel is None: if voice_channel is None:
raise Exception("channel not found") raise Exception("channel not found")
voice_client = await voice_channel.connect() voice_client = await voice_channel.connect()
print("connected to {0} ({0.id}) in {1} ({1.id})".format(voice_channel, guild)) print("connected to {0} ({0.id}) in {1} ({1.id})".format(voice_channel, guild))
source = discord.FFmpegPCMAudio(pulseaudio_device, before_options="-f pulse") source = discord.FFmpegPCMAudio(pulseaudio_device, before_options="-f pulse")
voice_client.play( voice_client.play(source, after=lambda e: print("Player error: %s" % e) if e else None)
source, after=lambda e: print("Player error: %s" % e) if e else None
)
bot.run(bot_token, bot=False) bot.run(bot_token, bot=False)

View File

@ -18,22 +18,21 @@ import typing
DISCORD_EPOCH = 1420070400000 # milliseconds DISCORD_EPOCH = 1420070400000 # milliseconds
# https://discord.com/developers/docs/resources/user#user-object-user-flags # https://discord.com/developers/docs/resources/user#user-object-user-flags
DISCORD_FLAGS = { DISCORD_FLAGS = {
"Discord Employee": 1 << 0, "Discord Employee": 1 << 0,
"Discord Partner": 1 << 1, "Discord Partner": 1 << 1,
"HypeSquad Events": 1 << 2, "HypeSquad Events": 1 << 2,
"Bug Hunter Level 1": 1 << 3, "Bug Hunter Level 1": 1 << 3,
"House of Bravery": 1 << 6, "House of Bravery": 1 << 6,
"House of Brilliance": 1 << 7, "House of Brilliance": 1 << 7,
"House of Balance": 1 << 8, "House of Balance": 1 << 8,
"Early Supporter": 1 << 9, "Early Supporter": 1 << 9,
"Team User": 1 << 10, "Team User": 1 << 10,
"System": 1 << 12, "System": 1 << 12,
"Bug Hunter Level 2": 1 << 14, "Bug Hunter Level 2": 1 << 14,
"Verified Bot": 1 << 16, "Verified Bot": 1 << 16,
"Verified Bot Developer": 1 << 17, "Verified Bot Developer": 1 << 17,
} }
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("user_snowflake", type=int) parser.add_argument("user_snowflake", type=int)
parser.add_argument("--bot-token", type=str) parser.add_argument("--bot-token", type=str)
@ -45,32 +44,29 @@ user_snowflake = cli_args.user_snowflake
bot_token = cli_args.bot_token bot_token = cli_args.bot_token
if bot_token is None: if bot_token is None:
with open( with open(os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")) as f:
os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt") bot_token = f.read().strip()
) as f:
bot_token = f.read().strip()
image_size = cli_args.image_size image_size = cli_args.image_size
if not (image_size is None or (image_size > 0 and image_size & (image_size - 1)) == 0): if not (image_size is None or (image_size > 0 and image_size & (image_size - 1)) == 0):
parser.error("image_size must be greater than zero and a power of two") parser.error("image_size must be greater than zero and a power of two")
try: try:
opener = urllib.request.build_opener() opener = urllib.request.build_opener()
# Don't send the User-Agent header, Discord blocks the default one # Don't send the User-Agent header, Discord blocks the default one
opener.addheaders = [] opener.addheaders = []
with opener.open( with opener.open(
urllib.request.Request( urllib.request.Request(
"http://discord.com/api/users/{}".format(user_snowflake), "http://discord.com/api/users/{}".format(user_snowflake),
headers={"Authorization": "Bot {}".format(bot_token)}, headers={"Authorization": "Bot {}".format(bot_token)},
), ),
timeout=10, timeout=10,
) as response: ) as response:
raw_data = json.load(response) raw_data = json.load(response)
except urllib.error.HTTPError as err: except urllib.error.HTTPError as err:
print(err, file=sys.stderr) print(err, file=sys.stderr)
print(err.read(), file=sys.stderr) print(err.read(), file=sys.stderr)
raise err raise err
data = {} data = {}
@ -78,19 +74,17 @@ data["ID"] = raw_data["id"]
data["Name"] = "{}#{}".format(raw_data["username"], raw_data["discriminator"]) data["Name"] = "{}#{}".format(raw_data["username"], raw_data["discriminator"])
default_avatar_url = "https://cdn.discordapp.com/embed/avatars/{}.png".format( default_avatar_url = "https://cdn.discordapp.com/embed/avatars/{}.png".format(
int(raw_data["discriminator"], 10) % 5 int(raw_data["discriminator"], 10) % 5
) )
avatar_url = ( avatar_url = (
"https://cdn.discordapp.com/avatars/{}/{}.{}".format( "https://cdn.discordapp.com/avatars/{}/{}.{}".format(
raw_data["id"], raw_data["id"],
raw_data["avatar"], raw_data["avatar"],
"gif" if raw_data["avatar"].startswith("a_") else "png", "gif" if raw_data["avatar"].startswith("a_") else "png",
) ) if raw_data["avatar"] is not None else default_avatar_url
if raw_data["avatar"] is not None
else default_avatar_url
) )
if image_size is not None: if image_size is not None:
avatar_url += "?size={}".format(image_size) avatar_url += "?size={}".format(image_size)
data["Avatar"] = avatar_url data["Avatar"] = avatar_url
data["Default avatar"] = default_avatar_url data["Default avatar"] = default_avatar_url
@ -101,38 +95,37 @@ data["System user"] = raw_data.get("system", False)
# https://discord.com/developers/docs/reference#convert-snowflake-to-datetime # https://discord.com/developers/docs/reference#convert-snowflake-to-datetime
snowflake_creation_time = (user_snowflake >> 22) + DISCORD_EPOCH snowflake_creation_time = (user_snowflake >> 22) + DISCORD_EPOCH
data["Created at"] = "{}.{} UTC".format( data["Created at"] = "{}.{} UTC".format(
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(snowflake_creation_time // 1000)), time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(snowflake_creation_time // 1000)),
snowflake_creation_time % 1000, snowflake_creation_time % 1000,
) )
user_flags = raw_data["public_flags"] user_flags = raw_data["public_flags"]
if user_flags == 0: if user_flags == 0:
data["Flags"] = "<none>" data["Flags"] = "<none>"
else: else:
user_flag_names = [] user_flag_names = []
for flag_name, bitmask in DISCORD_FLAGS.items(): for flag_name, bitmask in DISCORD_FLAGS.items():
if user_flags & bitmask: if user_flags & bitmask:
user_flag_names.append(flag_name) user_flag_names.append(flag_name)
data["Flags"] = ", ".join(user_flag_names) data["Flags"] = ", ".join(user_flag_names)
if cli_args.get_prop is None: if cli_args.get_prop is None:
max_name_length = max(map(len, data.keys())) max_name_length = max(map(len, data.keys()))
for name, value in data.items(): for name, value in data.items():
if value is True: if value is True:
value = "yes" value = "yes"
elif value is False: elif value is False:
value = "no" value = "no"
print( print(
"{}{:>{}}:{} {}".format( "{}{:>{}}:{} {}".format(
colorama.Style.BRIGHT, colorama.Style.BRIGHT,
name, name,
max_name_length + 1, max_name_length + 1,
colorama.Style.RESET_ALL, colorama.Style.RESET_ALL,
value, value,
) )
) )
else: else:
print(data[cli_args.get_prop]) print(data[cli_args.get_prop])

View File

@ -10,31 +10,29 @@ from pathlib import Path
import struct import struct
import json import json
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources")) sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
import factorio.property_tree import factorio.property_tree
with open(Path.home() / ".factorio" / "mods" / "mod-settings.dat", "rb") as f: with open(Path.home() / ".factorio" / "mods" / "mod-settings.dat", "rb") as f:
version_main: int version_main: int
version_major: int version_major: int
version_minor: int version_minor: int
version_developer: int version_developer: int
version_main, version_major, version_minor, version_developer = struct.unpack( version_main, version_major, version_minor, version_developer = struct.unpack("<HHHH", f.read(8))
"<HHHH", f.read(8)
)
always_false_flag = factorio.property_tree.read_bool(f) always_false_flag = factorio.property_tree.read_bool(f)
assert not always_false_flag assert not always_false_flag
deserialized_data = { deserialized_data = {
"factorio_version": { "factorio_version": {
"main": version_main, "main": version_main,
"major": version_major, "major": version_major,
"minor": version_minor, "minor": version_minor,
"developer": version_developer, "developer": version_developer,
}, },
"data": factorio.property_tree.read(f), "data": factorio.property_tree.read(f),
} }
print(json.dumps(deserialized_data, indent=2)) print(json.dumps(deserialized_data, indent=2))

View File

@ -7,6 +7,7 @@ import json
from sys import stdout from sys import stdout
import base64 import base64
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
encoding_names = ["utf8", "base16", "base32", "base64", "base85"] encoding_names = ["utf8", "base16", "base32", "base64", "base85"]
parser.add_argument("--encoding", "-e", choices=encoding_names, default="utf8") parser.add_argument("--encoding", "-e", choices=encoding_names, default="utf8")
@ -17,33 +18,33 @@ cli_args = parser.parse_args()
def bytes_to_json(b: bytes, encoding: str) -> Union[str, list[int]]: def bytes_to_json(b: bytes, encoding: str) -> Union[str, list[int]]:
if encoding == "utf8": if encoding == "utf8":
try: try:
return b.decode("utf8") return b.decode("utf8")
except UnicodeDecodeError: except UnicodeDecodeError:
return list(b) return list(b)
elif encoding == "base16": elif encoding == "base16":
return base64.b16encode(b).decode("ascii") return base64.b16encode(b).decode("ascii")
elif encoding == "base32": elif encoding == "base32":
return base64.b32encode(b).decode("ascii") return base64.b32encode(b).decode("ascii")
elif encoding == "base64": elif encoding == "base64":
return base64.b64encode(b).decode("ascii") return base64.b64encode(b).decode("ascii")
elif encoding == "base85": elif encoding == "base85":
return base64.b85encode(b).decode("ascii") return base64.b85encode(b).decode("ascii")
else: else:
assert False assert False
key_encoding: str = cli_args.key_encoding or cli_args.encoding key_encoding: str = cli_args.key_encoding or cli_args.encoding
value_encoding: str = cli_args.value_encoding or cli_args.encoding value_encoding: str = cli_args.value_encoding or cli_args.encoding
db = plyvel.DB(str(cli_args.db_path), create_if_missing=False) db = plyvel.DB(str(cli_args.db_path), create_if_missing=False)
with db.iterator() as iterator: with db.iterator() as iterator:
for key, value in iterator: for key, value in iterator:
json.dump( json.dump(
{ {
"key": bytes_to_json(key, key_encoding), "key": bytes_to_json(key, key_encoding),
"value": bytes_to_json(value, value_encoding), "value": bytes_to_json(value, value_encoding),
}, },
stdout, stdout,
) )
stdout.write("\n") stdout.write("\n")

View File

@ -1,14 +1,16 @@
#!/usr/bin/python3 #!/usr/bin/env python3
# Taken from <https://unix.stackexchange.com/a/509417/411555> # Taken from <https://unix.stackexchange.com/a/509417/411555>
import gi import gi
import sys import sys
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib # noqa E402 from gi.repository import Gtk, Gio, GLib
rec_mgr = Gtk.RecentManager.get_default() rec_mgr = Gtk.RecentManager.get_default()
for arg in sys.argv[1:]: for arg in sys.argv[1:]:
rec_mgr.add_item(Gio.File.new_for_path(arg).get_uri()) rec_mgr.add_item(Gio.File.new_for_path(arg).get_uri())
GLib.idle_add(Gtk.main_quit) GLib.idle_add(Gtk.main_quit)
Gtk.main() Gtk.main()

View File

@ -38,7 +38,6 @@
# - The module `skins.citizen.scripts` references search inputs which aren't # - The module `skins.citizen.scripts` references search inputs which aren't
# created by this script. # created by this script.
import argparse import argparse
import mwclient import mwclient
import json import json
@ -51,85 +50,85 @@ LANG = "en"
LANG_TEXT_DIRECTION = "ltr" LANG_TEXT_DIRECTION = "ltr"
MODULES_POST_LOAD = { MODULES_POST_LOAD = {
"vector": [ "vector": [
"site", "site",
"mediawiki.page.startup", "mediawiki.page.startup",
"mediawiki.page.ready", "mediawiki.page.ready",
"mediawiki.toc", "mediawiki.toc",
# "mediawiki.searchSuggest", # "mediawiki.searchSuggest",
# "mediawiki.page.watch.ajax", # "mediawiki.page.watch.ajax",
"skins.vector.js", "skins.vector.js",
], ],
"citizen": [ "citizen": [
# "site", # "site",
# "mediawiki.page.startup", # "mediawiki.page.startup",
# "mediawiki.page.ready", # "mediawiki.page.ready",
# "mediawiki.toc", # "mediawiki.toc",
# "skins.citizen.scripts.toc", # "skins.citizen.scripts.toc",
# "skins.citizen.scripts.search", # "skins.citizen.scripts.search",
# "skins.citizen.styles.search", # "skins.citizen.styles.search",
# "skins.citizen.icons.search", # "skins.citizen.icons.search",
# "skins.citizen.scripts", # "skins.citizen.scripts",
], ],
} }
MODULES_POST_LOAD_BLOCKED = { MODULES_POST_LOAD_BLOCKED = {
"citizen": [ "citizen": [
"skins.citizen.scripts.toc", "skins.citizen.scripts.toc",
"skins.citizen.scripts.search", "skins.citizen.scripts.search",
"skins.citizen.styles.search", "skins.citizen.styles.search",
"skins.citizen.icons.search", "skins.citizen.icons.search",
], ],
} }
MODULES_PRELOAD_STYLES = { MODULES_PRELOAD_STYLES = {
"vector": [ "vector": [
"mediawiki.legacy.commonPrint", "mediawiki.legacy.commonPrint",
"mediawiki.legacy.shared", "mediawiki.legacy.shared",
"mediawiki.skinning.interface", "mediawiki.skinning.interface",
"mediawiki.toc.styles", "mediawiki.toc.styles",
"skins.vector.styles", "skins.vector.styles",
"site.styles", "site.styles",
], ],
"citizen": [ "citizen": [
# "mediawiki.legacy.commonPrint", # "mediawiki.legacy.commonPrint",
# "mediawiki.legacy.shared", # "mediawiki.legacy.shared",
"mediawiki.skinning.content.externallinks", "mediawiki.skinning.content.externallinks",
# "mediawiki.toc.styles", # "mediawiki.toc.styles",
"skins.citizen.icons", "skins.citizen.icons",
"skins.citizen.styles", "skins.citizen.styles",
"skins.citizen.icons.ca", "skins.citizen.icons.ca",
"skins.citizen.icons.es", "skins.citizen.icons.es",
"skins.citizen.icons.footer", "skins.citizen.icons.footer",
"skins.citizen.icons.n", "skins.citizen.icons.n",
"skins.citizen.icons.pt", "skins.citizen.icons.pt",
"skins.citizen.icons.t", "skins.citizen.icons.t",
"skins.citizen.styles.fonts", "skins.citizen.styles.fonts",
"skins.citizen.styles.toc", "skins.citizen.styles.toc",
"site.styles", "site.styles",
], ],
} }
MODULES_PRELOAD_SCRIPTS = { MODULES_PRELOAD_SCRIPTS = {
"vector": ["startup"], "vector": ["startup"],
"citizen": ["startup"], "citizen": ["startup"],
} }
# ported from <https://github.com/wikimedia/mediawiki/blob/c15ded31a6ca79fa65c00d151a7220632ad90b6d/includes/parser/Sanitizer.php#L1205-L1222> # ported from <https://github.com/wikimedia/mediawiki/blob/c15ded31a6ca79fa65c00d151a7220632ad90b6d/includes/parser/Sanitizer.php#L1205-L1222>
def escape_css_class(class_str): def escape_css_class(class_str):
class_str = re.sub( class_str = re.sub(
r"""(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xA0""", r"""(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xA0""",
"_", "_",
class_str, class_str,
) )
class_str = re.sub(r"_+", "_", class_str) class_str = re.sub(r"_+", "_", class_str)
class_str = class_str.rstrip("_") class_str = class_str.rstrip("_")
return class_str return class_str
def json_dumps_compact(data): def json_dumps_compact(data):
return json.dumps(data, indent=None, separators=(",", ":")) return json.dumps(data, indent=None, separators=(",", ":"))
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -137,50 +136,55 @@ parser.add_argument("--site", type=str, required=True)
parser.add_argument("--scheme", type=str, default="https") parser.add_argument("--scheme", type=str, default="https")
parser.add_argument("--skin", type=str, default="vector") parser.add_argument("--skin", type=str, default="vector")
parser.add_argument( parser.add_argument(
"--input", type=str, required=True, "--input",
type=str,
required=True,
) )
parser.add_argument("--title", type=str) parser.add_argument("--title", type=str)
parser.add_argument("--output", type=str, required=True) parser.add_argument("--output", type=str, required=True)
cli_args = parser.parse_args() cli_args = parser.parse_args()
site = mwclient.Site(cli_args.site, scheme=cli_args.scheme) site = mwclient.Site(cli_args.site, scheme=cli_args.scheme)
def get_load_script_url(**args): def get_load_script_url(**args):
return "{path}load{ext}?{args}".format( return "{path}load{ext}?{args}".format(
path=site.path, path=site.path,
ext=site.ext, ext=site.ext,
args=urlencode({"lang": LANG, "skin": cli_args.skin, **args}), args=urlencode({
) "lang": LANG,
"skin": cli_args.skin,
**args
}),
)
with open(cli_args.input, "r") as f: with open(cli_args.input, "r") as f:
wikitext_str = f.read() wikitext_str = f.read()
result = site.post( result = site.post(
"parse", "parse",
title=cli_args.title, title=cli_args.title,
text=wikitext_str, text=wikitext_str,
contentmodel="wikitext", contentmodel="wikitext",
prop="text|indicators|displaytitle|modules|jsconfigvars|categorieshtml", prop="text|indicators|displaytitle|modules|jsconfigvars|categorieshtml",
preview=True, preview=True,
pst=True, # pre-save transforms pst=True, # pre-save transforms
sectionpreview=False, sectionpreview=False,
disableeditsection=True, # disables "[edit]" links next to headers disableeditsection=True, # disables "[edit]" links next to headers
useskin=cli_args.skin, useskin=cli_args.skin,
uselang=LANG, uselang=LANG,
)["parse"] )["parse"]
def get_modules(page_modules, added_modules_dict, blocked_modules_dict={}): def get_modules(page_modules, added_modules_dict, blocked_modules_dict={}):
modules = page_modules + added_modules_dict[cli_args.skin] modules = page_modules + added_modules_dict[cli_args.skin]
for blocked_module in blocked_modules_dict.get(cli_args.skin, []): for blocked_module in blocked_modules_dict.get(cli_args.skin, []):
try: try:
modules.remove(blocked_module) modules.remove(blocked_module)
except ValueError: except ValueError:
pass pass
return modules return modules
rendered_html = """\ rendered_html = """\
@ -240,53 +244,43 @@ rendered_html = """\
</html> </html>
""".format( """.format(
lang=html.escape(LANG), lang=html.escape(LANG),
text_dir=html.escape(LANG_TEXT_DIRECTION), text_dir=html.escape(LANG_TEXT_DIRECTION),
base_url=html.escape("{}://{}".format(site.scheme, site.host)), base_url=html.escape("{}://{}".format(site.scheme, site.host)),
page_modules_state_json=json_dumps_compact( page_modules_state_json=json_dumps_compact({
{ "noscript": "ready",
"noscript": "ready", "user.options": "ready",
"user.options": "ready", "user.tokens": "loading",
"user.tokens": "loading", **{name: "ready" for name in MODULES_PRELOAD_STYLES[cli_args.skin]},
**{name: "ready" for name in MODULES_PRELOAD_STYLES[cli_args.skin]}, }),
} page_config_json=json_dumps_compact(result["jsconfigvars"]),
), page_modules_json=json_dumps_compact(
page_config_json=json_dumps_compact(result["jsconfigvars"]), get_modules(result["modules"], MODULES_POST_LOAD, MODULES_POST_LOAD_BLOCKED)
page_modules_json=json_dumps_compact( ),
get_modules(result["modules"], MODULES_POST_LOAD, MODULES_POST_LOAD_BLOCKED) style_url=html.escape(
), get_load_script_url(
style_url=html.escape( only="styles",
get_load_script_url( modules="|".join(get_modules(result["modulestyles"], MODULES_PRELOAD_STYLES)),
only="styles", )
modules="|".join( ),
get_modules(result["modulestyles"], MODULES_PRELOAD_STYLES) script_url=html.escape(
), get_load_script_url(
) only="scripts",
), modules="|".join(get_modules(result["modulescripts"], MODULES_PRELOAD_SCRIPTS)),
script_url=html.escape( raw="1",
get_load_script_url( )
only="scripts", ),
modules="|".join( skin=html.escape(cli_args.skin),
get_modules(result["modulescripts"], MODULES_PRELOAD_SCRIPTS) page_class=html.escape(escape_css_class(result["displaytitle"])),
), title=html.escape(result["displaytitle"]),
raw="1", indicators_html="\n".join([
) '<div id="mw-indicator-{}" class="mw-indicator">{}</div>'.format(
), indicator["name"], indicator["*"]
skin=html.escape(cli_args.skin), ) for indicator in result["indicators"]
page_class=html.escape(escape_css_class(result["displaytitle"])), ]),
title=html.escape(result["displaytitle"]), content_html=result["text"]["*"],
indicators_html="\n".join( categories_html=result["categorieshtml"]["*"],
[
'<div id="mw-indicator-{}" class="mw-indicator">{}</div>'.format(
indicator["name"], indicator["*"]
)
for indicator in result["indicators"]
]
),
content_html=result["text"]["*"],
categories_html=result["categorieshtml"]["*"],
) )
with open(cli_args.output, "w") as f: with open(cli_args.output, "w") as f:
f.write(rendered_html) f.write(rendered_html)

View File

@ -2,8 +2,9 @@
import gi import gi
import argparse import argparse
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Pango # noqa: E402 from gi.repository import Gtk, Gdk, Pango
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -12,7 +13,6 @@ args = parser.parse_args()
message = " ".join(args.message) message = " ".join(args.message)
window = Gtk.ApplicationWindow() window = Gtk.ApplicationWindow()
window.set_keep_above(True) window.set_keep_above(True)
window.set_decorated(False) window.set_decorated(False)
@ -25,17 +25,17 @@ window.add(scrolled_window)
def on_key_release(target, event): def on_key_release(target, event):
key = event.keyval key = event.keyval
if key in [Gdk.KEY_Escape, Gdk.KEY_q, Gdk.KEY_Q]: if key in [Gdk.KEY_Escape, Gdk.KEY_q, Gdk.KEY_Q]:
window.close() window.close()
def on_configure(target, event): def on_configure(target, event):
if target != window or event.type != Gdk.EventType.CONFIGURE: if target != window or event.type != Gdk.EventType.CONFIGURE:
return return
font_desc = Pango.FontDescription() font_desc = Pango.FontDescription()
font_desc.set_size(Pango.SCALE * event.height * 2 / 3) font_desc.set_size(Pango.SCALE * event.height * 2 / 3)
label.override_font(font_desc) label.override_font(font_desc)
window.connect("configure-event", on_configure) window.connect("configure-event", on_configure)

View File

@ -9,162 +9,162 @@
import math import math
import gi import gi
gi.require_version("Playerctl", "2.0") gi.require_version("Playerctl", "2.0")
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0") gi.require_version("Gdk", "3.0")
gi.require_version("Pango", "1.0") gi.require_version("Pango", "1.0")
from gi.repository import Playerctl, Gtk, Gdk, GLib, Pango # noqa: E402 from gi.repository import Playerctl, Gtk, Gdk, GLib, Pango
# Larger priority values will make the player with this name appear higher in # Larger priority values will make the player with this name appear higher in
# the menu. The default priority is 0. # the menu. The default priority is 0.
PLAYER_NAME_PRIORITIES = { PLAYER_NAME_PRIORITIES = {
"audacious": 2, "audacious": 2,
"mpv": 1, "mpv": 1,
"vlc": 1, "vlc": 1,
"firefox": -1, "firefox": -1,
"chrome": -2, "chrome": -2,
"chromium": -2, "chromium": -2,
} }
PLAYER_ICON_NAME_FIXES = { PLAYER_ICON_NAME_FIXES = {
"chrome": "google-chrome", "chrome": "google-chrome",
} }
PLAYER_PLAYBACK_STATUS_EMOJIS = { PLAYER_PLAYBACK_STATUS_EMOJIS = {
Playerctl.PlaybackStatus.PLAYING: "\u25B6", Playerctl.PlaybackStatus.PLAYING: "\u25B6",
Playerctl.PlaybackStatus.PAUSED: "\u23F8", Playerctl.PlaybackStatus.PAUSED: "\u23F8",
Playerctl.PlaybackStatus.STOPPED: "\u23F9", Playerctl.PlaybackStatus.STOPPED: "\u23F9",
} }
def humanize_duration(duration): def humanize_duration(duration):
minutes, seconds = divmod(math.floor(duration), 60) minutes, seconds = divmod(math.floor(duration), 60)
hours, minutes = divmod(minutes, 60) hours, minutes = divmod(minutes, 60)
text = "{:02}:{:02}".format(minutes, seconds) text = "{:02}:{:02}".format(minutes, seconds)
if hours > 0: if hours > 0:
text = "{}:{}".format(hours, text) text = "{}:{}".format(hours, text)
return text return text
def iter_metadata_entries_for_player(player): def iter_metadata_entries_for_player(player):
metadata = player.props.metadata metadata = player.props.metadata
title = metadata.lookup_value("xesam:title") title = metadata.lookup_value("xesam:title")
if title: if title:
yield title.get_string() yield title.get_string()
album = metadata.lookup_value("xesam:album") album = metadata.lookup_value("xesam:album")
if album: if album:
yield album.get_string() yield album.get_string()
if player.props.can_seek: if player.props.can_seek:
position_secs = player.props.position / 1e6 position_secs = player.props.position / 1e6
duration = metadata.lookup_value("mpris:length") duration = metadata.lookup_value("mpris:length")
if duration is not None and duration.is_of_type(GLib.VariantType.new("x")): if duration is not None and duration.is_of_type(GLib.VariantType.new("x")):
duration_secs = duration.get_int64() / 1e6 duration_secs = duration.get_int64() / 1e6
yield "Time: {} / {}".format( yield "Time: {} / {}".format(
humanize_duration(position_secs), humanize_duration(duration_secs) humanize_duration(position_secs), humanize_duration(duration_secs)
) )
def iter_actions_for_player(player): def iter_actions_for_player(player):
if not player.props.can_control: if not player.props.can_control:
yield ("This player can't be controlled!", None, False, None) yield ("This player can't be controlled!", None, False, None)
return return
playback_status = player.props.playback_status playback_status = player.props.playback_status
if playback_status == Playerctl.PlaybackStatus.PLAYING: if playback_status == Playerctl.PlaybackStatus.PLAYING:
yield (
"_Pause",
"media-playback-pause",
player.props.can_pause,
player.pause,
)
elif playback_status == Playerctl.PlaybackStatus.PAUSED:
yield (
"Resume (_P)",
"media-playback-start",
player.props.can_play,
player.play,
)
elif playback_status == Playerctl.PlaybackStatus.STOPPED:
yield (
"_Play",
"media-playback-start",
player.props.can_play,
player.play,
)
# See <https://github.com/altdesktop/playerctl/blob/c83a12a97031f64b260ea7f1be03386c3886b2d4/playerctl/playerctl-cli.c#L231-L235>
yield ( yield (
"_Stop", "_Pause",
"media-playback-stop", "media-playback-pause",
player.props.can_play and playback_status != Playerctl.PlaybackStatus.STOPPED, player.props.can_pause,
player.stop, player.pause,
)
elif playback_status == Playerctl.PlaybackStatus.PAUSED:
yield (
"Resume (_P)",
"media-playback-start",
player.props.can_play,
player.play,
)
elif playback_status == Playerctl.PlaybackStatus.STOPPED:
yield (
"_Play",
"media-playback-start",
player.props.can_play,
player.play,
) )
# See <https://github.com/altdesktop/playerctl/blob/c83a12a97031f64b260ea7f1be03386c3886b2d4/playerctl/playerctl-cli.c#L231-L235>
yield (
"_Stop",
"media-playback-stop",
player.props.can_play and playback_status != Playerctl.PlaybackStatus.STOPPED,
player.stop,
)
yield (
"_Mute" if player.props.volume != 0.0 else "Nor_mal volume",
"audio-volume-muted" if player.props.volume != 0.0 else "audio-volume-high",
True,
lambda volume: player.set_volume(volume),
0.0 if player.props.volume != 0.0 else 1.0,
)
yield (
"Volume +10%",
"audio-volume-medium",
True,
lambda: player.set_volume(min(player.props.volume + 0.1, 1.0)),
)
yield (
"Volume -10%",
"audio-volume-low",
True,
lambda: player.set_volume(max(player.props.volume - 0.1, 0.0)),
)
yield (
"_Next",
"media-skip-forward",
player.props.can_go_next,
player.next,
)
yield (
"Previous (_B)",
"media-skip-backward",
player.props.can_go_previous,
player.previous,
)
shuffle = player.props.shuffle
yield (
"Don't shuffle (_R)" if shuffle else "Shuffle (_R)",
"media-playlist-shuffle",
True,
lambda: player.set_shuffle(not shuffle),
)
loop_status = player.props.loop_status
for loop_action_name, loop_action_status in [
("Don't _loop", Playerctl.LoopStatus.NONE),
("Loop _one", Playerctl.LoopStatus.TRACK),
("Loop _all", Playerctl.LoopStatus.PLAYLIST),
]:
yield ( yield (
"_Mute" if player.props.volume != 0.0 else "Nor_mal volume", loop_action_name,
"audio-volume-muted" if player.props.volume != 0.0 else "audio-volume-high", "media-playlist-repeat",
True, loop_action_status != loop_status,
lambda volume: player.set_volume(volume), lambda loop_action_status: player.set_loop_status(loop_action_status),
0.0 if player.props.volume != 0.0 else 1.0, loop_action_status,
)
yield (
"Volume +10%",
"audio-volume-medium",
True,
lambda: player.set_volume(min(player.props.volume + 0.1, 1.0)),
)
yield (
"Volume -10%",
"audio-volume-low",
True,
lambda: player.set_volume(max(player.props.volume - 0.1, 0.0)),
) )
yield ( yield (
"_Next", "Play a_gain",
"media-skip-forward", "go-first",
player.props.can_go_next, player.props.can_seek,
player.next, lambda: player.set_position(0),
) )
yield (
"Previous (_B)",
"media-skip-backward",
player.props.can_go_previous,
player.previous,
)
shuffle = player.props.shuffle
yield (
"Don't shuffle (_R)" if shuffle else "Shuffle (_R)",
"media-playlist-shuffle",
True,
lambda: player.set_shuffle(not shuffle),
)
loop_status = player.props.loop_status
for loop_action_name, loop_action_status in [
("Don't _loop", Playerctl.LoopStatus.NONE),
("Loop _one", Playerctl.LoopStatus.TRACK),
("Loop _all", Playerctl.LoopStatus.PLAYLIST),
]:
yield (
loop_action_name,
"media-playlist-repeat",
loop_action_status != loop_status,
lambda loop_action_status: player.set_loop_status(loop_action_status),
loop_action_status,
)
yield (
"Play a_gain",
"go-first",
player.props.can_seek,
lambda: player.set_position(0),
)
root_menu = Gtk.Menu() root_menu = Gtk.Menu()
@ -172,93 +172,84 @@ root_menu = Gtk.Menu()
player_names = Playerctl.list_players() player_names = Playerctl.list_players()
if len(player_names) > 0: if len(player_names) > 0:
players = [] players = []
for player_name in player_names: for player_name in player_names:
player = Playerctl.Player.new_from_name(player_name) player = Playerctl.Player.new_from_name(player_name)
players.append( players.append({
{ "player":
"player": player, player,
"player_name": player_name, "player_name":
"sorting_key": ( player_name,
player.props.playback_status != Playerctl.PlaybackStatus.PLAYING, "sorting_key": (
-PLAYER_NAME_PRIORITIES.get(player_name.name, 0), player.props.playback_status != Playerctl.PlaybackStatus.PLAYING,
player_name.instance, -PLAYER_NAME_PRIORITIES.get(player_name.name, 0),
), player_name.instance,
} ),
) })
players = sorted( players = sorted(players, key=lambda player_and_meta: player_and_meta["sorting_key"])
players, key=lambda player_and_meta: player_and_meta["sorting_key"]
for player_and_meta in players:
player_name = player_and_meta["player_name"]
player = player_and_meta["player"]
player_menu_item = Gtk.ImageMenuItem.new_with_label(
"{} [{}]".format(
player_name.instance,
PLAYER_PLAYBACK_STATUS_EMOJIS[player.props.playback_status],
)
) )
for player_and_meta in players: player_icon_name = PLAYER_ICON_NAME_FIXES.get(player_name.name, player_name.name)
player_name = player_and_meta["player_name"] player_icon = Gtk.Image.new_from_icon_name(player_icon_name, Gtk.IconSize.MENU)
player = player_and_meta["player"] player_menu_item.set_image(player_icon)
player_menu_item = Gtk.ImageMenuItem.new_with_label( actions_menu = Gtk.Menu()
"{} [{}]".format(
player_name.instance, track_metadata = player.props.metadata
PLAYER_PLAYBACK_STATUS_EMOJIS[player.props.playback_status], any_metadata_was_added = False
) for meta_entry_text in iter_metadata_entries_for_player(player):
meta_menu_item = Gtk.MenuItem.new_with_label(meta_entry_text)
meta_menu_item.set_sensitive(False)
meta_menu_item_label = meta_menu_item.get_child()
meta_menu_item_label.set_ellipsize(Pango.EllipsizeMode.END)
meta_menu_item_label.set_max_width_chars(20)
actions_menu.append(meta_menu_item)
any_metadata_was_added = True
if any_metadata_was_added:
actions_menu.append(Gtk.SeparatorMenuItem.new())
for (
action_name,
action_icon_name,
action_enabled,
action_fn,
*action_fn_args,
) in iter_actions_for_player(player):
action_menu_item = Gtk.ImageMenuItem.new_with_mnemonic(action_name)
if action_icon_name is not None:
action_icon = Gtk.Image.new_from_icon_name(action_icon_name, Gtk.IconSize.MENU)
action_menu_item.set_image(action_icon)
action_menu_item.set_sensitive(action_enabled)
if action_fn is not None:
action_menu_item.connect(
"activate",
lambda _menu_item, action_fn, action_fn_args: action_fn(*action_fn_args),
action_fn,
action_fn_args,
) )
player_icon_name = PLAYER_ICON_NAME_FIXES.get( actions_menu.append(action_menu_item)
player_name.name, player_name.name
)
player_icon = Gtk.Image.new_from_icon_name(player_icon_name, Gtk.IconSize.MENU)
player_menu_item.set_image(player_icon)
actions_menu = Gtk.Menu() player_menu_item.set_submenu(actions_menu)
root_menu.append(player_menu_item)
track_metadata = player.props.metadata
any_metadata_was_added = False
for meta_entry_text in iter_metadata_entries_for_player(player):
meta_menu_item = Gtk.MenuItem.new_with_label(meta_entry_text)
meta_menu_item.set_sensitive(False)
meta_menu_item_label = meta_menu_item.get_child()
meta_menu_item_label.set_ellipsize(Pango.EllipsizeMode.END)
meta_menu_item_label.set_max_width_chars(20)
actions_menu.append(meta_menu_item)
any_metadata_was_added = True
if any_metadata_was_added:
actions_menu.append(Gtk.SeparatorMenuItem.new())
for (
action_name,
action_icon_name,
action_enabled,
action_fn,
*action_fn_args,
) in iter_actions_for_player(player):
action_menu_item = Gtk.ImageMenuItem.new_with_mnemonic(action_name)
if action_icon_name is not None:
action_icon = Gtk.Image.new_from_icon_name(
action_icon_name, Gtk.IconSize.MENU
)
action_menu_item.set_image(action_icon)
action_menu_item.set_sensitive(action_enabled)
if action_fn is not None:
action_menu_item.connect(
"activate",
lambda _menu_item, action_fn, action_fn_args: action_fn(
*action_fn_args
),
action_fn,
action_fn_args,
)
actions_menu.append(action_menu_item)
player_menu_item.set_submenu(actions_menu)
root_menu.append(player_menu_item)
else: else:
menu_item = Gtk.MenuItem.new_with_label("No players were detected!") menu_item = Gtk.MenuItem.new_with_label("No players were detected!")
menu_item.set_sensitive(False) menu_item.set_sensitive(False)
root_menu.append(menu_item) root_menu.append(menu_item)
root_menu.connect("selection-done", Gtk.main_quit) root_menu.connect("selection-done", Gtk.main_quit)
root_menu.connect("deactivate", Gtk.main_quit) root_menu.connect("deactivate", Gtk.main_quit)

View File

@ -16,35 +16,31 @@ import shutil
import sqlite3 import sqlite3
from typing import Optional, Tuple, Generator from typing import Optional, Tuple, Generator
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources")) sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
import common_script_utils import common_script_utils
if sys.platform == "darwin": if sys.platform == "darwin":
firefox_home: Path = Path.home() / "Library" / "Application Support" / "Firefox" firefox_home: Path = Path.home() / "Library" / "Application Support" / "Firefox"
elif os.name == "posix": elif os.name == "posix":
firefox_home: Path = Path.home() / ".mozilla" / "firefox" firefox_home: Path = Path.home() / ".mozilla" / "firefox"
else: else:
common_script_utils.platform_not_supported_error() common_script_utils.platform_not_supported_error()
profiles_config = ConfigParser(interpolation=None) profiles_config = ConfigParser(interpolation=None)
profiles_config.read(firefox_home / "profiles.ini") profiles_config.read(firefox_home / "profiles.ini")
installs_sections: list[str] = [ installs_sections: list[str] = [s for s in profiles_config.sections() if s.startswith("Install")]
s for s in profiles_config.sections() if s.startswith("Install")
]
if not installs_sections: if not installs_sections:
raise Exception("no Firefox installations detected!") raise Exception("no Firefox installations detected!")
if len(installs_sections) > 1: if len(installs_sections) > 1:
raise Exception("multiple Firefox installations are not supported!") raise Exception("multiple Firefox installations are not supported!")
profile_dir: Path = firefox_home / profiles_config.get(installs_sections[0], "Default") profile_dir: Path = firefox_home / profiles_config.get(installs_sections[0], "Default")
# should places.sqlite be used instead? # should places.sqlite be used instead?
db_path: Path = profile_dir / "weave" / "bookmarks.sqlite" db_path: Path = profile_dir / "weave" / "bookmarks.sqlite"
if not db_path.is_file(): if not db_path.is_file():
raise Exception("'{}' is not a file".format(db_path)) raise Exception("'{}' is not a file".format(db_path))
# Firefox holds a lock over the database file, so I can't connect to it even # Firefox holds a lock over the database file, so I can't connect to it even
# in the readonly mode: https://stackoverflow.com/a/7857866/12005228 # in the readonly mode: https://stackoverflow.com/a/7857866/12005228
@ -55,78 +51,74 @@ os.close(db_copy_fd)
chooser_entries: list[Tuple[str, str, Optional[str]]] = [] chooser_entries: list[Tuple[str, str, Optional[str]]] = []
try: try:
shutil.copyfile(db_path, db_copy_path) shutil.copyfile(db_path, db_copy_path)
db = sqlite3.connect(db_copy_path) db = sqlite3.connect(db_copy_path)
urls: dict[int, str] = {} urls: dict[int, str] = {}
url_id: int url_id: int
url: str url: str
for url_id, url in db.execute("SELECT id, url FROM urls"): for url_id, url in db.execute("SELECT id, url FROM urls"):
urls[url_id] = url urls[url_id] = url
folders: dict[str, Tuple[Optional[str], str]] = {} folders: dict[str, Tuple[Optional[str], str]] = {}
folder_id: str folder_id: str
parent_folder_id: str parent_folder_id: str
folder_title: str folder_title: str
for folder_id, parent_folder_id, folder_title in db.execute( for folder_id, parent_folder_id, folder_title in db.execute(
"SELECT guid, parentGuid, title FROM items WHERE kind = 3 AND validity AND NOT isDeleted" "SELECT guid, parentGuid, title FROM items WHERE kind = 3 AND validity AND NOT isDeleted"
): ):
folders[folder_id] = ( folders[folder_id] = (
parent_folder_id if parent_folder_id != folder_id else None, parent_folder_id if parent_folder_id != folder_id else None,
folder_title, folder_title,
) )
url_title: str url_title: str
url_id: int url_id: int
url_keyword: str url_keyword: str
parent_folder_id: str parent_folder_id: str
for url_title, url_id, url_keyword, parent_folder_id in db.execute( for url_title, url_id, url_keyword, parent_folder_id in db.execute(
"SELECT title, urlId, keyword, parentGuid FROM items WHERE kind = 1 AND validity AND NOT isDeleted" "SELECT title, urlId, keyword, parentGuid FROM items WHERE kind = 1 AND validity AND NOT isDeleted"
): ):
url = urls[url_id] url = urls[url_id]
folder_path = list[str]() folder_path = list[str]()
parent_folder_id_2: Optional[str] = parent_folder_id parent_folder_id_2: Optional[str] = parent_folder_id
while parent_folder_id_2 is not None: while parent_folder_id_2 is not None:
folder = folders.get(parent_folder_id_2, None) folder = folders.get(parent_folder_id_2, None)
if folder is None: if folder is None:
# broken folder structure? # broken folder structure?
folder_path.clear() folder_path.clear()
break break
parent_folder_id_2, folder_title = folder parent_folder_id_2, folder_title = folder
if folder_title is not None: if folder_title is not None:
folder_path.append(folder_title) folder_path.append(folder_title)
folder_path_str = ( folder_path_str = (("/" + "/".join(reversed(folder_path))) if len(folder_path) > 0 else None)
("/" + "/".join(reversed(folder_path))) if len(folder_path) > 0 else None
)
chooser_entries.append((url_title, url, folder_path_str)) chooser_entries.append((url_title, url, folder_path_str))
if url_keyword is not None: if url_keyword is not None:
chooser_entries.append((url_keyword, url, folder_path_str)) chooser_entries.append((url_keyword, url, folder_path_str))
finally: finally:
os.remove(db_copy_path) os.remove(db_copy_path)
def chooser_entries_iter() -> Generator[str, None, None]: def chooser_entries_iter() -> Generator[str, None, None]:
for title, url, folder_path_str in chooser_entries: for title, url, folder_path_str in chooser_entries:
entry_items = [title, url] entry_items = [title, url]
if folder_path_str is not None: if folder_path_str is not None:
entry_items.append(folder_path_str) entry_items.append(folder_path_str)
entry = " \u2014\u2014 ".join(entry_items) entry = " \u2014\u2014 ".join(entry_items)
yield entry yield entry
chosen_index = common_script_utils.run_chooser( chosen_index = common_script_utils.run_chooser(chooser_entries_iter(), prompt="bookmark")
chooser_entries_iter(), prompt="bookmark"
)
if chosen_index >= 0: if chosen_index >= 0:
_title, url, _folder_path_str = chooser_entries[chosen_index] _title, url, _folder_path_str = chooser_entries[chosen_index]
print(url) print(url)
common_script_utils.set_clipboard(url) common_script_utils.set_clipboard(url)
common_script_utils.send_notification( common_script_utils.send_notification(
os.path.basename(__file__), "bookmark URL copied to clipboard!", url os.path.basename(__file__), "bookmark URL copied to clipboard!", url
) )

View File

@ -1,10 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python3
import random import random
def randbyte() -> int: def randbyte() -> int:
return random.randrange(0, 256) return random.randrange(0, 256)
print("127.{}.{}.{}".format(randbyte(), randbyte(), randbyte())) print("127.{}.{}.{}".format(randbyte(), randbyte(), randbyte()))

359
zsh/completions/_keybase Normal file
View File

@ -0,0 +1,359 @@
#compdef keybase
#autoload
# keybase completion, based on cli help text
# https://github.com/fnoris/keybase-zsh-completion
# The MIT License (MIT)
#
# Copyright (c) 2014 David Tiersch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
local curcontext="$curcontext" state line ret=1
local -a _command_args
local -a _global_args
local -a _subcommands
local IFS=$'\n'
_global_args=($(keybase advanced | grep '^\s*--' | sed -r 's/^[\t ]*(--[^\t ,]+)( [^\t ,]*)?,? ?(--?[^\t ]+)?[\t ]*(.*)$/(\3)\1[\4]/'))
_arguments -C -A "-v" -A "--version"\
'(- 1 *)'{-v,--version}'[display version information]' \
'1: :->cmds' \
$_global_args \
'*:: :->args' && ret=0
case $state in
cmds)
_subcommands=(
"base62:base62 operations"
"cert:print the CA cert for api.keybase.io"
"chat:chat securely with keybase users"
"ctl:control the background keybase service"
"currency:manage cyrptocurrency address"
"decrypt:decrypt messages or files for keybase users"
"deprovision:revoke the current device, log out, and delete local state"
"device:manage your devices"
"dump-keyfamily:print out a user's current key family"
"encrypt:encrypt messages or files for keybase users"
"follow:verify a user's authenticity and optionally follow them"
"track:verify a user's authenticity and optionally follow them"
"fs:perform filesystem operations"
"id:identify a user and check their signature chain"
"list-followers:list thost who follow you"
"list-following:list who you or the given user is following"
"log:manage keybase log"
"login:establish a session with the keybase server"
"logout:logout and remove session information"
"paperkey:generate paper keys for recovering your account"
"passphrase:change or recover your keybase passphrase"
"pgp:manage keybase PGP keys"
"ping:ping the keybase API server"
"prove:generate a new proof"
"rekey:rekey status and actions"
"search:search for keybase users"
"sign:sign a document"
"signup:signup for a new account"
"sigs:manage signatures"
"status:show information about current user"
"unfollow:unfollow a user"
"untrack:unfollow a user"
"update:the updater"
"verify:verify message or file signatures for keybase users"
"version:print out version and build information"
"help, h:shows a list fo commands or help for one command"
)
_describe -t subcommands 'Keybase subcommands' _subcommands && ret=0
;;
args)
case $line[1] in
help)
_values 'commands' \
'base62' \
'cert' \
'chat' \
'ctl' \
'currency' \
'decrypt' \
'deprovision' \
'device' \
'dump-keyfamily' \
'encrypt' \
'follow' \
'track' \
'fs' \
'id' \
'list-followers' \
'list-following' \
'log' \
'login' \
'logout' \
'paperkey' \
'pasphrase' \
'pgp' \
'ping' \
'prove' \
'rekey' \
'search' \
'sign' \
'signup' \
'sigs' \
'status' \
'unfollow' \
'untrack' \
'update' \
'verify' \
'version' \
'help, h' && ret=0
;;
base62)
_values "command" \
'decode[base62 decode]' \
'encode[base62 encode]' \
'help[Shows a list of commands or help for one command]'
;;
chat)
_values "command" \
'api[JSON api]' \
'download[Download an attachment from a conversation]' \
'hide[Hide or block a conversation]' \
'list[List conversations, sorted by activity]' \
'ls[List conversations, sorted by activity]' \
"list-unread[List conversations, with unread messages at the top]" \
"lsur[List conversations, with unread messages at the top]" \
'mute[Mute or unmute a conversation]' \
'read[Show new messages in a conversation and mark them as read]' \
'report[Report a conversation (also blocks it)]' \
'send[Send a message to a conversation]' \
'upload[Upload an attachment to a conversation]' \
'help[Shows a list of commands or help for one command]'
;;
ctl)
_values "command" \
'start[Start the backgroung keybase service]' \
'stop[Stop the backgroung keybase service]' \
'reload[Reload config file]' \
'restart[Restart the background keybase service]' \
"log-rotate[Close and open the keybase service's log file]" \
'watchdog[Start, watch and prop up the backgound service]' \
'watchdog2[Start and monitor background services]' \
'app-exit[Exit the Keybase app]' \
'help[Shows a list of commands or help for one command]'
;;
currency)
_values "command" \
'add[Sign a cryptocurrency (bitcoin or zcash) address into your identity]' \
'help[Shows a list of commands or help for one command]'
;;
decrypt)
_command_args=(
'(--infile)--infile[Specify an input file]' \
'(--message)--message[Provide the message on the command line]' \
'(--outfile)--outfile[Specify an outfile (stdout by default)]' \
'(--interactive)--interactive[Interactive prompt for decryption after sender verification]' \
'(--force)--force[Force unprompted decryption, even on an indentify failure]' \
'(--paperkey)--paperkey[Use a paper key for decryption]' \
'(--encryptor-outfile)--encryptor-outfile[Write the Keybase name of the encryptor to this file]'
)
;;
device)
_values "command" \
'remove[Remove a device]' \
'list[List devices]' \
'add[Authorize a new device]' \
'help[Shows a list of commands or help for one command]'
;;
encrypt)
_command_args=(
'(--binary)--binary[Output in binary (rather than ASCII/armored)]' \
'(--infile)--infile[Specify an input file]' \
'(message)--message[Provide the message on the command line]' \
'(--outfile)--outfile[Specify an outfile (stdout by default)]' \
"(--hide-recipients)--hide-recipients[Don't include recipients in metadata]" \
"(--anonymous)--anonymous[Don't include sender or recipients in metadata. Implies --hide-recipients]" \
"(--no-self)--no-self[Don't encrypt for yourself]"
)
;;
follow)
_command_args=(
"(--local)--local[Only follow locally, don't send a public statement to the server]" \
'(-y)-y[Approve remote following without prompting]' \
'(--skip-proof-cache)--skip-proof-cache[Skip cached proofs, force re-check]'
)
;;
track)
_command_args=(
"(--local)--local[Only follow locally, don't send a public statement to the server]" \
'(-y)-y[Approve remote following without prompting]' \
'(--skip-proof-cache)--skip-proof-cache[Skip cached proofs, force re-check]'
)
;;
fs)
_values "command" \
'ls[list directory contents]' \
'cp[copy one or more directory elements to dest]' \
'mv[move one or more directory elements to dest]' \
'read[output file contents to standard output]' \
'rm[remove one or more directory elements]' \
'mkdir[create directory]' \
'stat[stat directory element]' \
'get-status[get status of pending operation]' \
'kill[kill operation]' \
'ps[list running operations]' \
'write[write input to file]' \
'help[Shows a list of commands or help for one command]'
;;
id)
_command_args=(
'(--skip-proof-cache)--skip-proof-cache[Skip cached proofs, force re-check]'
)
;;
list-followers)
_command_args=(
'(--verbose)--verbose[A full dump, with more gory details]'
)
;;
list-following)
_command_args=(
'(--filter)--filter[Provide a regex filter]' \
'(--headers)--headers[Show column headers]' \
'(--json)--json[Output as JSON (default is text)]' \
'(--verbose)--verbose[A full dump, with more gory details]'
)
;;
log)
_values "command" \
'send[Send recent debug logs to keybase]' \
'help[Shows a list of commands or help for one command]'
;;
login)
_command_args=(
'(--provision-by-email)--provision-by-email[Use an email address associated with a keybase account to provision a device]'
)
;;
passphrase)
_values "command" \
'change[Change your keybase account passphrase]' \
'recover[Recover your keybase account passphrase]' \
'help[Shows a list of commands or help for one command]'
;;
pgp)
_values "command" \
'gen[Generate a new PGP key and write to local secret keychain]' \
'pull[Download the latest PGP keys for people you track]' \
'update[Update your public PGP keys on keybase with those exported from the local GPG keyring]' \
'select[Select a key as your own and register the public half with the server]' \
'sign[PGP sign a document]' \
'encrypt[PGP encrypt messages or files for keybase users]' \
'decrypt[PGP decrypt messages or files for keybase users]' \
'verify[PGP verify message or file signatures for keybase users]' \
'export[Export a PGP key from keybase]' \
'import[Import a PGP key into keybase]' \
'drop[Drop Keybases use of a PGP key]' \
'list[List the active PGP keys in your account]' \
'purge[Purge all PGP keys from Keybase keyring]' \
'help[Shows a list of commands or help for one command]'
;;
ping)
_command_args=(
'(--gregor)--gregor[Ping the Gregor server]'
)
;;
prove)
_command_args=(
'(--output)--output[Output proof text to file (rather than standard out)]' \
"(--force)--force[Don't prompt]" \
)
_values "prove command" \
'service[Supported services are: coinbase, hackernews, reddit, dns, github, twitter, web, http, https]' \
'service username[Username or hostname at that service]'
;;
rekey)
_values "command" \
'status[Get pending rekey status]' \
'paper[Submit a paper key to help rekeying]' \
'help[Shows a list of commands or help for one command]'
;;
search)
_command_args=(
'(--json)--json[Output as JSON]'
)
;;
sign)
_command_args=(
'(--binary)--binary[Output binary message (default is armored)]' \
'(--detached)--detached[Detached signature (default is attached)]' \
'(--infile)--infile[Specify an input file]' \
'(--message)--message[Provide the message to sign on the command line]' \
'(--outfile)--outfile[Specify an outfile (default is STDOUT)]'
)
;;
signup)
_command_args=(
'(--invite-code)--invite-code[Specify an invite code]' \
'(--email)--email[Specify an account email]' \
'(--username)--username[Specify a username]'
)
;;
sigs)
_values "command" \
'list[List signatures]' \
'revoke[Revoke a signature by sig ID]' \
'help[Shows a list of commands or help for one command]'
;;
status)
_command_args=(
'(--json)--json[Output status as JSON]' \
)
;;
update)
_values "command" \
'check[Trigger an update check]' \
'run[Run the update with custom options]' \
'check-in-use[Check if we are in use (safe for restart)]' \
'notify[Notify the service about an update event]'
;;
verify)
_command_args=(
'(--detached)--detached[Specify a detached signature file]' \
'(--infile)--infile[Specify an input file]' \
'(--message)--message[Provide the message to verify on the command line]' \
"(--no-output)--no-output[Don't output the verified message]" \
'(--outfile)--outfile[Specify an outfile (default is STDOUT)]'
'(--signed-by)--signed-by[Assert signed by the given user (can use user assertion fomat)]' \
)
;;
version)
_command_args=(
"(--format)--format[Alternate format for version output. Specify 's' for simple (1.2.3) or 'v' for verbose. Default (blank) includes build number (1.2.3-400)]" \
"(--no-service)--no-service[Don't report on the service's build information]"
)
;;
esac
;;
esac
_arguments \
$_command_args \
&& ret=0
return ret
s