summaryrefslogtreecommitdiffstats
path: root/lua/base.lua
blob: 200aea6bceb5a8693d19c3c701caec36f58e7642 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
-- encoding: UTF-8

_CHINESE_DIGITS = {
  [0] = "〇",
  [1] = "一",
  [2] = "二",
  [3] = "三",
  [4] = "四",
  [5] = "五",
  [6] = "六",
  [7] = "七",
  [8] = "八",
  [9] = "九",
  [10] = "十",
}
_DATE_PATTERN = "^(%d+)-(%d+)-(%d+)$"
_TIME_PATTERN = "^(%d+):(%d+)$"

function get_chinese_math_num(num)
  local ret
  if num < 10 then
    ret = _CHINESE_DIGITS[num]
  elseif num < 20 then
    ret = _CHINESE_DIGITS[10]
    if num > 10 then
      ret = ret .. _CHINESE_DIGITS[num % 10]
    end
  elseif num < 100 then
    local mod = num % 10
    ret = _CHINESE_DIGITS[(num - mod) / 10] .. _CHINESE_DIGITS[10]
    if mod > 0 then
      ret = ret .. _CHINESE_DIGITS[mod]
    end
  else
    error("Invalid number")
  end
  return ret
end

function get_chinese_non_math_num(num)
  local ret = ""
  for ch in tostring(num):gmatch(".") do
    if ch >= "0" and ch <= "9" then
      ch = _CHINESE_DIGITS[tonumber(ch)]
    end
    ret = ret .. ch
  end
  return ret
end

function _verify_time(hour, minute)
  if hour < 0 or hour > 23 or minute < 0 or minute > 59 then
    error("Invalid time")
  end
end

function _verify_date(month, day)
  if month < 1 or month > 12 or day < 1 or day > _MONTH_TABLE_LEAF[month] then
    error("Invalid date")
  end
end

function _verify_date_with_year(year, month, day)
  _verify_date(month, day)
  if year < 1 or year > 9999 then
    error("Invalid year")
  end
  if month == 2 and day == 29 then
    if year % 400 ~= 0 and year % 100 == 0 then
      error("Invalid lunar day")
    end
    if year % 4 ~= 0 then
      error("Invalid lunar day")
    end
  end
end

function get_chinese_date(y, m, d, full)
  if full then
    return get_chinese_non_math_num(y) .. "年" ..
           get_chinese_math_num(m) .. "月" ..
           get_chinese_math_num(d) .. "日"
  else
    return y .. "年" .. m .. "月" .. d .. "日"
  end
end

function get_chinese_time(h, m, full)
  if full then
    local ret = get_chinese_math_num(h) .. "时"
    if m > 0 then
      ret = ret .. get_chinese_math_num(m) .. "分"
    end
    return ret
  else
    return h .. "时" .. m .. "分"
  end
end

function normalize_date(y, m, d)
  return string.format("%d-%02d-%02d", y, m, d)
end

function normalize_time(h, m)
  return string.format("%02d:%02d", h, m)
end

function get_time(input)
  local now = input
  if #input == 0 then
    now = os.date("%H:%M")
  end
  local hour, minute
  now:gsub(_TIME_PATTERN, function(h, m)
    hour = tonumber(h)
    minute = tonumber(m)
  end)
  _verify_time(hour, minute)
  return {
    normalize_time(hour, minute),
    get_chinese_time(hour, minute, false),
    get_chinese_time(hour, minute, true),
  }
end

function get_date(input)
  local now = input
  if #input == 0 then
    now = os.date("%Y-%m-%d")
  end
  local year, month, day
  now:gsub(_DATE_PATTERN, function(y, m, d)
    year = tonumber(y)
    month = tonumber(m)
    day = tonumber(d)
  end)
  _verify_date_with_year(year, month, day)
  return {
    normalize_date(year, month, day),
    get_chinese_date(year, month, day, false),
    get_chinese_date(year, month, day, true),
  }
end

----------------------------------

_MATH_KEYWORDS = {
  "abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "deg", "exp",
  "floor", "fmod", "frexp", "ldexp", "log", "log10", "max", "min", "modf", "pi",
  "pow", "rad", "random", "randomseed", "sin", "sinh", "sqrt", "tan", "tanh",
}

function _add_math_keyword(input)
  local ret = input
  for _, keyword in pairs(_MATH_KEYWORDS) do
    ret = ret:gsub(string.format("([^%%a\\.])(%s((.-)))", keyword), "%1math.%2")
    ret = ret:gsub(string.format("^(%s((.-)))", keyword), "math.%1")
  end
  return ret
end

