diff --git a/README.md b/README.md index bc2805b..7be5e18 100644 --- a/README.md +++ b/README.md @@ -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) @@ -55,7 +55,7 @@ Queries the code index and returns at most 100 results. #### CLI ```commandline -searchcode test +searchcode "import module" ``` #### SDK @@ -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) @@ -77,7 +77,7 @@ for result in search.results: #### CLI ````commandline -searchcode test --languages java,javascript +searchcode "import module" --languages java,javascript ```` #### SDK @@ -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) @@ -99,7 +99,7 @@ ___ #### CLI ```commandline -searchcode test --sources bitbucket,codeplex +searchcode "import module" --sources bitbucket,codeplex ``` #### SDK @@ -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) @@ -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 @@ -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) @@ -144,7 +144,7 @@ ___ #### CLI ```commandline -searchcode test --callback myCallback +searchcode "import module" --callback myCallback ``` #### SDK @@ -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) ``` diff --git a/searchcode/api.py b/searchcode/api.py index f128dd7..0ac84c9 100644 --- a/searchcode/api.py +++ b/searchcode/api.py @@ -1,6 +1,5 @@ import typing as t from platform import python_version, platform -from types import SimpleNamespace import requests @@ -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. @@ -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 = [] @@ -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: """ @@ -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. @@ -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() diff --git a/searchcode/cli.py b/searchcode/cli.py index d1cac55..5dbd439 100644 --- a/searchcode/cli.py +++ b/searchcode/cli.py @@ -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 @@ -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, @@ -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, @@ -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 @@ -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() @@ -105,9 +120,9 @@ 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. @@ -115,14 +130,14 @@ def print_table(records: List[SimpleNamespace], ignore_keys: List[str] = None) - :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) @@ -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) diff --git a/tests/test_searchcode.py b/tests/test_searchcode.py index 893ddf3..7f3ed56 100644 --- a/tests/test_searchcode.py +++ b/tests/test_searchcode.py @@ -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():