Publish package
This commit is contained in:
		
							
								
								
									
										57
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										57
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,57 +0,0 @@ | |||||||
| on: |  | ||||||
|   push: |  | ||||||
|   pull_request: |  | ||||||
|   workflow_run: |  | ||||||
|  |  | ||||||
| name: Build and deploy |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     permissions: |  | ||||||
|       contents: write |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v4 |  | ||||||
|  |  | ||||||
|       # Could instead install `texlive-full`, but that takes ~20 minutes. |  | ||||||
|       # We'll specify the packages we need manually. |  | ||||||
|       - name: "Install TeXLive" |  | ||||||
|         run: | |  | ||||||
|           sudo apt update |  | ||||||
|           DEBIAN_FRONTEND=noninteractive \ |  | ||||||
|             sudo apt install --yes \ |  | ||||||
|             texlive texlive-xetex \ |  | ||||||
|             texlive-games texlive-fonts-extra texlive-latex-extra \ |  | ||||||
|             texlive-pictures texlive-pstricks |  | ||||||
|  |  | ||||||
|       # Typst isn't packaged, and manual download gives us |  | ||||||
|       # more control anyway. |  | ||||||
|       - name: "Download Typst" |  | ||||||
|         run: | |  | ||||||
|           wget -q "https://github.com/typst/typst/releases/download/v0.12.0/typst-x86_64-unknown-linux-musl.tar.xz" |  | ||||||
|           tar -xf "typst-x86_64-unknown-linux-musl.tar.xz" |  | ||||||
|           mv "typst-x86_64-unknown-linux-musl/typst" . |  | ||||||
|           rm "typst-x86_64-unknown-linux-musl.tar.xz" |  | ||||||
|           rm -dr "typst-x86_64-unknown-linux-musl" |  | ||||||
|  |  | ||||||
|       # Builds all handouts, LaTeX and Typst |  | ||||||
|       - name: "Build handouts" |  | ||||||
|         run: TYPST_PATH="$(pwd)/typst" python tools/build/main.py |  | ||||||
|  |  | ||||||
|       # Upload logs, even if build fails. |  | ||||||
|       # LaTeX stdout/stderr isn't always helpful. |  | ||||||
|       - name: "Save LaTeX logs" |  | ||||||
|         uses: actions/upload-artifact@v3 |  | ||||||
|         if: always() |  | ||||||
|         with: |  | ||||||
|           name: "LaTeX logs" |  | ||||||
|           path: "**/*.log" |  | ||||||
|           retention-days: 1 |  | ||||||
|  |  | ||||||
|       # Upload build output |  | ||||||
|       - name: "Save output" |  | ||||||
|         uses: actions/upload-artifact@v3 |  | ||||||
|         with: |  | ||||||
|           name: "Build output" |  | ||||||
|           path: "output/*" |  | ||||||
|           retention-days: 10 |  | ||||||
							
								
								
									
										107
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | name: CI | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |   pull_request: | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   typos: | ||||||
|  |     name: "Typos" | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  |       - name: Check typos | ||||||
|  |         uses: crate-ci/typos@master | ||||||
|  |         with: | ||||||
|  |           config: ./tools/typos.toml | ||||||
|  |  | ||||||
|  |   typstyle: | ||||||
|  |     name: "Typst formatting" | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: "Download Typstyle" | ||||||
|  |         run: | | ||||||
|  |           wget -q "https://github.com/Enter-tainer/typstyle/releases/download/v0.12.14/typstyle-x86_64-unknown-linux-musl" | ||||||
|  |           chmod +x typstyle-x86_64-unknown-linux-musl | ||||||
|  |  | ||||||
|  |       - name: Check typst formatting | ||||||
|  |         run: | | ||||||
|  |           find . -name "*.typ" -type f -print0 | xargs -0 \ | ||||||
|  |             ./typstyle-x86_64-unknown-linux-musl --check | ||||||
|  |  | ||||||
|  |   build: | ||||||
|  |     needs: | ||||||
|  |       - typos | ||||||
|  |       - typstyle | ||||||
|  |  | ||||||
|  |     name: "Build" | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       contents: write | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       # Could instead install `texlive-full`, but that takes ~20 minutes. | ||||||
|  |       # We'll specify the packages we need manually. | ||||||
|  |       - name: "Install TeXLive" | ||||||
|  |         run: | | ||||||
|  |           sudo apt update | ||||||
|  |           DEBIAN_FRONTEND=noninteractive \ | ||||||
|  |             sudo apt install --yes \ | ||||||
|  |             texlive texlive-xetex \ | ||||||
|  |             texlive-games texlive-fonts-extra texlive-latex-extra \ | ||||||
|  |             texlive-pictures texlive-pstricks \ | ||||||
|  |             python3-requests | ||||||
|  |  | ||||||
|  |       # Typst isn't packaged, and manual download gives us | ||||||
|  |       # more control anyway. | ||||||
|  |       - name: "Download Typst" | ||||||
|  |         run: | | ||||||
|  |           wget -q "https://github.com/typst/typst/releases/download/v0.12.0/typst-x86_64-unknown-linux-musl.tar.xz" | ||||||
|  |           tar -xf "typst-x86_64-unknown-linux-musl.tar.xz" | ||||||
|  |           mv "typst-x86_64-unknown-linux-musl/typst" . | ||||||
|  |           rm "typst-x86_64-unknown-linux-musl.tar.xz" | ||||||
|  |           rm -dr "typst-x86_64-unknown-linux-musl" | ||||||
|  |  | ||||||
|  |       # Builds all handouts, LaTeX and Typst | ||||||
|  |       - name: "Build handouts" | ||||||
|  |         run: TYPST_PATH="$(pwd)/typst" python tools/scripts/build.py | ||||||
|  |  | ||||||
|  |       # Upload logs, even if build fails. | ||||||
|  |       # LaTeX stdout/stderr isn't always helpful. | ||||||
|  |       - name: "Save LaTeX logs" | ||||||
|  |         uses: actions/upload-artifact@v3 | ||||||
|  |         if: always() | ||||||
|  |         with: | ||||||
|  |           name: "LaTeX logs" | ||||||
|  |           path: "**/*.log" | ||||||
|  |           retention-days: 14 | ||||||
|  |  | ||||||
|  |       # Upload build output | ||||||
|  |       - name: "Save output" | ||||||
|  |         uses: actions/upload-artifact@v3 | ||||||
|  |         with: | ||||||
|  |           name: "Build output" | ||||||
|  |           path: "output/*" | ||||||
|  |           retention-days: 7 | ||||||
|  |  | ||||||
|  |       - name: "Publish package (hash)" | ||||||
|  |         if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} | ||||||
|  |         run: | | ||||||
|  |           PUBLISH_USER="${{ secrets.PUBLISH_USER }}" \ | ||||||
|  |           PUBLISH_KEY="${{ secrets.PUBLISH_KEY }}" \ | ||||||
|  |           VERSION="${{ github.sha }}" \ | ||||||
|  |           PACKAGE="${{ vars.PACKAGE }}" \ | ||||||
|  |             python tools/scripts/publish.py | ||||||
|  |  | ||||||
|  |       - name: "Publish package (latest)" | ||||||
|  |         if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} | ||||||
|  |         run: | | ||||||
|  |           PUBLISH_USER="${{ secrets.PUBLISH_USER }}" \ | ||||||
|  |           PUBLISH_KEY="${{ secrets.PUBLISH_KEY }}" \ | ||||||
|  |           VERSION="latest" \ | ||||||
|  |           PACKAGE="${{ vars.PACKAGE }}" \ | ||||||
|  |             python tools/scripts/publish.py | ||||||
							
								
								
									
										30
									
								
								.github/workflows/lints.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/lints.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,30 +0,0 @@ | |||||||