function compute(input)
  local expr = "return " .. _add_math_keyword(input)
  local func = loadstring(expr)
  if func == nil then
    return "-- 未完整表达式 --"
  end
  local ret = func()
  if ret == math.huge then -- div/0
    return "-- 计算错误 --"
  end
  if ret ~= ret then
    -- We rely on the property that NaN is the only value not equal to itself.
    return "-- 计算错误 --"
  end
  return ret
end


--------------------------
_ZODIAC_TABLE = {
  [{3, 21, 4, 19}] = "白羊座(Aries) ♈",
  [{4, 20, 5, 20}] = "金牛座(Taurus) ♉",
  [{5, 21, 6, 21}] = "双子座(Gemini) ♊",
  [{6, 22, 7, 22}] = "巨蟹座(Cancer) ♋",
  [{7, 23, 8, 22}] = "狮子座(Leo) ♌",
  [{8, 23, 9, 23}] = "处女座(Virgo) ♍",
  [{9, 24, 10, 23}] = "天秤座(Libra) ♎",
  [{10, 24, 11, 21}] = "天蝎座(Scorpio) ♏",
  [{11, 22, 12, 21}] = "射手座(Sagittarius) ♐",
  [{12, 22, 12, 31}] = "摩羯座(Capricorn) ♑",
  [{1, 1, 1, 19}] = "摩羯座(Capricorn) ♑",
  [{1, 20, 2, 18}] = "水瓶座(Aquarius) ♒",
  [{2, 19, 3, 20}] = "双鱼座(Pisces) ♓",
}

_MONTH_TABLE_NORMAL = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
_MONTH_TABLE_LEAF = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

function _compute_month_and_day(month1, day1, month2, day2)
  if month1 < month2 then
    return -1
  elseif month1 > month2 then
    return 1
  elseif day1 < day2 then
    return -1
  elseif day1 > day2 then
    return 1
  else
    return 0
  end
end

-- birthday is a string in MM-DD format.
function query_zodiac(birthday)
  local month = 0
  local day = 0
  birthday:gsub("([0-9]+)-([0-9]+)$",
                function(m, d)
                  month = tonumber(m)
                  day = tonumber(d)
                end
               )
  _verify_date(month, day)
  for range, name in pairs(_ZODIAC_TABLE) do
    local from_month = range[1]
    local from_day = range[2]
    local to_month = range[3]
    local to_day = range[4]
    if _compute_month_and_day(month, day, from_month, from_day) >=0 and
       _compute_month_and_day(month, day, to_month, to_day) <=0 then
      return name
    end
  end
  error("Should never reach here")
end


------------
ime.register_command("sj", "get_time", "输入时间", "alpha", "输入可选时间,例如12:34")
ime.register_command("rq", "get_date", "输入日期", "alpha", "输入可选日期,例如2013-01-01")
ime.register_command("js", "compute", "计算模式", "none", "输入表达式,例如log(2)")
ime.register_command("xz", "query_zodiac", "查询星座", "none", "输入您的生日,例如12-3")

