Skip to content

Commit a9ca328

Browse files
authored
Add custom nix flake (#51)
# What's changing? This adds a `flake.nix` file, allowing users to build and install LazyGithub via nix. # Why is it changing? Previous installation mechanisms meant either installing via the Github CLI or via `uvx`, which weren't always the most convenient. --------- Co-authored-by: gizmo385 <gizmo385@users.noreply.github.com>
1 parent 0ccb43c commit a9ca328

8 files changed

Lines changed: 167 additions & 73 deletions

File tree

.claude/settings.local.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
"allow": [
44
"Bash(find:*)",
55
"Bash(ls:*)",
6-
"Bash(./start.sh:*)"
6+
"Bash(./start.sh:*)",
7+
"Bash(nix flake check:*)",
8+
"Bash(nix-prefetch-url:*)",
9+
"Bash(nix-prefetch-github:*)",
10+
"Bash(nix search:*)",
11+
"Bash(nix build:*)"
712
],
813
"deny": []
914
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,6 @@ cython_debug/
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161+
162+
# nix stuff
163+
result

flake.lock

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
description = "A terminal UI for interacting with Github";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
flake-utils.url = "github:numtide/flake-utils";
7+
};
8+
9+
outputs =
10+
{
11+
self,
12+
nixpkgs,
13+
flake-utils,
14+
}:
15+
flake-utils.lib.eachDefaultSystem (
16+
system:
17+
let
18+
pkgs = nixpkgs.legacyPackages.${system};
19+
20+
# Use Python with tests disabled globally
21+
python = pkgs.python311.override {
22+
packageOverrides = final: prev: {
23+
buildPythonPackage =
24+
args:
25+
prev.buildPythonPackage (
26+
args
27+
// {
28+
doCheck = false;
29+
}
30+
);
31+
buildPythonApplication =
32+
args:
33+
prev.buildPythonApplication (
34+
args
35+
// {
36+
doCheck = false;
37+
}
38+
);
39+
};
40+
};
41+
42+
in
43+
{
44+
packages.default = python.pkgs.buildPythonApplication {
45+
pname = "lazy-github";
46+
version = builtins.replaceStrings ["\n" "\"" " " "VERSION" "="] ["" "" "" "" ""] (builtins.readFile ./lazy_github/version.py);
47+
format = "pyproject";
48+
49+
src = ./.;
50+
51+
nativeBuildInputs = with python.pkgs; [
52+
hatchling
53+
];
54+
55+
propagatedBuildInputs = with python.pkgs; [
56+
httpx
57+
hishel
58+
pydantic
59+
textual
60+
click
61+
];
62+
63+
doCheck = false;
64+
65+
meta = with pkgs.lib; {
66+
description = "A terminal UI for interacting with Github";
67+
homepage = "https://github.com/gizmo385/gh-lazy";
68+
license = licenses.mit;
69+
maintainers = [ ];
70+
};
71+
};
72+
73+
# Development shell
74+
devShells.default = pkgs.mkShell {
75+
buildInputs = [
76+
python
77+
python.pkgs.pip
78+
python.pkgs.uv
79+
];
80+
};
81+
}
82+
);
83+
}
84+

lazy_github/cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import shutil
23

34
import click
@@ -14,6 +15,10 @@
1415
@click.pass_context
1516
def cli(ctx: click.Context) -> None:
1617
"""A Terminal UI for interacting with Github"""
18+
# Set LAZY_GITHUB_ORIGINAL_PWD if not already set (fixes repo detection when installed via flake)
19+
if "LAZY_GITHUB_ORIGINAL_PWD" not in os.environ:
20+
os.environ["LAZY_GITHUB_ORIGINAL_PWD"] = os.getcwd()
21+
1722
if ctx.invoked_subcommand is None:
1823
ctx.invoke(run)
1924

lazy_github/ui/screens/settings.py

Lines changed: 8 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from pydantic import BaseModel
66
from pydantic.fields import FieldInfo
7-
from textual import on, work
7+
from textual import on
88
from textual.app import ComposeResult
99
from textual.binding import Binding
1010
from textual.containers import Container, Horizontal, ScrollableContainer, Vertical
@@ -16,8 +16,6 @@
1616
from textual.validation import ValidationResult, Validator
1717
from textual.widget import Widget
1818
from textual.widgets import Button, Collapsible, Input, Label, Markdown, RichLog, Rule, Select, Static, Switch
19-
from textual_fspicker import FileOpen
20-
from textual_fspicker.path_filters import Filters
2119

