Skip to content

Commit 2b434a1

Browse files
committed
Format SQL output to a Slack message
1 parent e733d3d commit 2b434a1

7 files changed

Lines changed: 177 additions & 46 deletions

File tree

‎package.json‎

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"description": "Format code on the clipboard",
66
"icon": "icon.png",
77
"author": "ikaraszi",
8-
"categories": ["Developer Tools"],
8+
"categories": [
9+
"Developer Tools"
10+
],
911
"license": "MIT",
1012
"commands": [
1113
{
@@ -109,6 +111,12 @@
109111
"title": "Format SQL",
110112
"description": "Formats SQL on the clipboard",
111113
"mode": "view"
114+
},
115+
{
116+
"name": "format-sql-output",
117+
"title": "Format SQL With Output",
118+
"description": "Formats SQL with its result on the clipboard",
119+
"mode": "view"
112120
}
113121
],
114122
"preferences": [
@@ -126,25 +134,82 @@
126134
"type": "dropdown",
127135
"description": "Set the SQL dialect",
128136
"data": [
129-
{ "value": "sql", "title": "Standard SQL" },
130-
{ "value": "bigquery", "title": "GCP BigQuery" },
131-
{ "value": "db2", "title": "IBM DB2" },
132-
{ "value": "db2i", "title": "IBM DB2i (experimental)" },
133-
{ "value": "duckdb", "title": "DuckDB" },
134-
{ "value": "hive", "title": "Apache Hive" },
135-
{ "value": "mariadb", "title": "MariaDB" },
136-
{ "value": "mysql", "title": "MySQL" },
137-
{ "value": "tidb", "title": "TiDB" },
138-
{ "value": "n1ql", "title": "Couchbase N1QL" },
139-
{ "value": "plsql", "title": "Oracle PL/SQL" },
140-
{ "value": "postgresql", "title": "PostgreSQL" },
141-
{ "value": "redshift", "title": "Amazon Redshift" },
142-
{ "value": "singlestoredb", "title": "SingleStoreDB" },
143-
{ "value": "snowflake", "title": "Snowflake" },
144-
{ "value": "spark", "title": "Spark" },
145-
{ "value": "sqlite", "title": "SQLite" },
146-
{ "value": "transactsql", "title": "SQL Server Transact-SQL" },
147-
{ "value": "trino", "title": "Trino / Presto" }
137+
{
138+
"value": "sql",
139+
"title": "Standard SQL"
140+
},
141+
{
142+
"value": "bigquery",
143+
"title": "GCP BigQuery"
144+
},
145+
{
146+
"value": "db2",
147+
"title": "IBM DB2"
148+
},
149+
{
150+
"value": "db2i",
151+
"title": "IBM DB2i (experimental)"
152+
},
153+
{
154+
"value": "duckdb",
155+
"title": "DuckDB"
156+
},
157+
{
158+
"value": "hive",
159+
"title": "Apache Hive"
160+
},
161+
{
162+
"value": "mariadb",
163+
"title": "MariaDB"
164+
},
165+
{
166+
"value": "mysql",
167+
"title": "MySQL"
168+
},
169+
{
170+
"value": "tidb",
171+
"title": "TiDB"
172+
},
173+
{
174+
"value": "n1ql",
175+
"title": "Couchbase N1QL"
176+
},
177+
{
178+
"value": "plsql",
179+
"title": "Oracle PL/SQL"
180+
},
181+
{
182+
"value": "postgresql",
183+
"title": "PostgreSQL"
184+
},
185+
{
186+
"value": "redshift",
187+
"title": "Amazon Redshift"
188+
},
189+
{
190+
"value": "singlestoredb",
191+
"title": "SingleStoreDB"
192+
},
193+
{
194+
"value": "snowflake",
195+
"title": "Snowflake"
196+
},
197+
{
198+
"value": "spark",
199+
"title": "Spark"
200+
},
201+
{
202+
"value": "sqlite",
203+
"title": "SQLite"
204+
},
205+
{
206+
"value": "transactsql",
207+
"title": "SQL Server Transact-SQL"
208+
},
209+
{
210+
"value": "trino",
211+
"title": "Trino / Presto"
212+
}
148213
],
149214
"required": false
150215
}

