Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ WORKDIR /app

COPY . /app

RUN pip install --upgrade pip \
&& pip install . \
RUN pip install --upgrade pip poetry \
&& poetry install \
&& apt-get clean

ENTRYPOINT ["searchcode"]
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ sc search "import module"

```python
from pprint import pprint
from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="import module")
Expand Down Expand Up @@ -70,7 +70,7 @@ searchcode "import module"

```python
from pprint import pprint
from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="import module")
Expand All @@ -92,7 +92,7 @@ searchcode "import module" --languages java,javascript

```python
from pprint import pprint
from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="import module", languages=["Java", "JavaScript"])
Expand All @@ -115,7 +115,7 @@ searchcode "import module" --sources bitbucket,codeplex

```python
from pprint import pprint
from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="import module", sources=["BitBucket", "CodePlex"])
Expand All @@ -138,7 +138,7 @@ searchcode "import module" --lines-of-code-gt 500 --lines-of-code-lt 1000

```python
from pprint import pprint
from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="import module", lines_of_code_gt=500, lines_of_code_lt=1000)
Expand All @@ -161,7 +161,7 @@ searchcode "import module" --callback myCallback

```python
from pprint import pprint
from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="import module", callback="myCallback")
Expand Down Expand Up @@ -222,7 +222,7 @@ searchode code 4061576

