From bb2b780b77a80c6beb662414c19ab830d26bcd79 Mon Sep 17 00:00:00 2001 From: yotsuyanagi Date: Fri, 19 Feb 2016 12:36:53 +0900 Subject: [PATCH] first commit --- .gitignore | 56 ++++++++++++++ README.rst | 42 ++++++++++ jj_menu/__init__.py | 6 ++ jj_menu/jj_menu.py | 185 ++++++++++++++++++++++++++++++++++++++++++++ setup.py | 19 +++++ 5 files changed, 308 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100644 jj_menu/__init__.py create mode 100644 jj_menu/jj_menu.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17a06cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# General +tmp +temp +#* +.#* + +# Editor +*~ +*.orig +*.swp + +# Python +*.pyc +*.pyo + +# Windows +Thumbs.db + +# Eclipse +.project + +# Pydev +.pydevproject + +# Mac +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + + +# CocoaPod +Pods/* +Podfile.lock + +# JetBrains +.idea + +# if required +# data/* +# config/* diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e70c963 --- /dev/null +++ b/README.rst @@ -0,0 +1,42 @@ +~~~~~~~ +jj-menu +~~~~~~~ + +Simple CLI Menu + + +Install +------- +:: + + $ sudo pip install git+https://github.com/junion-org/pip_github_test.git + + +Setup +----- + +Create jjfile.py into any directory. + +:: + + menu = [ + ('list python processes', 'ps -eafw|grep python'), + ('move tmp', 'cd /tmp/'), + ('list dirs', ['ls .', 'ls ..', 'ls ../..']), + ] + +Run +--- + +:: + + $ jj + + +Key binds +--------- + +ESC: Exit +Q: Exit +k: Up +j: Down diff --git a/jj_menu/__init__.py b/jj_menu/__init__.py new file mode 100644 index 0000000..b64b8b2 --- /dev/null +++ b/jj_menu/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# coding: utf-8 + +__author__ = 'ytyng' +__version__ = '0.0.1' +__license__ = 'MIT' diff --git a/jj_menu/jj_menu.py b/jj_menu/jj_menu.py new file mode 100644 index 0000000..f749972 --- /dev/null +++ b/jj_menu/jj_menu.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + +from __future__ import unicode_literals, print_function +import os +import sys +import locale +import curses +import _curses +import six + +# 文字化け対応 +locale.setlocale(locale.LC_ALL, '') + +COLOR_ACTIVE = 1 + +result_filename = '/tmp/_jj_result' + + +class MenuFileNotFound(Exception): + pass + + +def initialize_colors(): + curses.use_default_colors() + curses.init_pair(COLOR_ACTIVE, curses.COLOR_BLACK, curses.COLOR_WHITE) + + +def find_menu_file_path(cwd): + paths = [ + os.path.join(cwd, 'jjfile'), + os.path.join(cwd, 'jjfile.py'), ] + for p in paths: + if os.path.exists(p): + return p + parent_dir = os.path.dirname(cwd) + if parent_dir == cwd: + raise MenuFileNotFound() + else: + return find_menu_file_path(parent_dir) + + +def import_menu_settings(): + menu_file_path = find_menu_file_path(os.getcwd()) + + importer = __import__ + # Get directory and fabfile name + dir_name, file_name = os.path.split(menu_file_path) + if dir_name not in sys.path: + sys.path.insert(0, dir_name) + imported = importer(os.path.splitext(file_name)[0]) + return imported + + +def get_menu(): + def _get_menus(): + """ + メニュー項目1つなら同じものに展開 + """ + menus = getattr(import_menu_settings(), 'menu') + for m in menus: + if isinstance(m, str): + yield (m, m) + elif len(m) >= 2: + if isinstance(m[1], (list, tuple)): + yield (m[0], ";".join(m[1])) + else: + yield m + + return list(_get_menus()) + + +def window_addstr(window, y, x, message, color=None): + """ + python 2, 3 multi-bytes compatible + """ + if six.PY2: + new_message = message.encode('utf-8') + else: + new_message = message + args = [y, x, new_message] + if color is not None: + args.append(color) + try: + window.addstr(*args) + except _curses.error: + pass + # print(e) + + +class Launcher(object): + def __init__(self, stdscr): + stdscr.refresh() # なにより先にまず1回リフレッシュ + self.stdscr = stdscr + self.max_y, self.max_x = stdscr.getmaxyx() + self.pos_y = 0 + self.menu = get_menu() + self.init_outfile() + + def render(self): + win = curses.newwin( + len(self.menu), self.max_x, 0, 0) + for y, item in enumerate(self.menu): + item_name_str = item[0] + if y == self.pos_y: + window_addstr( + win, y, 0, '*> {}'.format(item_name_str), + curses.color_pair(COLOR_ACTIVE)) + else: + window_addstr( + win, y, 0, ' {}'.format(item_name_str)) + win.refresh() + # win.refresh(0, 0, 0, 0, len(MENU), self.max_x) + + help_win = curses.newwin(1, self.max_x, self.max_y - 1, 0) + message = '$ {}'.format(self.menu[self.pos_y][1]) + window_addstr( + help_win, 0, 0, message[:self.max_x - 1], + curses.color_pair(COLOR_ACTIVE)) + help_win.refresh() + + def debug(self, message): + """ + 簡易デバッグ + """ + win = curses.newwin(1, 10, self.max_y - 2, self.max_x - 10) + window_addstr(win, 0, 0, message[:10]) + win.refresh() + + def serve(self): + while True: + + self.render() + + # キー入力待機 + c = self.stdscr.getch() + + if c in (14, 106, 258): # ↓ + if self.pos_y < len(self.menu) - 1: + self.pos_y += 1 + + elif c in (16, 107, 259): # ↑ + if self.pos_y > 0: + self.pos_y -= 1 + + elif c in (2, 104, 260): # ← + pass + + elif c in (6, 108, 261): # → + pass + elif c in (113, 27): # Q, Esc + raise KeyboardInterrupt() + elif c == 10: + # 決定 + with open(result_filename, 'w') as fp: + fp.write(self.menu[self.pos_y][1]) + return self.menu[self.pos_y] + else: + self.debug('{}'.format(c)) + + def init_outfile(self): + with open(result_filename, 'w') as fp: + fp.write('') + + +def launch(stdscr): + initialize_colors() + _curses.curs_set(0) + # 画面サイズ取得 + launcher = Launcher(stdscr) + + selected = launcher.serve() + return selected + + +def main(): + try: + selected = curses.wrapper(launch) + print('$ {}'.format(selected[1])) + except KeyboardInterrupt: + exit(1) + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..599d0bc --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# coding: utf-8 +from setuptools import setup, find_packages +from jj_menu import __author__, __version__, __license__ + +install_requires=['curses', 'six'] + +setup( + name='jj-menu', + version=__version__, + description='Simple CLI Menu', + license=__license__, + author=__author__, + author_email='ytyng@live.jp', + url='https://github.com/jytyng/jj-menu.git', + keywords='CLI Menu, Python', + packages=find_packages(), + install_requires=[], +)