2220
from lazy_github.lib.bindings import LazyGithubBindings
2321
from lazy_github.lib.context import LazyGithubContext
@@ -41,59 +39,12 @@ def validate(self, value: str) -> ValidationResult:
4139
return self.success()
4240

4341

44-
class FileSelector(Vertical):
45-
DEFAULT_CSS = """
46-
FileSelector {
47-
height: 10;
48-
}
49-
50-
Input {
51-
width: 70;
52-
}
53-
"""
42+
class PathInput(Input):
43+
"""Simple input field for file paths"""
5444

5545
def __init__(self, field_name: str, field: FieldInfo, selected_file: Path | None) -> None:
56-
super().__init__()
57-
self.field_name = field_name
58-
self.field = field
59-
self.selected_file: Path | None = selected_file
60-
61-
self.input_id = _id_for_field_input(self.field_name)
62-
self.submit_button = Button("Select File")
63-
self.remove_button = Button("Remove File")
64-
self.remove_button.visible = self.selected_file is not None
65-
self.remove_button.display = self.selected_file is not None
66-
67-
def compose(self) -> ComposeResult:
68-
current_filename = str(self.selected_file) if self.selected_file else ""
69-
yield Input(current_filename, disabled=True, id=self.input_id, placeholder="No file selected")
70-
yield self.submit_button
71-
yield self.remove_button
72-
73-
@work
74-
async def select_file(self) -> None:
75-
markdown_filter = Filters(("Markdown", lambda p: p.suffix.lower() == ".md"))
76-
file_picker = FileOpen(
77-
open_button="Select",
78-
default_file=self.selected_file,
79-
must_exist=True,
80-
filters=markdown_filter,
81-
)
82-
if new_selected_file := await self.app.push_screen_wait(file_picker):
83-
self.selected_file = new_selected_file
84-
self.query_one(f"#{self.input_id}", Input).value = str(new_selected_file)
85-
86-
@on(Button.Pressed)
87-
async def handle_select_file_pressed(self, press: Button.Pressed) -> None:
88-
if press.button is self.submit_button:
89-
self.select_file()
90-
self.remove_button.visible = True
91-
self.remove_button.display = True
92-
elif press.button is self.remove_button:
93-
self.query_one(f"#{self.input_id}", Input).value = ""
94-
self.selected_file = None
95-
self.remove_button.visible = False
96-
self.remove_button.display = False
46+
current_filename = str(selected_file) if selected_file else ""
47+
super().__init__(value=current_filename, id=_id_for_field_input(field_name), placeholder="Enter file path...")
9748

9849

9950
class FieldSetting(Container):
@@ -126,7 +77,7 @@ def _field_to_widget(self) -> Widget:
12677
elif self.field.annotation == list[str]:
12778
return Input(value=str(", ".join(self.value)), id=id, validators=[ListOfStringValidator()])
12879
elif self.field.annotation == Optional[Path]:
129-
return FileSelector(self.field_name, self.field, self.value)
80+
return PathInput(self.field_name, self.field, self.value)
13081
else:
13182
# If no other input mechanism fits, then we'll fallback to just a raw string input field
13283
return Input(value=str(self.value), id=id)
@@ -342,8 +293,8 @@ def _update_settings(self):
342293

343294
# We want to handle paths specially
344295
new_value = updated_value_input.value
345-
if field_info.annotation == Path | None:
346-
new_value = Path(str(new_value)) if new_value else None
296+
if field_info.annotation == Optional[Path]:
297+
new_value = Path(str(new_value).strip()) if new_value and str(new_value).strip() else None
347298

348299
setattr(model, field_name, new_value)
349300

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ dependencies = [
2626
"pydantic<3",
2727
"textual",
2828
"click>=8.1.7",
29-
"textual-fspicker>=0.4.1",
3029
]
3130

3231
[project.scripts]

uv.lock

Lines changed: 0 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)