```python

from searchcode import Searchcode
from src.searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
data = sc.code(4061576)
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "searchcode"
version = "0.5.0"
version = "0.5.1"
description = "Simple, comprehensive code search."
authors = ["Ritchie Mwewa <rly0nheart@duck.com>"]
license = "GPLv3+"
Expand Down Expand Up @@ -30,5 +30,5 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
sc = "searchcode.cli:cli"
searchcode = "searchcode.cli:cli"
sc = "searchcode.__app:cli"
searchcode = "searchcode.__app:cli"
116 changes: 18 additions & 98 deletions searchcode/cli.py → src/searchcode/__app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import os
import subprocess
import typing as t
from types import SimpleNamespace

import rich_click as click
from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.pretty import pprint
from rich.syntax import Syntax

from . import License, __pkg__, __version__
from . import License
from .__lib import (
__pkg__,
__version__,
update_window_title,
clear_screen,
print_jsonp,
print_panels,
)
from .api import Searchcode

sc = Searchcode(user_agent=f"{__pkg__}-sdk/cli")
Expand All @@ -46,7 +49,7 @@ def cli():
Simple, comprehensive code search.
"""

__update_window_title("Source code search engine.")
update_window_title(text="Source code search engine.")


@cli.command("license")
Expand All @@ -59,8 +62,8 @@ def licence(
"""
Show license information
"""
__clear_screen()
__update_window_title(
clear_screen()
update_window_title(
text="Terms and Conditions" if conditions else "Warranty" if warranty else None
)
if conditions:
Expand Down Expand Up @@ -133,8 +136,8 @@ def search(

e.g., sc search "import module"
"""
__clear_screen()
__update_window_title(text=query)
clear_screen()
update_window_title(text=query)

with console.status(
f"Querying code index with search string: [green]{query}[/]..."
Expand All @@ -154,9 +157,9 @@ def search(
)

(
__print_jsonp(jsonp=response)
print_jsonp(jsonp=response)
if callback
else (pprint(response) if pretty else __print_panels(data=response.results))
else (pprint(response) if pretty else print_panels(data=response.results))
)


Expand All @@ -168,8 +171,8 @@ def code(id: int):

e.g., sc code 4061576
"""
__clear_screen()
__update_window_title(text=str(id))
clear_screen()
update_window_title(text=str(id))
with console.status(f"Fetching data for code file with ID: [cyan]{id}[/]..."):
data = sc.code(id)
lines = data.code
Expand All @@ -179,86 +182,3 @@ def code(id: int):
code=lines, lexer=language, line_numbers=True, theme="dracula"
)
console.print(syntax)


def __print_jsonp(jsonp: str) -> None:
"""
Pretty-prints a raw JSONP string.

:param jsonp: A complete JSONP string.
"""
syntax = Syntax(jsonp, "text", line_numbers=True)
console.print(syntax)


def __print_panels(data: t.List[SimpleNamespace]):
"""
Render a list of code records as rich panels with syntax highlighting.
Line numbers are preserved and displayed alongside code content.

:param data: A list of dictionaries, where each dictionary represents a code record
"""

def extract_code_string_with_linenumbers(lines_dict: t.Dict[str, str]) -> str:
"""
Convert a dictionary of line_number: code_line into a single
multiline string sorted by line number.

Each line is right-aligned to maintain visual alignment in output.

:param lines_dict: Dictionary where keys are line numbers (as strings) and values are lines of code.
:return: Multiline string with original line numbers included.
"""
sorted_lines = sorted(lines_dict.items(), key=lambda x: int(x[0]))
numbered_lines = [
f"{line_no.rjust(4)} {line.rstrip()}" for line_no, line in sorted_lines
]
return "\n".join(numbered_lines)

for item in data:
filename = item.filename
repo = item.repo
language = item.language
lines_count = item.linescount
lines = item.lines

code_string = extract_code_string_with_linenumbers(lines_dict=lines.__dict__)

syntax = Syntax(
code=code_string,
lexer=language,
word_wrap=False,
indent_guides=True,
theme="dracula",
)

panel = Panel(
renderable=syntax,
box=box.ROUNDED,
title=f"[bold]{filename}[/] ([blue]{repo}[/]) {language} ⸱ [cyan]{lines_count}[/] lines",
highlight=True,
)

console.print(panel)


def __update_window_title(text: str):
"""
Update the current window title with the specified text.

:param text: Text to update the window with.
"""
console.set_window_title(f"{__pkg__.capitalize()} - {text}")


def __clear_screen():
"""
Clear the screen.

Not using console.clear() because it doesn't really clear the screen.
It instead creates a space between the items on top and below,
then moves the cursor to the items on the bottom, thus creating the illusion of a "cleared screen".

Using subprocess might be a bad idea, but I'm yet to see how bad of an idea that is.
"""
subprocess.run(["cls" if os.name == "nt" else "clear"])
2 changes: 1 addition & 1 deletion searchcode/__init__.py → src/searchcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .api import Searchcode

__pkg__ = "searchcode"
__version__ = "0.5.0"
__version__ = "0.5.1"
__author__ = "Ritchie Mwewa"


Expand Down
96 changes: 96 additions & 0 deletions src/searchcode/__lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import os
import subprocess
import typing as t
from types import SimpleNamespace

from rich import box
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax

from . import __pkg__, __version__

console = Console()


def print_jsonp(jsonp: str) -> None:
"""
Pretty-prints a raw JSONP string.

:param jsonp: A complete JSONP string.
"""
syntax = Syntax(jsonp, "text", line_numbers=True)
console.print(syntax)


def print_panels(data: t.List[SimpleNamespace]):
"""
Render a list of code records as rich panels with syntax highlighting.
Line numbers are preserved and displayed alongside code content.

:param data: A list of dictionaries, where each dictionary represents a code record
"""

def extract_code_string_with_linenumbers(lines_dict: t.Dict[str, str]) -> str:
"""
Convert a dictionary of line_number: code_line into a single
multiline string sorted by line number.

Each line is right-aligned to maintain visual alignment in output.

:param lines_dict: Dictionary where keys are line numbers (as strings) and values are lines of code.
:return: Multiline string with original line numbers included.
"""
sorted_lines = sorted(lines_dict.items(), key=lambda x: int(x[0]))
numbered_lines = [
f"{line_no.rjust(4)} {line.rstrip()}" for line_no, line in sorted_lines
]
return "\n".join(numbered_lines)

for item in data:
filename = item.filename
repo = item.repo
language = item.language
lines_count = item.linescount
lines = item.lines

code_string = extract_code_string_with_linenumbers(lines_dict=lines.__dict__)

syntax = Syntax(
code=code_string,
lexer=language,
word_wrap=False,
indent_guides=True,
theme="dracula",
)

panel = Panel(
renderable=syntax,
box=box.ROUNDED,
title=f"[bold]{filename}[/] ([blue]{repo}[/]) {language} ⸱ [cyan]{lines_count}[/] lines",
highlight=True,
)

console.print(panel)


def update_window_title(text: str):
"""
Update the current window title with the specified text.

:param text: Text to update the window with.
"""
console.set_window_title(f"{__pkg__.capitalize()} v{__version__} - {text}")


def clear_screen():
"""
Clear the screen.

Not using console.clear() because it doesn't really clear the screen.
It instead creates a space between the items on top and below,
then moves the cursor to the items on the bottom, thus creating the illusion of a "cleared screen".

Using subprocess might be a bad idea, but I'm yet to see how bad of an idea that is.
"""
subprocess.run(["cls" if os.name == "nt" else "clear"])
File renamed without changes.
File renamed without changes.