Skip to content

Commit d03970b

Browse files
shortcutsmillotpalgolia-bot
authored
docs: generate guides from snippets (#3139)
Co-authored-by: Pierre Millot <[email protected]> Co-authored-by: algolia-bot <[email protected]>
1 parent 6470b87 commit d03970b

File tree

59 files changed

+912
-4318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+912
-4318
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ generators/bin
3434

3535
**/java/bin/
3636
**/kotlin/bin/
37+
.kotlin/
3738

3839
# Dart
3940
.dart_tool/

generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void processOpts() {
6060
if (mode.equals("tests")) {
6161
ctsManager.addTestsSupportingFiles(supportingFiles);
6262

63-
testsGenerators.add(new TestsRequest(language, client));
63+
testsGenerators.add(new TestsRequest(language, client, false));
6464
testsGenerators.add(new TestsClient(language, client));
6565
} else if (mode.equals("snippets")) {
6666
ctsManager.addSnippetsSupportingFiles(supportingFiles);

generators/src/main/java/com/algolia/codegen/cts/snippets/SnippetsGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
public class SnippetsGenerator extends TestsRequest {
1212

1313
public SnippetsGenerator(String language, String client) {
14-
super(language, client);
14+
super(language, client, true);
1515
}
1616

1717
@Override

generators/src/main/java/com/algolia/codegen/cts/tests/Request.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
public class Request {
1313

1414
public String testName;
15+
public boolean isSnippet;
1516

1617
public Map<String, Object> parameters;
1718
public RequestOptions requestOptions;
@@ -23,6 +24,7 @@ public String toString() {
2324
StringBuilder sb = new StringBuilder();
2425
sb.append("class Request {\n");
2526
sb.append(" testName: ").append(testName).append("\n");
27+
sb.append(" isSnippet").append(isSnippet).append("\n");
2628
sb.append(" parameters: ").append(parameters).append("\n");
2729
sb.append(" requestOptions: ").append(requestOptions).append("\n");
2830
sb.append(" request: ").append(request).append("\n");

generators/src/main/java/com/algolia/codegen/cts/tests/TestsRequest.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313

1414
public class TestsRequest extends TestsGenerator {
1515

16-
public TestsRequest(String language, String client) {
16+
private final boolean withSnippets;
17+
18+
public TestsRequest(String language, String client, boolean withSnippets) {
1719
super(language, client);
20+
this.withSnippets = withSnippets;
1821
}
1922

2023
protected Map<String, Request[]> loadRequestCTS() throws Exception {
@@ -84,13 +87,14 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
8487
}
8588
Request[] op = cts.get(operationId);
8689

87-
List<Object> tests = new ArrayList<>();
90+
List<Map<String, Object>> tests = new ArrayList<>();
8891
for (int i = 0; i < op.length; i++) {
8992
Map<String, Object> test = new HashMap<>();
9093
Request req = op[i];
9194
test.put("method", operationId);
92-
test.put("testName", req.testName == null ? operationId + i : req.testName);
93-
test.put("testIndex", i);
95+
test.put("testName", req.testName == null ? operationId : req.testName);
96+
test.put("testIndex", i == 0 ? "" : i);
97+
test.put("isSnippet", req.isSnippet);
9498
if (ope.returnType != null && ope.returnType.length() > 0) {
9599
test.put("returnType", camelize(ope.returnType));
96100
}
@@ -174,9 +178,20 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
174178
testObj.put("tests", tests);
175179
testObj.put("operationId", operationId);
176180

177-
Map<String, Object> snippet = (Map<String, Object>) tests.get(0);
178-
snippet.put("description", snippet.get("testName"));
179-
testObj.put("snippet", snippet);
181+
if (withSnippets) {
182+
List<Map<String, Object>> snippets = tests.stream().filter(t -> (boolean) t.getOrDefault("isSnippet", false)).toList();
183+
if (snippets.size() == 0) {
184+
Map<String, Object> snippet = tests.get(0);
185+
snippet.put("description", snippet.get("testName"));
186+
snippet.put("testName", "default");
187+
snippets = List.of(snippet);
188+
} else {
189+
for (Map<String, Object> snippet : snippets) {
190+
snippet.put("description", snippet.get("testName"));
191+
}
192+
}
193+
testObj.put("snippets", snippets);
194+
}
180195

181196
blocks.add(testObj);
182197
}

scripts/.eslintrc.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,7 @@ module.exports = {
4141
},
4242
],
4343
'@typescript-eslint/sort-type-union-intersection-members': 0,
44+
'no-param-reassign': 0,
45+
'@typescript-eslint/consistent-type-assertions': 0,
4446
},
4547
};

scripts/buildClients.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export async function buildClients(generators: Generator[]): Promise<void> {
5353
const generatorsMap = generators.reduce(
5454
(map, gen) => {
5555
if (!(gen.language in map)) {
56-
// eslint-disable-next-line no-param-reassign
5756
map[gen.language] = [];
5857
}
5958

scripts/buildSpecs.ts

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import yaml from 'js-yaml';
55
import { Cache } from './cache.js';
66
import { GENERATORS, capitalize, createClientName, exists, run, toAbsolutePath } from './common.js';
77
import { createSpinner } from './spinners.js';
8-
import type { CodeSamples, Language, SnippetSamples, Spec } from './types.js';
8+
import type { CodeSamples, Language, SnippetForMethod, SnippetSamples, Spec } from './types.js';
99

1010
const ALGOLIASEARCH_LITE_OPERATIONS = ['search', 'customPost'];
1111

@@ -22,6 +22,38 @@ function getCodeSampleLabel(language: Language): CodeSamples['label'] {
2222
}
2323
}
2424

25+
// Iterates over the snippet samples and sanitize the data to only keep the method part in order to use it in the guides.
26+
function transformCodeSamplesToGuideMethods(snippetSamples: SnippetSamples): string {
27+
for (const [language, operationWithSample] of Object.entries(snippetSamples)) {
28+
for (const [operation, samples] of Object.entries(operationWithSample)) {
29+
if (operation === 'import') {
30+
continue;
31+
}
32+
33+
for (const [sampleName, sample] of Object.entries(samples)) {
34+
const sampleMatch = sample.match(
35+
/.*Initialize the client\n(.*)((.|\n)*)(.*Call the API\n)((.|\n)*)/,
36+
);
37+
if (!sampleMatch) {
38+
continue;
39+
}
40+
41+
const initLine = sampleMatch[1];
42+
const callLine = sampleMatch[5];
43+
44+
if (!('init' in snippetSamples[language])) {
45+
snippetSamples[language].init = {
46+
default: initLine.replace(/\n$/, ''),
47+
};
48+
}
49+
snippetSamples[language][operation][sampleName] = callLine.replace(/\n$/, '');
50+
}
51+
}
52+
}
53+
54+
return JSON.stringify(snippetSamples, null, 2);
55+
}
56+
2557
// For a given `clientName`, reads the matching snippet file for every available clients and builds an hashmap of snippets per operationId per language.
2658
async function transformSnippetsToCodeSamples(clientName: string): Promise<SnippetSamples> {
2759
const snippetSamples = Object.values(GENERATORS).reduce(
@@ -42,26 +74,40 @@ async function transformSnippetsToCodeSamples(clientName: string): Promise<Snipp
4274
'utf8',
4375
);
4476

77+
const importMatch = snippetFileContent.match(/>IMPORT\n([\s\S]*?)\n.*IMPORT</);
78+
if (importMatch) {
79+
snippetSamples[gen.language].import = {
80+
default: importMatch[1].replace(/\n$/, ''),
81+
};
82+
}
83+
4584
// iterate over every matches (operationId) and store it in the hashmap for later use
46-
for (const match of snippetFileContent.matchAll(/>SEPARATOR (.+)\n([\s\S]*?)SEPARATOR</g)) {
85+
for (const match of snippetFileContent.matchAll(
86+
/>SEPARATOR (\w+) (.*)\n([\s\S]*?)SEPARATOR</g,
87+
)) {
4788
const lines: string[] = match[0].split('\n').slice(1, -1);
4889
if (!lines.length) {
4990
throw new Error(`No snippet found for ${gen.language} ${gen.client}`);
5091
}
5192

52-
if (!snippetSamples[gen.language]) {
53-
snippetSamples[gen.language] = {};
93+
const operationId = match[1];
94+
const testName = match[2] || 'default';
95+
96+
if (!snippetSamples[gen.language][operationId]) {
97+
snippetSamples[gen.language][operationId] = {};
5498
}
5599

56-
snippetSamples[gen.language][match[1]] = '';
100+
const snippetForMethod: SnippetForMethod = snippetSamples[gen.language][operationId];
101+
102+
snippetForMethod[testName] = '';
57103

58104
const indent = lines[0].length - lines[0].trim().length;
59105
// skip first and last lines because they contain the SEPARATOR or operationId
60106
lines.forEach((line) => {
61107
// best effort to determine how far the snippet is indented so we
62108
// can have every snippets in the documentation on the far left
63109
// without impacting the formatting
64-
snippetSamples[gen.language][match[1]] += `${line.slice(indent).replaceAll(/\t/g, ' ')}\n`;
110+
snippetForMethod[testName] += `${line.slice(indent).replaceAll(/\t/g, ' ')}\n`;
65111
});
66112
}
67113
}
@@ -94,7 +140,16 @@ async function transformBundle({
94140

95141
const bundledSpec = yaml.load(await fsp.readFile(bundledPath, 'utf8')) as Spec;
96142
const tagsDefinitions = bundledSpec.tags;
97-
const snippetSamples = docs ? await transformSnippetsToCodeSamples(clientName) : {};
143+
const snippetSamples = docs
144+
? await transformSnippetsToCodeSamples(clientName)
145+
: ({} as SnippetSamples);
146+
147+
if (docs) {
148+
await fsp.writeFile(
149+
toAbsolutePath(`website/src/generated/${clientName}-snippets.js`),
150+
`export const snippets = ${transformCodeSamplesToGuideMethods(JSON.parse(JSON.stringify(snippetSamples)))}`,
151+
);
152+
}
98153

99154
for (const [pathKey, pathMethods] of Object.entries(bundledSpec.paths)) {
100155
for (const [method, specMethod] of Object.entries(pathMethods)) {
@@ -120,11 +175,13 @@ async function transformBundle({
120175
specMethod['x-codeSamples'] = [];
121176
}
122177

123-
specMethod['x-codeSamples'].push({
124-
lang: gen.language,
125-
label: getCodeSampleLabel(gen.language),
126-
source: snippetSamples[gen.language][specMethod.operationId],
127-
});
178+
if (snippetSamples[gen.language][specMethod.operationId]) {
179+
specMethod['x-codeSamples'].push({
180+
lang: gen.language,
181+
label: getCodeSampleLabel(gen.language),
182+
source: Object.values(snippetSamples[gen.language][specMethod.operationId])[0],
183+
});
184+
}
128185
}
129186

130187
if (!bundledSpec.paths[pathKey][method].tags) {

scripts/ci/githubActions/setRunVariables.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export const DEPENDENCIES = LANGUAGES.reduce(
5656
const key = `${lang.toUpperCase()}_CLIENT_CHANGED`;
5757
const langFolder = getLanguageFolder(lang);
5858

59-
// eslint-disable-next-line no-param-reassign
6059
finalDependencies[key] = [
6160
':!**node_modules',
6261
`templates/${lang}`,

scripts/common.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export const GENERATORS = Object.entries(clientsConfig).reduce(
4747
clientName = client;
4848
}
4949

50-
// eslint-disable-next-line no-param-reassign
5150
current[key] = {
5251
additionalProperties: {},
5352
...gen,
@@ -59,7 +58,6 @@ export const GENERATORS = Object.entries(clientsConfig).reduce(
5958

6059
// guess the package name for js from the output folder variable
6160
if (language === 'javascript') {
62-
// eslint-disable-next-line no-param-reassign
6361
current[key].additionalProperties.packageName = output.substring(
6462
output.lastIndexOf('/') + 1,
6563
);

scripts/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ export type CodeSamples = {
124124
source: string;
125125
};
126126

127-
export type SnippetSamples = Record<Language, Record<string, string>>;
127+
export type SnippetForMethod = Record<string, string>;
128+
export type SnippetForLanguage = Record<string, SnippetForMethod>;
129+
export type SnippetSamples = Record<Language, SnippetForLanguage>;
128130

129131
/**
130132
* Paths of a spec.

scripts/website/build.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
touch website/yarn.lock
55

66
# Generate the file with constants
7-
mkdir -p website/src/generated
87
cat config/clients.config.json | jq -r '"export const versions = \(map_values(.packageVersion))"' > website/src/generated/variables.js
98

109
# install website deps and build

snippets/kotlin/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ configure<SpotlessExtension> {
2727
"ktlint_standard_no-wildcard-imports" to "disabled",
2828
"ktlint_standard_trailing-comma-on-declaration-site" to "disabled",
2929
"ktlint_standard_filename" to "disabled",
30+
"ktlint_standard_import-ordering" to "disabled",
3031
),
3132
)
3233
}

snippets/ruby/Gemfile.lock

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ GEM
1212
ast (2.4.2)
1313
connection_pool (2.4.1)
1414
dotenv (3.1.2)
15-
faraday (2.9.0)
15+
faraday (2.9.1)
1616
faraday-net_http (>= 2.0, < 3.2)
1717
faraday-net_http (3.1.0)
1818
net-http
@@ -26,15 +26,16 @@ GEM
2626
net-http-persistent (4.0.2)
2727
connection_pool (~> 2.2)
2828
parallel (1.24.0)
29-
parser (3.3.1.0)
29+
parser (3.3.2.0)
3030
ast (~> 2.4.1)
3131
racc
32-
racc (1.7.3)
32+
racc (1.8.0)
3333
rainbow (3.1.1)
3434
rake (13.2.1)
35-
regexp_parser (2.9.1)
35+
regexp_parser (2.9.2)
3636
rexml (3.2.8)
37-
rubocop (1.63.5)
37+
strscan (>= 3.0.9)
38+
rubocop (1.64.1)
3839
json (~> 2.3)
3940
language_server-protocol (>= 3.17.0)
4041
parallel (~> 1.10)
@@ -48,10 +49,12 @@ GEM
4849
rubocop-ast (1.31.3)
4950
parser (>= 3.3.1.0)
5051
ruby-progressbar (1.13.0)
52+
strscan (3.1.0)
5153
unicode-display_width (2.5.0)
5254
uri (0.13.0)
5355

5456
PLATFORMS
57+
aarch64-linux
5558
x86_64-linux
5659

5760
DEPENDENCIES

specs/common/parameters.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ EndDateRequired:
6363
ForwardToReplicas:
6464
in: query
6565
name: forwardToReplicas
66+
required: false
6667
description: Whether changes are applied to replica indices.
6768
schema:
6869
type: boolean
@@ -73,6 +74,7 @@ Page:
7374
description: |
7475
Requested page of the API response.
7576
If `null`, the API response is not paginated.
77+
required: false
7678
schema:
7779
oneOf:
7880
- type: integer
@@ -97,6 +99,7 @@ HitsPerPage:
9799
in: query
98100
name: hitsPerPage
99101
description: Number of hits per page.
102+
required: false
100103
schema:
101104
type: integer
102105
default: 100
@@ -105,6 +108,7 @@ Offset:
105108
in: query
106109
name: offset
107110
description: Position of the first item to return.
111+
required: false
108112
schema:
109113
type: integer
110114
default: 0
@@ -114,6 +118,7 @@ Limit:
114118
in: query
115119
name: limit
116120
description: Number of items to return.
121+
required: false
117122
schema:
118123
type: integer
119124
default: 10

0 commit comments

Comments
 (0)