| on: |  | ||||||
|   push: |  | ||||||
|   pull_request: |  | ||||||
|  |  | ||||||
| name: Lints |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   typos: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v4 |  | ||||||
|       - name: Check typos |  | ||||||
|         uses: crate-ci/typos@master |  | ||||||
|         with: |  | ||||||
|           config: ./tools/typos.toml |  | ||||||
|  |  | ||||||
|   typstyle: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v4 |  | ||||||
|  |  | ||||||
|       - name: "Download Typstyle" |  | ||||||
|         run: | |  | ||||||
|           wget -q "https://github.com/Enter-tainer/typstyle/releases/download/v0.12.14/typstyle-x86_64-unknown-linux-musl" |  | ||||||
|           chmod +x typstyle-x86_64-unknown-linux-musl |  | ||||||
|  |  | ||||||
|       - name: Check typst formatting |  | ||||||
|         run: | |  | ||||||
|           find . -name "*.typ" -type f -print0 | xargs -0 \ |  | ||||||
|             ./typstyle-x86_64-unknown-linux-musl --check |  | ||||||
| @ -126,6 +126,9 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 		return None | 		return None | ||||||
| 	meta = read_meta_toml(meta_path) | 	meta = read_meta_toml(meta_path) | ||||||
| 
 | 
 | ||||||