print("lua script loaded.")
/span>self): """ Return the width (in characters) of output tty. If stdout is not a tty, this method will return ``None``. """ # /usr/include/asm/termios.h says that struct winsize has four # unsigned shorts, hence the HHHH if sys.stdout.isatty(): try: winsize = fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)) return struct.unpack('HHHH', winsize)[1] except IOError: return None def max_col_width(self, rows, col=None): """ Return the max width (in characters) of a specified column. For example: >>> ui = textui() >>> rows = [ ... ('a', 'package'), ... ('an', 'egg'), ... ] >>> ui.max_col_width(rows, col=0) # len('an') 2 >>> ui.max_col_width(rows, col=1) # len('package') 7 >>> ui.max_col_width(['a', 'cherry', 'py']) # len('cherry') 6 """ if type(rows) not in (list, tuple): raise TypeError( 'rows: need %r or %r; got %r' % (list, tuple, rows) ) if len(rows) == 0: return 0 if col is None: return max(len(row) for row in rows) return max(len(row[col]) for row in rows) def __get_encoding(self, stream): assert stream in (sys.stdin, sys.stdout) if stream.encoding is None: return 'UTF-8' return stream.encoding def decode(self, value): """ Decode text from stdin. """ if type(value) is str: encoding = self.__get_encoding(sys.stdin) return value.decode(encoding) elif type(value) in (list, tuple): return tuple(self.decode(v) for v in value) return value def encode(self, unicode_text): """ Encode text for output to stdout. """ assert type(unicode_text) is unicode encoding = self.__get_encoding(sys.stdout) return unicode_text.encode(encoding) def choose_number(self, n, singular, plural=None): if n == 1 or plural is None: return singular % n return plural % n def encode_binary(self, value): """ Convert a binary value to base64. We know a value is binary if it is a python str type, otherwise it is a plain string. """ if type(value) is str: return base64.b64encode(value) else: return value def print_plain(self, string): """ Print exactly like ``print`` statement would. """ print unicode(string) def print_line(self, text, width=None): """ Force printing on a single line, using ellipsis if needed. For example: >>> ui = textui() >>> ui.print_line('This line can fit!', width=18) This line can fit! >>> ui.print_line('This line wont quite fit!', width=18) This line wont ... The above example aside, you normally should not specify the ``width``. When you don't, it is automatically determined by calling `textui.get_tty_width()`. """ if width is None: width = self.get_tty_width() if width is not None and width < len(text): text = text[:width - 3] + '...' print unicode(text) def print_paragraph(self, text, width=None): """ Print a paragraph, automatically word-wrapping to tty width. For example: >>> text = ''' ... Python is a dynamic object-oriented programming language that can ... be used for many kinds of software development. ... ''' >>> ui = textui() >>> ui.print_paragraph(text, width=45) Python is a dynamic object-oriented programming language that can be used for many kinds of software development. The above example aside, you normally should not specify the ``width``. When you don't, it is automatically determined by calling `textui.get_tty_width()`. The word-wrapping is done using the Python ``textwrap`` module. See: http://docs.python.org/library/textwrap.html """ if width is None: width = self.get_tty_width() for line in textwrap.wrap(text.strip(), width): print line def print_indented(self, text, indent=1): """ Print at specified indentation level. For example: >>> ui = textui() >>> ui.print_indented('One indentation level.') One indentation level. >>> ui.print_indented('Two indentation levels.', indent=2) Two indentation levels. >>> ui.print_indented('No indentation.', indent=0) No indentation. """ print (CLI_TAB * indent + text) def print_keyval(self, rows, indent=1): """ Print (key = value) pairs, one pair per line. For example: >>> items = [ ... ('in_server', True), ... ('mode', u'production'), ... ] >>> ui = textui() >>> ui.print_keyval(items) in_server = True mode = u'production' >>> ui.print_keyval(items, indent=0) in_server = True mode = u'production' Also see `textui.print_indented`. """ for (key, value) in rows: self.print_indented('%s = %r' % (key, self.encode_binary(value)), indent) def print_attribute(self, attr, value, format='%s: %s', indent=1, one_value_per_line=True): """ Print an ldap attribute. For example: >>> attr = 'dn' >>> ui = textui() >>> ui.print_attribute(attr, u'dc=example,dc=com') dn: dc=example,dc=com >>> attr = 'objectClass' >>> ui.print_attribute(attr, [u'top', u'someClass'], one_value_per_line=False) objectClass: top, someClass >>> ui.print_attribute(attr, [u'top', u'someClass']) objectClass: top objectClass: someClass """ assert isinstance(attr, basestring) if not isinstance(value, (list, tuple)): # single-value attribute self.print_indented(format % (attr, self.encode_binary(value)), indent) else: # multi-value attribute if one_value_per_line: for v in value: self.print_indented(format % (attr, self.encode_binary(v)), indent) else: value = map(lambda v: self.encode_binary(v), value) if len(value) > 0 and type(value[0]) in (list, tuple): # This is where we print failed add/remove members for l in value: text = ': '.join(l) self.print_indented(format % (attr, self.encode_binary(text)), indent) return else: if len(value) > 0: text = ', '.join(value) else: return line_len = self.get_tty_width() if line_len and text: s_indent = '%s%s' % ( CLI_TAB * indent, ' ' * (len(attr) + 2) ) line_len -= len(s_indent) text = textwrap.wrap( text, line_len, break_long_words=False ) if len(text) == 0: text = [u''] else: text = [text] self.print_indented(format % (attr, text[0]), indent) for line in text[1:]: self.print_plain('%s%s' % (s_indent, line)) def print_entry1(self, entry, indent=1, attr_map={}, attr_order=['dn'], one_value_per_line=True): """ Print an ldap entry dict. """ assert isinstance(entry, dict) assert isinstance(attr_map, dict) assert isinstance(attr_order, (list, tuple)) def print_attr(a): if attr in attr_map: self.print_attribute( attr_map[attr], entry[attr], indent=indent, one_value_per_line=one_value_per_line ) else: self.print_attribute( attr, entry[attr], indent=indent, one_value_per_line=one_value_per_line ) for attr in attr_order: if attr in entry: print_attr(attr) del entry[attr] for attr in sorted(entry): print_attr(attr) def print_entries(self, entries, order=None, labels=None, flags=None, print_all=True, format='%s: %s', indent=1): assert isinstance(entries, (list, tuple)) first = True for entry in entries: if not first: print '' first = False self.print_entry(entry, order, labels, flags, print_all, format, indent) def print_entry(self, entry, order=None, labels=None, flags=None, print_all=True, format='%s: %s', indent=1): """ """ if isinstance(entry, (list, tuple)): entry = dict(entry) assert isinstance(entry, dict) if labels is None: labels = dict() one_value_per_line = True else: one_value_per_line = False if order is not None: for key in order: if key not in entry: continue label = labels.get(key, key) flag = flags.get(key, []) value = entry[key] if 'suppress_empty' in flag and value in [u'', '', [], None]: continue if isinstance(value, dict): if frontend.entry_count(value) == 0: continue self.print_indented(format % (label, ''), indent) self.print_entry( value, order, labels, flags, print_all, format, indent=indent+1 ) else: self.print_attribute( label, value, format, indent, one_value_per_line ) del entry[key] if print_all: for key in sorted(entry): label = labels.get(key, key) self.print_attribute( key, entry[key], format, indent, one_value_per_line ) def print_dashed(self, string, above=True, below=True, indent=0, dash='-'): """ Print a string with a dashed line above and/or below. For example: >>> ui = textui() >>> ui.print_dashed('Dashed above and below.') ----------------------- Dashed above and below. ----------------------- >>> ui.print_dashed('Only dashed below.', above=False) Only dashed below. ------------------ >>> ui.print_dashed('Only dashed above.', below=False) ------------------ Only dashed above. """ assert isinstance(dash, basestring) assert len(dash) == 1 dashes = dash * len(string) if above: self.print_indented(dashes, indent) self.print_indented(string, indent) if below: self.print_indented(dashes, indent) def print_h1(self, text): """ Print a primary header at indentation level 0. For example: >>> ui = textui() >>> ui.print_h1('A primary header') ================ A primary header ================ """ self.print_dashed(text, indent=0, dash='=') def print_h2(self, text): """ Print a secondary header at indentation level 1. For example: >>> ui = textui() >>> ui.print_h2('A secondary header') ------------------ A secondary header ------------------ """ self.print_dashed(text, indent=1, dash='-') def print_name(self, name): """ Print a command name. The typical use for this is to mark the start of output from a command. For example, a hypothetical ``show_status`` command would output something like this: >>> ui = textui() >>> ui.print_name('show_status') ------------ show-status: ------------ """ self.print_dashed('%s:' % to_cli(name)) def print_header(self, msg, output): self.print_dashed(msg % output) def print_summary(self, msg): """ Print a summary at the end of a comand's output. For example: >>> ui = textui() >>> ui.print_summary('Added user "jdoe"') ----------------- Added user "jdoe" ----------------- """ self.print_dashed(msg) def print_count(self, count, singular, plural=None): """ Print a summary count. The typical use for this is to print the number of items returned by a command, especially when this return count can vary. This preferably should be used as a summary and should be the final text a command outputs. For example: >>> ui = textui() >>> ui.print_count(1, '%d goose', '%d geese') ------- 1 goose ------- >>> ui.print_count(['Don', 'Sue'], 'Found %d user', 'Found %d users') ------------- Found 2 users ------------- If ``count`` is not an integer, it must be a list or tuple, and then ``len(count)`` is used as the count. """ if type(count) is not int: assert type(count) in (list, tuple, dict) count = len(count) self.print_dashed( self.choose_number(count, singular, plural) ) def print_error(self, text): print ' ** %s **' % unicode(text) def prompt(self, label, default=None, get_values=None, optional=False): """ Prompt user for input. """ # TODO: Add tab completion using readline if optional: prompt = u'[%s]' % label else: prompt = u'%s' % label if default is None: prompt = u'%s: ' % prompt else: prompt = u'%s [%s]: ' % (prompt, default) try: data = raw_input(self.encode(prompt)) except EOFError: return None return self.decode(data) def prompt_yesno(self, label, default=None): """ Prompt user for yes/no input. This method returns True/False according to user response. Parameter "default" should be True, False or None If Default parameter is not None, user can enter an empty input instead of Yes/No answer. Value passed to Default is returned in that case. If Default parameter is None, user is asked for Yes/No answer until a correct answer is provided. Answer is then returned. In case of an error, a None value may returned """ default_prompt = None if default is not None: if default: default_prompt = "Yes" else: default_prompt = "No" if default_prompt: prompt = u'%s Yes/No (default %s): ' % (label, default_prompt) else: prompt = u'%s Yes/No: ' % label