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
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ searchcode --help
from searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="test")
search = sc.search(query="import module")

for result in search.results:
print(result)
Expand Down Expand Up @@ -55,7 +55,7 @@ Queries the code index and returns at most 100 results.
#### CLI

```commandline
searchcode test
searchcode "import module"
```

#### SDK
Expand All @@ -64,7 +64,7 @@ searchcode test
from searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="test")
search = sc.search(query="import module")

for result in search.results:
print(result)
Expand All @@ -77,7 +77,7 @@ for result in search.results:
#### CLI

````commandline
searchcode test --languages java,javascript
searchcode "import module" --languages java,javascript
````

#### SDK
Expand All @@ -86,7 +86,7 @@ searchcode test --languages java,javascript
from searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="test", languages=["Java", "JavaScript"])
search = sc.search(query="import module", languages=["Java", "JavaScript"])

for result in search.results:
print(result.language)
Expand All @@ -99,7 +99,7 @@ ___
#### CLI

```commandline
searchcode test --sources bitbucket,codeplex
searchcode "import module" --sources bitbucket,codeplex
```

#### SDK
Expand All @@ -108,7 +108,7 @@ searchcode test --sources bitbucket,codeplex
from searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="test", sources=["BitBucket", "CodePlex"])
search = sc.search(query="import module", sources=["BitBucket", "CodePlex"])

for result in search.results:
print(result.filename)
Expand All @@ -121,7 +121,7 @@ ___
#### CLI

```commandline
searchcode test --lines-of-code-gt 500 --lines-of-code-lt 1000
searchcode "import module" --lines-of-code-gt 500 --lines-of-code-lt 1000
```

#### SDK
Expand All @@ -131,7 +131,7 @@ searchcode test --lines-of-code-gt 500 --lines-of-code-lt 1000
from searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="test", lines_of_code_gt=500, lines_of_code_lt=1000)
search = sc.search(query="import module", lines_of_code_gt=500, lines_of_code_lt=1000)

for result in search.results:
print(result)
Expand All @@ -144,7 +144,7 @@ ___
#### CLI

```commandline
searchcode test --callback myCallback
searchcode "import module" --callback myCallback
```

#### SDK
Expand All @@ -153,7 +153,7 @@ searchcode test --callback myCallback
from searchcode import Searchcode