|  | 	handout_file = sanitize_file_name(f"{meta['title']}.pdf") | ||||||
|  | 	solutions_file = sanitize_file_name(f"{meta['title']}.sols.pdf") | ||||||
|  | 
 | ||||||
| 	# Do nothing if not published | 	# Do nothing if not published | ||||||
| 	if not meta["publish_handout"]: | 	if not meta["publish_handout"]: | ||||||
| 		return None | 		return None | ||||||
| @ -136,7 +139,6 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 	out = OUT_DIR / out_subdir | 	out = OUT_DIR / out_subdir | ||||||
| 	out.mkdir(parents=True, exist_ok=True) | 	out.mkdir(parents=True, exist_ok=True) | ||||||
| 
 | 
 | ||||||
| 	file_name = sanitize_file_name(f"{meta['title']}.pdf") |  | ||||||
| 	res = subprocess.run( | 	res = subprocess.run( | ||||||
| 		[ | 		[ | ||||||
| 			TYPST_PATH, | 			TYPST_PATH, | ||||||
| @ -145,7 +147,7 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 			"main.typ", | 			"main.typ", | ||||||
| 			"--input", | 			"--input", | ||||||
| 			"show_solutions=false", | 			"show_solutions=false", | ||||||
| 			f"{out}/{file_name}", | 			f"{out}/{handout_file}", | ||||||
| 		], | 		], | ||||||
| 		cwd=source_dir, | 		cwd=source_dir, | ||||||
| 		stdout=subprocess.PIPE, | 		stdout=subprocess.PIPE, | ||||||
| @ -158,14 +160,13 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 	# Build solutions | 	# Build solutions | ||||||
| 	if meta["publish_solutions"]: | 	if meta["publish_solutions"]: | ||||||
| 		log(f"Building typst (solutions): {source_dir}") | 		log(f"Building typst (solutions): {source_dir}") | ||||||
| 		file_name = sanitize_file_name(f"{meta['title']}.sols.pdf") |  | ||||||
| 		res = subprocess.run( | 		res = subprocess.run( | ||||||
| 			[ | 			[ | ||||||
| 				TYPST_PATH, | 				TYPST_PATH, | ||||||
| 				"compile", | 				"compile", | ||||||
| 				"--ignore-system-fonts", | 				"--ignore-system-fonts", | ||||||
| 				"main.typ", | 				"main.typ", | ||||||
| 				f"{out}/{file_name}", | 				f"{out}/{solutions_file}", | ||||||
| 			], | 			], | ||||||
| 			cwd=source_dir, | 			cwd=source_dir, | ||||||
| 			stdout=subprocess.PIPE, | 			stdout=subprocess.PIPE, | ||||||
| @ -178,11 +179,9 @@ def build_typst(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 	return { | 	return { | ||||||
| 		"title": meta["title"], | 		"title": meta["title"], | ||||||
| 		"group": str(out_subdir), | 		"group": str(out_subdir), | ||||||
| 		"handout_file": f"{out_subdir}/{meta['title']}.pdf", | 		"handout_file": str(out / handout_file), | ||||||
| 		"solutions_file": ( | 		"solutions_file": ( | ||||||
| 			f"{out_subdir}/{meta['title']}.sols.pdf" | 			str(out / solutions_file) if meta["publish_solutions"] else None | ||||||
| 			if meta["publish_solutions"] |  | ||||||
| 			else None |  | ||||||
| 		), | 		), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -198,6 +197,9 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 		return None | 		return None | ||||||
| 	meta = read_meta_toml(meta_path) | 	meta = read_meta_toml(meta_path) | ||||||
| 
 | 
 | ||||||
|  | 	handout_file = sanitize_file_name(f"{meta['title']}.pdf") | ||||||
|  | 	solutions_file = sanitize_file_name(f"{meta['title']}.sols.pdf") | ||||||
|  | 
 | ||||||
| 	# Do nothing if not published | 	# Do nothing if not published | ||||||
| 	if not meta["publish_handout"]: | 	if not meta["publish_handout"]: | ||||||
| 		return None | 		return None | ||||||
| @ -219,9 +221,8 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 		stderr=subprocess.PIPE, | 		stderr=subprocess.PIPE, | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	file_name = sanitize_file_name(f"{meta['title']}.pdf") |  | ||||||
| 	try: | 	try: | ||||||
| 		shutil.copy(source_dir / "main.pdf", f"{out}/{file_name}") | 		shutil.copy(source_dir / "main.pdf", f"{out}/{handout_file}") | ||||||
| 	except Exception as e: | 	except Exception as e: | ||||||
| 		log(f"Error: {e}") | 		log(f"Error: {e}") | ||||||
| 		log_error(res) | 		log_error(res) | ||||||
| @ -243,9 +244,8 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 			stderr=subprocess.PIPE, | 			stderr=subprocess.PIPE, | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 		file_name = sanitize_file_name(f"{meta['title']}.sols.pdf") |  | ||||||
| 		try: | 		try: | ||||||
| 			shutil.copy(source_dir / "main.pdf", f"{out}/{file_name}") | 			shutil.copy(source_dir / "main.pdf", f"{out}/{solutions_file}") | ||||||
| 		except Exception as e: | 		except Exception as e: | ||||||
| 			log(f"Error: {e}") | 			log(f"Error: {e}") | ||||||
| 			log_error(res) | 			log_error(res) | ||||||
| @ -256,11 +256,9 @@ def build_xetex(source_dir: Path, out_subdir: Path) -> IndexEntry | None: | |||||||
| 	return { | 	return { | ||||||
| 		"title": meta["title"], | 		"title": meta["title"], | ||||||
| 		"group": str(out_subdir), | 		"group": str(out_subdir), | ||||||
| 		"handout_file": f"{out_subdir}/{meta['title']}.pdf", | 		"handout_file": str(out / handout_file), | ||||||
| 		"solutions_file": ( | 		"solutions_file": ( | ||||||
| 			f"{out_subdir}/{meta['title']}.sols.pdf" | 			str(out / solutions_file) if meta["publish_solutions"] else None | ||||||
| 			if meta["publish_solutions"] |  | ||||||
| 			else None |  | ||||||
| 		), | 		), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -294,14 +292,11 @@ def build_dir(base: str, out_sub: str, index: list[IndexEntry]): | |||||||
| 			if done: | 			if done: | ||||||
| 				break | 				break | ||||||
| 
 | 
 | ||||||
| 	return index |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| index: list[IndexEntry] = [] | index: list[IndexEntry] = [] | ||||||
| 
 | build_dir("src/Advanced", "Advanced", index) | ||||||
| index.extend(build_dir("src/Advanced", "Advanced", index)) | build_dir("src/Intermediate", "Intermediate", index) | ||||||
| index.extend(build_dir("src/Intermediate", "Intermediate", index)) | build_dir("src/Warm-Ups", "Warm-Ups", index) | ||||||
| index.extend(build_dir("src/Warm-Ups", "Warm-Ups", index)) |  | ||||||
| 
 | 
 | ||||||
| with open(OUT_DIR / "index.json", "w") as f: | with open(OUT_DIR / "index.json", "w") as f: | ||||||
| 	f.write(json.dumps(index)) | 	f.write(json.dumps(index)) | ||||||
							
								
								
									
										95
									
								
								tools/scripts/publish.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								tools/scripts/publish.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | |||||||
|  | # Publish the output of `build.py` | ||||||
|  | # as a Gitea package. | ||||||
|  |  | ||||||
|  | from pathlib import Path | ||||||
|  | import requests | ||||||
|  | import json | ||||||
|  | import os | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | URL = "https://git.betalupi.com" | ||||||
|  | USER = os.environ["PUBLISH_USER"] | ||||||
|  | PACKAGE = os.environ["PACKAGE"] | ||||||
|  | VERSION = os.environ["VERSION"] | ||||||
|  | AUTH = requests.auth.HTTPBasicAuth(USER, os.environ["PUBLISH_KEY"]) | ||||||
|  |  | ||||||
|  | ROOT: Path = Path(os.getcwd()) | ||||||
|  | SRC_DIR: Path = ROOT / "output" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def log(msg): | ||||||
|  | 	print(f"[PUBLISH.PY] {msg}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log(f"Version is {VERSION}") | ||||||
|  | log(f"Package is {PACKAGE}") | ||||||
|  | log(f"Running in {ROOT}") | ||||||
|  | if not ROOT.is_dir(): | ||||||
|  | 	log("Root is not a directory, cannot continue") | ||||||
|  | 	exit(1) | ||||||
|  |  | ||||||
|  | log(f"Source dir is {SRC_DIR}") | ||||||
|  | if not SRC_DIR.exists(): | ||||||
|  | 	log("Source dir doesn't exist, cannot continue") | ||||||
|  | 	exit(1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def del_package(): | ||||||
|  | 	log(f"Deleting package {PACKAGE}/{VERSION}") | ||||||
|  | 	res = requests.delete( | ||||||
|  | 		f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}", | ||||||
|  | 		auth=AUTH, | ||||||
|  | 	) | ||||||
|  | 	if res.status_code != 204 and res.status_code != 404: | ||||||
|  | 		log(f"Deletion failed with code {res.status_code}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Delete if already exists | ||||||
|  | # (important for the `latest` package) | ||||||
|  | del_package() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def upload(data, target: str): | ||||||
|  | 	target = re.sub("[^A-Za-z0-9_. -]+", "", target) | ||||||
|  |  | ||||||
|  | 	res = requests.put( | ||||||
|  | 		f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}/{target}", | ||||||
|  | 		auth=AUTH, | ||||||
|  | 		data=data, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	if res.status_code != 201: | ||||||
|  | 		log(f"Upload failed with code {res.status_code}") | ||||||
|  | 		del_package()  # Do not keep partial package if upload fails | ||||||
|  | 		exit(1) | ||||||
|  |  | ||||||
|  | 	return f"{URL}/api/packages/{USER}/generic/{PACKAGE}/{VERSION}/{target}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | index_file = SRC_DIR / "index.json" | ||||||
|  | with index_file.open("r") as f: | ||||||
|  | 	index = json.load(f) | ||||||
|  |  | ||||||
|  | new_index = [] | ||||||
|  | for handout in index: | ||||||
|  | 	title = handout["title"] | ||||||
|  | 	group = handout["group"] | ||||||
|  | 	h_file = SRC_DIR / handout["handout_file"] | ||||||
|  | 	s_file = handout["solutions_file"] | ||||||
|  | 	if s_file is not None: | ||||||
|  | 		s_file = SRC_DIR / s_file | ||||||
|  | 	log(f"Uploading {title}") | ||||||
|  |  | ||||||
|  | 	h_url = None | ||||||
|  | 	s_url = None | ||||||
|  |  | ||||||
|  | 	h_url = upload(h_file.open("rb").read(), f"{group} - {title}.pdf") | ||||||
|  | 	if s_file is not None: | ||||||
|  | 		log(f"Uploading {title} solutions") | ||||||
|  | 		s_url = upload(s_file.open("rb").read(), f"{group} - {title}.sols.pdf") | ||||||
|  |  | ||||||
|  | 	new_index.append( | ||||||
|  | 		{"title": title, "group": group, "handout": h_url, "solutions": s_url} | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | upload(json.dumps(new_index), "index.json") | ||||||
| @ -2,6 +2,7 @@ exclude = ["venv"] | |||||||
| line-length = 88 | line-length = 88 | ||||||
| indent-width = 4 | indent-width = 4 | ||||||
| target-version = "py39" | target-version = "py39" | ||||||
|  | include = ["scripts/**/*.py"] | ||||||
| 
 | 
 | ||||||
| [lint] | [lint] | ||||||
| select = ["E4", "E7", "E9", "F"] | select = ["E4", "E7", "E9", "F"] | ||||||
		Reference in New Issue
	
	Block a user