‎src/format-sql-output.tsx‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Detail } from "@raycast/api";
2+
import { usePromise } from "@raycast/utils";
3+
import { useRef } from "react";
4+
import { formatSQL } from "./format-sql";
5+
6+
import { copyToClipboard, readFromClipboard, wrapWithCodeblock } from "./utils";
7+
8+
const wrapAsSQL = wrapWithCodeblock("sql");
9+
10+
const wrapAsPlain = wrapWithCodeblock();
11+
12+
const hasContent = (input?: string) => {
13+
return input && 0 < input.trim().length;
14+
};
15+
16+
const dividers = [/\n(?=[┌╔])/, /(?<=;)\n/];
17+
18+
type Parts = readonly [string, string];
19+
20+
const splitInput = (input: string): Parts | undefined => {
21+
for (const divider of dividers) {
22+
const [sql, output] = input.split(divider, 2);
23+
if (hasContent(sql) && hasContent(output)) {
24+
return [sql, output] as const;
25+
}
26+
}
27+
};
28+
29+
const safeFormatSQL = async (input: string): Promise<string> => {
30+
try {
31+
return await formatSQL(input);
32+
} catch {
33+
return input;
34+
}
35+
};
36+
37+
const pipeline = async (): Promise<string> => {
38+
const input = await readFromClipboard();
39+
40+
const result = splitInput(input);
41+
if (!result) throw new Error("Invalid input");
42+
const [sql, output] = result;
43+
44+
const outputPlain = wrapAsPlain(output.trim());
45+
46+
const formattedSQL = await safeFormatSQL(sql.trim());
47+
48+
const clipboard = [wrapAsPlain(formattedSQL), outputPlain].join("\n");
49+
await copyToClipboard(clipboard);
50+
51+
return [wrapAsSQL(formattedSQL), outputPlain].join("\n");
52+
};
53+
54+
function SQLOutputFormat() {
55+
const abortable = useRef<AbortController>();
56+
57+
const { isLoading, data: markdown } = usePromise(pipeline, [], { abortable });
58+
59+
return <Detail isLoading={isLoading} markdown={markdown} />;
60+
}
61+
62+
export default () => <SQLOutputFormat />;

‎src/format-sql.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { Format } from "./format";
44
import { getPreferences } from "./preferences";
55
import { Formatter } from "./types";
66

7-
const { sqlDialect } = getPreferences();
7+
export const formatSQL: Formatter = async (input) => {
8+
const { sqlDialect } = getPreferences();
89

9-
const formatter: Formatter = async (input) => {
1010
return format(input, {
1111
language: sqlDialect,
1212
functionCase: "upper",
@@ -15,4 +15,4 @@ const formatter: Formatter = async (input) => {
1515
});
1616
};
1717

18-
export default () => <Format language="sql" formatter={formatter} />;
18+
export default () => <Format language="sql" formatter={formatSQL} />;

‎src/format.tsx‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@ import { Detail } from "@raycast/api";
22
import { usePromise } from "@raycast/utils";
33
import { useRef } from "react";
44

5-
import { pipeline } from "./pipeline";
65
import { Formatter, Language } from "./types";
6+
import { copyToClipboard, readFromClipboard, wrapWithCodeblock } from "./utils";
77

8-
type FormatProps = {
8+
type PipelineArgs = {
99
language: Language;
1010
formatter: Formatter;
1111
};
1212

13-
export function Format({ language, formatter }: FormatProps) {
13+
const pipeline = async (args: PipelineArgs): Promise<string> => {
14+
const { language, formatter } = args;
15+
16+
return readFromClipboard().then(formatter).then(copyToClipboard).then(wrapWithCodeblock(language));
17+
};
18+
19+
type FormatProps = PipelineArgs;
20+
21+
export function Format(props: FormatProps) {
1422
const abortable = useRef<AbortController>();
1523

16-
const { isLoading, data: markdown } = usePromise(pipeline, [language, formatter], { abortable });
24+
const { isLoading, data: markdown } = usePromise(pipeline, [props], { abortable });
1725

1826
return <Detail isLoading={isLoading} markdown={markdown} />;
1927
}

‎src/pipeline.ts‎

Lines changed: 0 additions & 18 deletions
This file was deleted.

‎src/prettier.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { format, BuiltInParserName } from "prettier";
1+
import { BuiltInParserName, format } from "prettier";
22

33
import { Format } from "./format";
44
import { getPreferences } from "./preferences";

‎src/utils.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Clipboard } from "@raycast/api";
2+
3+
import { Language } from "./types";
4+
5+
export const readFromClipboard = async (): Promise<string> => (await Clipboard.readText()) || "";
6+
7+
export const copyToClipboard = async (code: string): Promise<string> => {
8+
await Clipboard.copy(code);
9+
return code;
10+
};
11+
12+
export const wrapWithCodeblock = (language?: Language) => {
13+
return (code: string) => ["```", language ? ` ${language}` : "", "\n", code, "\n", "```"].join("");
14+
};

0 commit comments

Comments
 (0)