sc = Searchcode(user_agent="My-Searchcode-script")
search = sc.search(query="test", callback="myCallback")
search = sc.search(query="import module", callback="myCallback")
print(search)
```

Expand Down
45 changes: 10 additions & 35 deletions searchcode/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import typing as t
from platform import python_version, platform
from types import SimpleNamespace

import requests

Expand All @@ -25,7 +24,7 @@ def search(
lines_of_code_gt: t.Optional[int] = None,
lines_of_code_lt: t.Optional[int] = None,
callback: t.Optional[str] = None,
) -> t.Union[SimpleNamespace, str]:
) -> t.Union[t.Dict, str]:
"""
Searches and returns code snippets matching the query.

Expand Down Expand Up @@ -54,8 +53,8 @@ def search(
:type lines_of_code_lt: int
:param callback: Callback function (JSONP only)
:type callback: str
:return: The search results as a SimpleNamespace object.
:rtype: SimpleNamespace
:return: The search results as a Dict object.
:rtype: Dict
"""

results: t.List = []
Expand All @@ -76,11 +75,13 @@ def search(
*[("lan", language_id) for language_id in language_ids],
*[("src", source_id) for source_id in source_ids],
],
is_callback=callback,
callback=callback,
)

response["results"] = response.get("results")[:per_page]
return self.__response_to_namespace_obj(response=response)
if not callback:
response["results"] = response.get("results", [])[:per_page]

return response

def code(self, __id: int) -> str:
"""
Expand Down Expand Up @@ -115,7 +116,7 @@ def __send_request(
self,
endpoint: str,
params: t.Optional[t.List[t.Tuple[str, str]]] = None,
is_callback: str = None,
callback: str = None,
) -> t.Union[t.Dict, t.List, str]:
"""
(Private function) Sends a GET request to the specified endpoint with the given headers and parameters.
Expand All @@ -138,30 +139,4 @@ def __send_request(
},
)
response.raise_for_status()
return response.text if is_callback else response.json()

def __response_to_namespace_obj(
self, response: t.Union[t.List[t.Dict], t.Dict]
) -> t.Union[t.List[SimpleNamespace], SimpleNamespace, t.List[t.Dict], t.Dict]:
"""
(Private function) Recursively converts the API response into a SimpleNamespace object(s).

:param response: The object to convert, either a dictionary or a list of dictionaries.
:type response: Union[List[Dict], Dict]
:return: A SimpleNamespace object or list of SimpleNamespace objects.
:rtype: Union[List[SimpleNamespace], SimpleNamespace, None]
"""

if isinstance(response, t.Dict):
return SimpleNamespace(
**{
key: self.__response_to_namespace_obj(response=value)
for key, value in response.items()
}
)
elif isinstance(response, t.List):
return [
self.__response_to_namespace_obj(response=item) for item in response
]
else:
return response
return response.text if callback else response.json()
53 changes: 39 additions & 14 deletions searchcode/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from types import SimpleNamespace
from typing import Optional, List
from typing import Optional, List, Dict

import rich_click as click
from rich import print as rprint, box
from rich.pretty import pprint
from rich.syntax import Syntax
from rich.table import Table
from whats_that_code.election import guess_language_all_methods
Expand All @@ -17,14 +17,16 @@
@click.group()
def cli():
"""
Searchcode: Simple, comprehensive code search.
Searchcode CLI

Simple, comprehensive code search.
"""
...


@cli.command()
@click.argument("query", type=str)
@click.option("--pretty", type=bool, help="Return results in raw JSON format.")
@click.option("--pretty", help="Return results in raw JSON format.", is_flag=True)
@click.option(
"--page",
type=int,
Expand Down Expand Up @@ -57,6 +59,11 @@ def cli():
type=str,
help="A comma-separated list of code languages to filter results.",
)
@click.option(
"--callback",
type=str,
help="callback function (returns JSONP)",
)
def search(
query: str,
page: int = 0,
Expand All @@ -66,11 +73,12 @@ def search(
lines_of_code_gt: Optional[int] = None,
languages: Optional[str] = None,
sources: Optional[str] = None,
callback: Optional[str] = None,
):
"""
Query the code index and (returns 100 results by default).

e.g., searchcode search "gsub ext:erb" --pretty
e.g., searchcode search "import module"
"""
languages = languages.split(",") if languages else None
sources = sources.split(",") if sources else None
Expand All @@ -83,11 +91,18 @@ def search(
sources=sources,
lines_of_code_lt=lines_of_code_lt,
lines_of_code_gt=lines_of_code_gt,
callback=callback,
)

(
__print_jsonp(jsonp=results)
if callback
else (
pprint(results)
if pretty
else __print_table(records=results["results"], ignore_keys=["lines"])
)
)
if pretty:
rprint(results)
else:
print_table(records=results.results, ignore_keys=["lines"])


@cli.command()
Expand All @@ -105,24 +120,24 @@ def code(id: int):
rprint(syntax)


def print_table(records: List[SimpleNamespace], ignore_keys: List[str] = None) -> None:
def __print_table(records: List[Dict], ignore_keys: List[str] = None) -> None:
"""
Creates a rich table from a list of SimpleNamespace objects,
Creates a rich table from a list of dict objects,
ignoring specified keys.

:param records: List of SimpleNamespace instances.
:param ignore_keys: List of keys to exclude from the table.
:return: None. Prints the table using rich.
"""
if not records:
raise ValueError("Data must be a non-empty list of SimpleNamespace objects.")
raise ValueError("Data must be a non-empty list of dict objects.")

ignore_keys = ignore_keys or []

# Collect all unique keys across all records, excluding ignored ones
all_keys = set()
for record in records:
all_keys.update(key for key in record.__dict__.keys() if key not in ignore_keys)
all_keys.update(key for key in record.keys() if key not in ignore_keys)

columns = sorted(all_keys)

Expand All @@ -133,8 +148,18 @@ def print_table(records: List[SimpleNamespace], ignore_keys: List[str] = None) -
table.add_column(column.capitalize(), style=style)

for record in records:
data = record.__dict__
data = record
row = [str(data.get(column, "")) for column in columns]
table.add_row(*row)

rprint(table)


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)
rprint(syntax)
4 changes: 2 additions & 2 deletions tests/test_searchcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

def test_filter_by_extension():
search = sc.search("gsub ext:erb")
for result in search.results:
assert result.filename.endswith(".erb")
for result in search.get("results"):
assert result.get("filename").endswith(".erb")


def test_code_result():
Expand Down