uxuxx 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uxuxx-1.4.0/PKG-INFO +7 -0
- uxuxx-1.4.0/README.md +5 -0
- uxuxx-1.4.0/pyproject.toml +21 -0
- uxuxx-1.4.0/setup.cfg +4 -0
- uxuxx-1.4.0/src/uxuxx/__init__.py +75 -0
- uxuxx-1.4.0/src/uxuxx/cli.py +61 -0
- uxuxx-1.4.0/src/uxuxx/data.py +1 -0
- uxuxx-1.4.0/src/uxuxx/deps.py +19 -0
- uxuxx-1.4.0/src/uxuxx/ports.py +24 -0
- uxuxx-1.4.0/src/uxuxx/server.py +97 -0
- uxuxx-1.4.0/src/uxuxx.egg-info/PKG-INFO +7 -0
- uxuxx-1.4.0/src/uxuxx.egg-info/SOURCES.txt +14 -0
- uxuxx-1.4.0/src/uxuxx.egg-info/dependency_links.txt +1 -0
- uxuxx-1.4.0/src/uxuxx.egg-info/entry_points.txt +2 -0
- uxuxx-1.4.0/src/uxuxx.egg-info/top_level.txt +1 -0
- uxuxx-1.4.0/tests/test_uxuxx.py +19 -0
uxuxx-1.4.0/PKG-INFO
ADDED
uxuxx-1.4.0/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "uxuxx"
|
|
7
|
+
version = "1.4.0"
|
|
8
|
+
description = "Zadachi informatiki 8 klass"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
authors = [{name = "uxuxs82"}]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
uxuxx = "uxuxx.cli:main"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
package-dir = {"" = "src"}
|
|
18
|
+
license-files = []
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["src"]
|
uxuxx-1.4.0/setup.cfg
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from .data import TASKS
|
|
3
|
+
__version__ = "1.4.0"
|
|
4
|
+
|
|
5
|
+
def _best(q):
|
|
6
|
+
q = q.lower().strip()
|
|
7
|
+
words = q.split()
|
|
8
|
+
best = None
|
|
9
|
+
score = -1
|
|
10
|
+
for t in TASKS:
|
|
11
|
+
text = (t["desc"] + " " + t.get("notes", "") + " " + t["code"]).lower()
|
|
12
|
+
s = 10 if q in text else sum(1 for w in words if w in text)
|
|
13
|
+
if s > score:
|
|
14
|
+
score = s
|
|
15
|
+
best = t
|
|
16
|
+
return best if score > 0 else None
|
|
17
|
+
|
|
18
|
+
def _slide(sn):
|
|
19
|
+
for t in TASKS:
|
|
20
|
+
if t["slide"] == sn:
|
|
21
|
+
return t
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
def _task(sn, lvl):
|
|
25
|
+
for t in TASKS:
|
|
26
|
+
if t["slide"] == sn and t["level"].lower() == lvl.lower():
|
|
27
|
+
return t
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def _handle(*args):
|
|
31
|
+
if not args:
|
|
32
|
+
print("uxuxx(1, slovo) | uxuxx(2, slayd) | uxuxx(3, slayd, uroven)")
|
|
33
|
+
return None
|
|
34
|
+
if len(args) == 1:
|
|
35
|
+
t = _best(str(args[0]))
|
|
36
|
+
elif len(args) == 2:
|
|
37
|
+
c = str(args[0])
|
|
38
|
+
v = args[1]
|
|
39
|
+
if c == "1":
|
|
40
|
+
t = _best(str(v))
|
|
41
|
+
elif c == "2":
|
|
42
|
+
t = _slide(int(v))
|
|
43
|
+
else:
|
|
44
|
+
t = _best(str(v))
|
|
45
|
+
elif len(args) == 3:
|
|
46
|
+
if str(args[0]) == "3":
|
|
47
|
+
t = _task(int(args[1]), str(args[2]))
|
|
48
|
+
else:
|
|
49
|
+
t = _best(" ".join(str(x) for x in args[1:]))
|
|
50
|
+
else:
|
|
51
|
+
t = _best(" ".join(str(x) for x in args))
|
|
52
|
+
if not t:
|
|
53
|
+
print("ne naideno")
|
|
54
|
+
return None
|
|
55
|
+
print(t["code"])
|
|
56
|
+
return t
|
|
57
|
+
|
|
58
|
+
class _Uxuxx:
|
|
59
|
+
def __call__(self, *a, **k):
|
|
60
|
+
return _handle(*a)
|
|
61
|
+
def search(self, q):
|
|
62
|
+
return _handle(1, q)
|
|
63
|
+
def slide(self, n):
|
|
64
|
+
return _handle(2, n)
|
|
65
|
+
def task(self, n, l):
|
|
66
|
+
return _handle(3, n, l)
|
|
67
|
+
def all(self):
|
|
68
|
+
for t in TASKS:
|
|
69
|
+
print(str(t["slide"]) + "|" + t["level"] + " " + t["desc"])
|
|
70
|
+
def count(self):
|
|
71
|
+
return len(TASKS)
|
|
72
|
+
def __repr__(self):
|
|
73
|
+
return "<uxuxx>"
|
|
74
|
+
|
|
75
|
+
sys.modules[__name__] = _Uxuxx()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from .data import TASKS
|
|
3
|
+
|
|
4
|
+
def _best(q):
|
|
5
|
+
q = q.lower().strip()
|
|
6
|
+
words = q.split()
|
|
7
|
+
best = None
|
|
8
|
+
score = -1
|
|
9
|
+
for t in TASKS:
|
|
10
|
+
text = (t["desc"] + " " + t.get("notes", "") + " " + t["code"]).lower()
|
|
11
|
+
s = 10 if q in text else sum(1 for w in words if w in text)
|
|
12
|
+
if s > score:
|
|
13
|
+
score = s
|
|
14
|
+
best = t
|
|
15
|
+
return best if score > 0 else None
|
|
16
|
+
|
|
17
|
+
def main(argv=None):
|
|
18
|
+
a = argv if argv is not None else sys.argv[1:]
|
|
19
|
+
if not a or a[0] in ("help", "-h", "--help"):
|
|
20
|
+
print("uxuxx 1 slovo poisk")
|
|
21
|
+
print("uxuxx 2 141 slayd")
|
|
22
|
+
print("uxuxx 3 141 A zadacha")
|
|
23
|
+
print("uxuxx 4 vse")
|
|
24
|
+
print("uxuxx 5 skolko")
|
|
25
|
+
return 0
|
|
26
|
+
if a[0] == "4":
|
|
27
|
+
for t in TASKS:
|
|
28
|
+
print(str(t["slide"]) + "|" + t["level"] + " " + t["desc"])
|
|
29
|
+
return 0
|
|
30
|
+
if a[0] == "5":
|
|
31
|
+
print(len(TASKS))
|
|
32
|
+
return 0
|
|
33
|
+
if a[0] == "2":
|
|
34
|
+
sn = int(a[1])
|
|
35
|
+
for t in TASKS:
|
|
36
|
+
if t["slide"] == sn:
|
|
37
|
+
print(t["code"])
|
|
38
|
+
return 0
|
|
39
|
+
print("net")
|
|
40
|
+
return 1
|
|
41
|
+
if a[0] == "3":
|
|
42
|
+
sn = int(a[1])
|
|
43
|
+
lvl = a[2].lower()
|
|
44
|
+
for t in TASKS:
|
|
45
|
+
if t["slide"] == sn and t["level"].lower() == lvl:
|
|
46
|
+
print(t["code"])
|
|
47
|
+
return 0
|
|
48
|
+
print("net")
|
|
49
|
+
return 1
|
|
50
|
+
if a[0] == "1":
|
|
51
|
+
t = _best(" ".join(a[1:]))
|
|
52
|
+
else:
|
|
53
|
+
t = _best(" ".join(a))
|
|
54
|
+
if not t:
|
|
55
|
+
print("ne naideno")
|
|
56
|
+
return 1
|
|
57
|
+
print(t["code"])
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TASKS = [{'slide': 10, 'level': 'В', 'desc': 'лесенкой Вася пошел гулять', 'notes': 'вывод print', 'code': 'print("Вася")\nprint("пошел")\nprint("гулять")'}, {'slide': 10, 'level': 'С', 'desc': 'рисунок из букв ж H z', 'notes': 'фигура', 'code': 'print(" ж")\nprint(" жжж")\nprint(" жжжжж")\nprint("жжжжжжж")\nprint(" HH HH")\nprint("HHHHHHH")\nprint(" HH HH")\nprint(" zzzzz")'}, {'slide': 25, 'level': 'А', 'desc': 'сумма трех чисел', 'notes': 'ввод', 'code': 'a=int(input())\nb=int(input())\nc=int(input())\nprint(a+b+c)'}, {'slide': 25, 'level': 'В', 'desc': 'сумма и произведение трех', 'notes': 'f-строки', 'code': 'a,b,c=map(int,input().split())\nprint(f"{a}+{b}+{c}={a+b+c}")\nprint(f"{a}*{b}*{c}={a*b*c}")'}, {'slide': 26, 'level': 'С', 'desc': 'среднее трех', 'notes': 'среднее', 'code': 'a,b,c=map(int,input().split())\nprint(f"({a}+{b}+{c})/3={(a+b+c)/3}")'}, {'slide': 36, 'level': 'А', 'desc': 'секунды в минуты и секунды', 'notes': 'деление', 'code': 't=int(input())\nprint(f"{t//60} мин. {t%60} с.")'}, {'slide': 36, 'level': 'В', 'desc': 'секунды в часы минуты секунды', 'notes': 'часы', 'code': 't=int(input())\nprint(f"{t//3600} ч. {t%3600//60} мин. {t%60} с.")'}, {'slide': 37, 'level': 'С', 'desc': 'время окончания урока', 'notes': 'школа', 'code': 'n=int(input())\ntotal=8*60+30+(n-1)*55+45\nprint(f"{total//60}-{total%60:02d}")'}, {'slide': 38, 'level': 'А1', 'desc': 'квадрат числа', 'notes': 'квадрат', 'code': 'n=int(input())\nprint(n*n)'}, {'slide': 38, 'level': 'А2', 'desc': 'последняя цифра', 'notes': 'остаток', 'code': 'n=int(input())\nprint(n%10)'}, {'slide': 38, 'level': 'А3', 'desc': 'первая цифра трехзначного', 'notes': 'сотни', 'code': 'n=int(input())\nprint(n//100)'}, {'slide': 39, 'level': 'В1', 'desc': 'вторая с конца цифра', 'notes': 'десятки', 'code': 'n=int(input())\nprint(n//10%10)'}, {'slide': 39, 'level': 'В2', 'desc': 'сумма цифр двузначного', 'notes': 'разбор', 'code': 'n=int(input())\nprint(n//10+n%10)'}, {'slide': 39, 'level': 'В3', 'desc': 'минуты секунды в секунды', 'notes': 'перевод', 'code': 'm=int(input())\ns=int(input())\nprint(m*60+s)'}, {'slide': 40, 'level': 'С1', 'desc': 'сумма цифр четырехзначного', 'notes': 'while', 'code': 'n=int(input())\ns=0\nwhile n:\n s+=n%10\n n//=10\nprint(s)'}, {'slide': 40, 'level': 'С2', 'desc': 'развернуть трехзначное', 'notes': 'наоборот', 'code': 'n=int(input())\nprint(n%10*100+n//10%10*10+n//100)'}, {'slide': 40, 'level': 'С3', 'desc': 'первые две цифры в конец', 'notes': 'перестановка', 'code': 'n=int(input())\nprint(n%100*100+n//100)'}, {'slide': 41, 'level': 'А', 'desc': 'трехзначное в столбик с последней', 'notes': 'while', 'code': 'n=int(input())\nwhile n:\n print(n%10)\n n//=10'}, {'slide': 41, 'level': 'В', 'desc': 'трехзначное в столбик с первой', 'notes': 'порядок', 'code': 'n=int(input())\nprint(n//100)\nprint(n//10%10)\nprint(n%10)'}, {'slide': 50, 'level': 'А', 'desc': 'сколько фото влезет на флешку', 'notes': 'объем', 'code': 'print(int(2048/float(input())))'}, {'slide': 51, 'level': 'В', 'desc': 'оцифровка звука размер файла', 'notes': 'звук', 'code': 'm=int(input())\nprint(int(44100*24*2*60*m/8/1024/1024)+1,"Мбайт")'}, {'slide': 52, 'level': 'С', 'desc': 'разведчики первая цифра дробной', 'notes': 'дробь', 'code': 'x=float(input())\nprint(int(((x*x)%1)*10))'}, {'slide': 57, 'level': 'А', 'desc': 'лото 5 случайных номеров', 'notes': 'random', 'code': 'from random import randint\nfor _ in range(5):\n print(randint(1,90))'}, {'slide': 57, 'level': 'В', 'desc': 'лото 5 разных номеров', 'notes': 'диапазоны', 'code': 'from random import randint\nprint(randint(1,18),randint(19,36),randint(37,54),randint(55,72),randint(73,90))'}, {'slide': 58, 'level': 'С', 'desc': 'кубик три раза число квадрат', 'notes': 'кубик', 'code': 'from random import randint\na,b,c=randint(1,6),randint(1,6),randint(1,6)\nn=a*100+b*10+c\nprint(n,n*n)'}, {'slide': 59, 'level': 'D', 'desc': 'случайное трехзначное цифры', 'notes': 'цифры', 'code': 'from random import randint\nn=randint(100,999)\nprint(n)\nprint(n//100)\nprint(n//10%10)\nprint(n%10)'}, {'slide': 74, 'level': 'А', 'desc': 'максимум минимум из двух без max', 'notes': 'без max', 'code': 'a,b=map(int,input().split())\nprint("Max",a if a>b else b)\nprint("Min",a if a<b else b)'}, {'slide': 74, 'level': 'В', 'desc': 'максимум из четырех без max', 'notes': 'из 4', 'code': 'a,b,c,d=map(int,input().split())\nM=a\nif b>M:M=b\nif c>M:M=c\nif d>M:M=d\nprint(M)'}, {'slide': 75, 'level': 'С', 'desc': 'кто из трех старше', 'notes': 'возраст', 'code': 'a=int(input())\nb=int(input())\nc=int(input())\nif a>b and a>c:print("Антон")\nelif b>a and b>c:print("Борис")\nelif c>a and c>b:print("Виктор")\nelse:print("Одного возраста")'}, {'slide': 86, 'level': 'А', 'desc': 'три роста по возрастанию', 'notes': 'сортировка', 'code': 'a,b,c=map(int,input().split())\nprint("По росту." if a<b<c else "Не по росту!")'}, {'slide': 87, 'level': 'В', 'desc': 'номер месяца время года', 'notes': 'времена', 'code': 'm=int(input())\nif m in(12,1,2):print("Зима")\nelif m in(3,4,5):print("Весна")\nelif m in(6,7,8):print("Лето")\nelif m in(9,10,11):print("Осень")\nelse:print("Ошибка")'}, {'slide': 88, 'level': 'С', 'desc': 'возраст год года лет', 'notes': 'склонение', 'code': 'n=int(input())\nif n%10==1 and n%100!=11:print(n,"год")\nelif n%10 in(2,3,4) and n%100 not in(12,13,14):print(n,"года")\nelse:print(n,"лет")'}, {'slide': 94, 'level': 'А', 'desc': 'трехзначное да нет', 'notes': 'трехзначное', 'code': 'n=abs(int(input()))\nprint("да" if 100<=n<=999 else "нет")'}, {'slide': 95, 'level': 'В', 'desc': 'трехзначное палиндром', 'notes': 'палиндром', 'code': 'n=int(input())\nprint("да" if n//100==n%10 else "нет")'}, {'slide': 96, 'level': 'С', 'desc': 'все цифры одинаковы', 'notes': 'одинаковые', 'code': 'n=int(input())\nprint("да" if n//100==n//10%10==n%10 else "нет")'}, {'slide': 108, 'level': 'А', 'desc': 'отладка сумма цифр трехзначного', 'notes': 'отладка', 'code': 'N=int(input())\nprint(N%10+N//10%10+N//100)'}, {'slide': 109, 'level': 'В', 'desc': 'сумма цифр с отрицательными', 'notes': 'abs', 'code': 'N=abs(int(input()))\nprint(N%10+N//10%10+N//100)'}, {'slide': 110, 'level': 'С', 'desc': 'наибольшее из трех отладка', 'notes': 'максимум', 'code': 'a=int(input())\nb=int(input())\nc=int(input())\nM=a\nif b>M:M=b\nif c>M:M=c\nprint(M)'}, {'slide': 118, 'level': 'А', 'desc': 'N раз вывести привет', 'notes': 'for', 'code': 'n=int(input())\nfor _ in range(n):print("Привет!")'}, {'slide': 118, 'level': 'В', 'desc': 'квадрат меньше N', 'notes': 'while', 'code': 'N=int(input())\nk=1\nwhile (k+1)**2<N:k+=1\nprint(k)'}, {'slide': 119, 'level': 'С', 'desc': 'число Фибоначчи не больше N', 'notes': 'Фибоначчи', 'code': 'N=int(input())\na,b=0,1\nwhile b<=N:a,b=b,a+b\nprint(a)'}, {'slide': 127, 'level': 'А', 'desc': 'число в троичной системе', 'notes': 'троичная', 'code': 'n=int(input())\nwhile n:print(n%3);n//=3'}, {'slide': 127, 'level': 'В', 'desc': 'сколько нулей в двоичной записи', 'notes': 'двоичная', 'code': 'n=int(input())\nk=0\nwhile n:\n if n%2==0:k+=1\n n//=2\nprint(k)'}, {'slide': 128, 'level': 'С', 'desc': 'наибольшая цифра', 'notes': 'максимум', 'code': 'n=int(input())\nM=0\nwhile n:\n d=n%10\n if d>M:M=d\n n//=10\nprint(M)'}, {'slide': 128, 'level': 'D', 'desc': 'одинаковые цифры рядом', 'notes': 'рядом', 'code': 's=input()\nprint("да" if any(s[i]==s[i+1] for i in range(len(s)-1)) else "нет")'}, {'slide': 132, 'level': 'А', 'desc': 'простые сомножители одной строкой', 'notes': 'разложение', 'code': 'n=int(input())\nd=2;parts=[]\nwhile n>1:\n if n%d==0:parts.append(str(d));n//=d\n else:d+=1\nprint("*".join(parts))'}, {'slide': 132, 'level': 'В', 'desc': 'сколько простых делителей', 'notes': 'делители', 'code': 'n=int(input())\nd=2;k=0\nwhile n>1:\n if n%d==0:k+=1;n//=d\n else:d+=1\nprint(k)'}, {'slide': 133, 'level': 'С', 'desc': 'сумма различных простых делителей', 'notes': 'различные', 'code': 'n=int(input())\nd=2;s=0;last=0\nwhile n>1:\n if n%d==0:\n if d!=last:s+=d;last=d\n n//=d\n else:d+=1\nprint(s)'}, {'slide': 133, 'level': 'D', 'desc': 'одинаковые простые делители', 'notes': 'повторы', 'code': 'n=int(input())\nd=2;last=0;f=False\nwhile n>1:\n if n%d==0:\n if d==last:f=True;break\n last=d;n//=d\n else:d+=1\nprint("да" if f else "нет")'}, {'slide': 141, 'level': 'А', 'desc': 'НОД алгоритм Евклида вычитанием', 'notes': 'НОД вычитание', 'code': 'a,b=map(int,input().split())\nA,B=a,b\nwhile a!=b:\n if a>b:a-=b\n else:b-=a\nprint(f"НОД({A},{B})={a}")'}, {'slide': 141, 'level': 'В', 'desc': 'НОД модифицированный алгоритм', 'notes': 'НОД остаток', 'code': 'a,b=map(int,input().split())\nA,B=a,b\nwhile b:a,b=b,a%b\nprint(f"НОД({A},{B})={a}")'}, {'slide': 142, 'level': 'С', 'desc': 'сравнить шаги двух алгоритмов НОД', 'notes': 'шаги', 'code': 'a,b=map(int,input().split())\nx,y=a,b;k1=0\nwhile x!=y:\n if x>y:x-=y\n else:y-=x\n k1+=1\nk2=0\nwhile b:a,b=b,a%b;k2+=1\nprint(k1,k2)'}]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
|
|
3
|
+
REQUIRED = ("x11vnc", "websockify")
|
|
4
|
+
OPTIONAL = ("Xvfb", "tigervnc")
|
|
5
|
+
|
|
6
|
+
def which(name):
|
|
7
|
+
return shutil.which(name)
|
|
8
|
+
|
|
9
|
+
def check_deps():
|
|
10
|
+
found = {}
|
|
11
|
+
missing = []
|
|
12
|
+
for name in REQUIRED + OPTIONAL:
|
|
13
|
+
path = which(name)
|
|
14
|
+
if path:
|
|
15
|
+
found[name] = path
|
|
16
|
+
else:
|
|
17
|
+
missing.append(name)
|
|
18
|
+
hard = [n for n in REQUIRED if n not in found]
|
|
19
|
+
return {"found": found, "missing": missing, "hard_missing": hard}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
def find_free_port(start=6080, limit=200):
|
|
4
|
+
for port in range(start, start + limit):
|
|
5
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
6
|
+
try:
|
|
7
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
8
|
+
s.bind(("127.0.0.1", port))
|
|
9
|
+
return port
|
|
10
|
+
except OSError:
|
|
11
|
+
pass
|
|
12
|
+
finally:
|
|
13
|
+
s.close()
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
def local_ip():
|
|
17
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
18
|
+
try:
|
|
19
|
+
s.connect(("8.8.8.8", 53))
|
|
20
|
+
return s.getsockname()[0]
|
|
21
|
+
except OSError:
|
|
22
|
+
return None
|
|
23
|
+
finally:
|
|
24
|
+
s.close()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import http.server, os, shutil, subprocess, threading, time
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .ports import find_free_port, local_ip
|
|
4
|
+
|
|
5
|
+
WEB = Path(__file__).parent / "web"
|
|
6
|
+
|
|
7
|
+
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
8
|
+
def __init__(self, *a, **k):
|
|
9
|
+
super().__init__(*a, directory=str(WEB), **k)
|
|
10
|
+
def log_message(self, *a): pass
|
|
11
|
+
|
|
12
|
+
class Server:
|
|
13
|
+
def __init__(self, os_iso=None, memory=512, http_port=None, vnc_port=None):
|
|
14
|
+
self.os_iso = os_iso
|
|
15
|
+
self.memory = memory
|
|
16
|
+
self.http_port = http_port or find_free_port(6080)
|
|
17
|
+
self.vnc_port = vnc_port or find_free_port(5900)
|
|
18
|
+
self.procs = []
|
|
19
|
+
self.httpd = None
|
|
20
|
+
|
|
21
|
+
def _spawn(self, args, name):
|
|
22
|
+
try:
|
|
23
|
+
p = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
24
|
+
self.procs.append((name, p))
|
|
25
|
+
print("start: " + name)
|
|
26
|
+
return True
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
print("missing: " + name)
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def start(self):
|
|
32
|
+
print("uxuxxvnc v0.2.0")
|
|
33
|
+
print("http port: " + str(self.http_port))
|
|
34
|
+
print("vnc port: " + str(self.vnc_port))
|
|
35
|
+
print("")
|
|
36
|
+
if self.os_iso:
|
|
37
|
+
qemu = shutil.which("qemu-system-x86_64") or shutil.which("qemu-system-i386")
|
|
38
|
+
if not qemu:
|
|
39
|
+
print("ERROR: qemu not found.")
|
|
40
|
+
print("termux: pkg install qemu-system-x86-64-headless")
|
|
41
|
+
print("debian: apt install qemu-system-x86")
|
|
42
|
+
print("alpine: apk add qemu-system-x86_64")
|
|
43
|
+
return
|
|
44
|
+
if not os.path.exists(self.os_iso):
|
|
45
|
+
print("ERROR: iso not found: " + self.os_iso)
|
|
46
|
+
return
|
|
47
|
+
print("qemu: " + qemu)
|
|
48
|
+
print("iso: " + self.os_iso)
|
|
49
|
+
print("mem: " + str(self.memory) + "MB")
|
|
50
|
+
self._spawn([qemu, "-m", str(self.memory), "-smp", "1",
|
|
51
|
+
"-cdrom", self.os_iso, "-boot", "d",
|
|
52
|
+
"-vga", "std",
|
|
53
|
+
"-net", "nic,model=e1000", "-net", "user",
|
|
54
|
+
"-rtc", "base=localtime",
|
|
55
|
+
"-vnc", "127.0.0.1:" + str(self.vnc_port - 5900)],
|
|
56
|
+
"qemu")
|
|
57
|
+
time.sleep(2)
|
|
58
|
+
ws = shutil.which("websockify")
|
|
59
|
+
if ws:
|
|
60
|
+
self._spawn([ws, str(self.http_port), "127.0.0.1:" + str(self.vnc_port),
|
|
61
|
+
"--web", str(WEB)], "websockify")
|
|
62
|
+
else:
|
|
63
|
+
self.httpd = http.server.ThreadingHTTPServer(("127.0.0.1", self.http_port), Handler)
|
|
64
|
+
threading.Thread(target=self.httpd.serve_forever, daemon=True).start()
|
|
65
|
+
print("start: http (websockify missing)")
|
|
66
|
+
time.sleep(0.5)
|
|
67
|
+
ip = local_ip()
|
|
68
|
+
print("")
|
|
69
|
+
print("open: http://127.0.0.1:" + str(self.http_port))
|
|
70
|
+
if ip:
|
|
71
|
+
print("lan: http://" + ip + ":" + str(self.http_port))
|
|
72
|
+
print("ctrl+c to stop")
|
|
73
|
+
try:
|
|
74
|
+
while True:
|
|
75
|
+
time.sleep(1)
|
|
76
|
+
except KeyboardInterrupt:
|
|
77
|
+
pass
|
|
78
|
+
finally:
|
|
79
|
+
self.stop()
|
|
80
|
+
|
|
81
|
+
def stop(self):
|
|
82
|
+
print("")
|
|
83
|
+
print("shutting down")
|
|
84
|
+
if self.httpd:
|
|
85
|
+
self.httpd.shutdown()
|
|
86
|
+
for _, p in self.procs:
|
|
87
|
+
try:
|
|
88
|
+
p.terminate()
|
|
89
|
+
p.wait(timeout=2)
|
|
90
|
+
except Exception:
|
|
91
|
+
try: p.kill()
|
|
92
|
+
except Exception: pass
|
|
93
|
+
self.procs.clear()
|
|
94
|
+
|
|
95
|
+
def serve(os_iso=None, memory=512, http_port=None, vnc_port=None):
|
|
96
|
+
Server(os_iso=os_iso, memory=memory,
|
|
97
|
+
http_port=http_port, vnc_port=vnc_port).start()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/uxuxx/__init__.py
|
|
4
|
+
src/uxuxx/cli.py
|
|
5
|
+
src/uxuxx/data.py
|
|
6
|
+
src/uxuxx/deps.py
|
|
7
|
+
src/uxuxx/ports.py
|
|
8
|
+
src/uxuxx/server.py
|
|
9
|
+
src/uxuxx.egg-info/PKG-INFO
|
|
10
|
+
src/uxuxx.egg-info/SOURCES.txt
|
|
11
|
+
src/uxuxx.egg-info/dependency_links.txt
|
|
12
|
+
src/uxuxx.egg-info/entry_points.txt
|
|
13
|
+
src/uxuxx.egg-info/top_level.txt
|
|
14
|
+
tests/test_uxuxx.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uxuxx
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import uxuxx
|
|
2
|
+
from uxuxx import ports, deps
|
|
3
|
+
|
|
4
|
+
def test_version():
|
|
5
|
+
assert uxuxx.__version__ == "0.1.0"
|
|
6
|
+
|
|
7
|
+
def test_free_port():
|
|
8
|
+
p = ports.find_free_port(25000)
|
|
9
|
+
assert p is not None and 25000 <= p < 25200
|
|
10
|
+
|
|
11
|
+
def test_which_python():
|
|
12
|
+
assert deps.which("python3") is not None
|
|
13
|
+
|
|
14
|
+
def test_which_missing():
|
|
15
|
+
assert deps.which("this-does-not-exist-xyz") is None
|
|
16
|
+
|
|
17
|
+
def test_deps_shape():
|
|
18
|
+
d = deps.check_deps()
|
|
19
|
+
assert "found" in d and "missing" in d and "hard_missing" in d
|