Skip to content

Commit 804fa23

Browse files
authored
cmd/starlet: improve documentation generation (#97)
1 parent afeb584 commit 804fa23

File tree

9 files changed

+282
-103
lines changed

9 files changed

+282
-103
lines changed

cmd/starlet/internal/bot/doc.md

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,58 @@ Reads the content of a file.
4848

4949
## `gemini`
5050

51-
A module for interacting with the Google Gemini API.
51+
This module provides a single function, generate_content, which uses the
52+
Gemini API to generate text, optionally with an image as context.
53+
54+
It accepts the following keyword arguments:
55+
56+
- model (str): The name of the model to use for generation (e.g., "gemini-1.5-flash").
57+
- contents (list of (str, str) tuples): A list of (role, text) tuples representing
58+
the conversation history. Valid roles are typically "user" and "model".
59+
- image (bytes, optional): The raw bytes of an image to include. The image is
60+
inserted as a new part just before the last part of the 'contents'.
61+
This is useful for multimodal prompts (e.g., asking a question about an image).
62+
- system_instructions (str, optional): System instructions to guide Gemini's response.
63+
- unsafe (bool, optional): If set to true, disables all safety settings for the
64+
content generation, allowing potentially harmful content. Use with caution.
65+
66+
For example, for a text-only prompt:
67+
68+
responses = gemini.generate_content(
69+
model="gemini-1.5-flash",
70+
contents=[
71+
("user", "Once upon a time,"),
72+
("model", "there was a brave knight."),
73+
("user", "What happened next?")
74+
],
75+
system_instructions="You are a creative story writer. Write a short story based on the provided prompt."
76+
)
77+
78+
To ask a question about an image:
79+
80+
image_data = ... # read image file content as bytes
81+
responses = gemini.generate_content(
82+
model="gemini-1.5-flash",
83+
contents=[
84+
("user", "Describe this image in detail.")
85+
],
86+
image=image_data
87+
)
88+
89+
The responses variable will contain a list of generated responses, where each response
90+
is a list of strings representing the parts of the generated content.
5291

5392
## `kvcache`
5493

55-
A module for caching key-value pairs.
94+
This module provides two functions:
95+
96+
- get(key: str) -> value | None: Retrieves the value associated with the
97+
given string key. Returns the stored value if the key exists and has
98+
not expired. Returns None if the key is not found or if the entry
99+
has expired. Accessing a key resets its TTL timer.
100+
- set(key: str, value: any): Stores the given value under the specified
101+
string key. Any existing value for the key is overwritten. Storing a
102+
value resets the TTL timer for that key.
56103

57104
## `markdown`
58105

@@ -64,16 +111,45 @@ Converts a Markdown string to a Telegram message struct.
64111

65112
## `module()`
66113

67-
Creates a new Starlark module from a dictionary.
114+
Instantiates a module struct with the name from the specified keyword arguments.
68115

69116
## `struct()`
70117

71-
Creates a new Starlark struct from a dictionary.
118+
Instantiates an immutable struct from the specified keyword arguments.
72119

73120
## `telegram`
74121

75-
A module for interacting with the Telegram Bot API.
122+
This module provides two functions: call and get_file.
123+
124+
# call
125+
126+
The call function takes two arguments:
127+
128+
- method (string): The Telegram Bot API method to call.
129+
- args (dict): The arguments to pass to the method.
130+
131+
For example, to send a message to a chat:
132+
133+
response = telegram.call(
134+
method="sendMessage",
135+
args={
136+
"chat_id": 123456789,
137+
"text": "Hello, world!",
138+
}
139+
)
140+
141+
The response variable will contain the response from the Telegram Bot API.
142+
143+
# get_file
144+
145+
The get_file function takes one argument:
146+
147+
- file_id (string): The ID of the file to download.
148+
149+
It returns the content of the file as bytes. For example:
150+
151+
file_content = telegram.get_file(file_id="...")
76152

77153
## `time`
78154

79-
A module for time-related functions.
155+
A module for time-related functions. See https://pkg.go.dev/go.starlark.net/lib/time#Module.

cmd/starlet/internal/bot/env.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import (
1212

1313
"go.astrophena.name/base/version"
1414
"go.astrophena.name/tools/internal/starlark/go2star"
15-
starlarkgemini "go.astrophena.name/tools/internal/starlark/lib/gemini"
16-
starlarktelegram "go.astrophena.name/tools/internal/starlark/lib/telegram"
15+
"go.astrophena.name/tools/internal/starlark/lib/gemini"
16+
"go.astrophena.name/tools/internal/starlark/lib/kvcache"
17+
"go.astrophena.name/tools/internal/starlark/lib/telegram"
1718
"go.astrophena.name/tools/internal/util/tgmarkup"
1819

1920
starlarktime "go.starlark.net/lib/time"
@@ -77,7 +78,7 @@ func (d Environment) render(b *strings.Builder, level int, prefix string) {
7778
}
7879
b.WriteString("`\n\n")
7980

80-
b.WriteString(m.Doc)
81+
b.WriteString(strings.TrimSpace(m.Doc))
8182
b.WriteString("\n\n")
8283

8384
if len(m.Members) > 0 {
@@ -148,12 +149,12 @@ func (b *Bot) environment() Environment {
148149
},
149150
{
150151
Name: "gemini",
151-
Doc: "A module for interacting with the Google Gemini API.",
152-
Value: starlarkgemini.Module(b.geminic),
152+
Doc: gemini.Documentation(),
153+
Value: gemini.Module(b.geminic),
153154
},
154155
{
155156
Name: "kvcache",
156-
Doc: "A module for caching key-value pairs.",
157+
Doc: kvcache.Documentation(),
157158
Value: b.kvCache,
158159
},
159160
{
@@ -169,22 +170,22 @@ func (b *Bot) environment() Environment {
169170
},
170171
{
171172
Name: "module",
172-
Doc: "Creates a new Starlark module from a dictionary.",
173+
Doc: "Instantiates a module struct with the name from the specified keyword arguments.",
173174
Value: starlark.NewBuiltin("module", starlarkstruct.MakeModule),
174175
},
175176
{
176177
Name: "struct",
177-
Doc: "Creates a new Starlark struct from a dictionary.",
178+
Doc: "Instantiates an immutable struct from the specified keyword arguments.",
178179
Value: starlark.NewBuiltin("struct", starlarkstruct.Make),
179180
},
180181
{
181182
Name: "telegram",
182-
Doc: "A module for interacting with the Telegram Bot API.",
183-
Value: starlarktelegram.Module(b.tgToken, b.httpc),
183+
Doc: telegram.Documentation(),
184+
Value: telegram.Module(b.tgToken, b.httpc),
184185
},
185186
{
186187
Name: "time",
187-
Doc: "A module for time-related functions.",
188+
Doc: "A module for time-related functions. See https://pkg.go.dev/go.starlark.net/lib/time#Module.",
188189
Value: starlarktime.Module,
189190
},
190191
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// © 2025 Ilya Mateyko. All rights reserved.
2+
// Use of this source code is governed by the ISC
3+
// license that can be found in the LICENSE.md file.
4+
5+
/*
6+
Package gemini contains a Starlark module that exposes Gemini API.
7+
8+
This module provides a single function, generate_content, which uses the
9+
Gemini API to generate text, optionally with an image as context.
10+
11+
It accepts the following keyword arguments:
12+
13+
- model (str): The name of the model to use for generation (e.g., "gemini-1.5-flash").
14+
- contents (list of (str, str) tuples): A list of (role, text) tuples representing
15+
the conversation history. Valid roles are typically "user" and "model".
16+
- image (bytes, optional): The raw bytes of an image to include. The image is
17+
inserted as a new part just before the last part of the 'contents'.
18+
This is useful for multimodal prompts (e.g., asking a question about an image).
19+
- system_instructions (str, optional): System instructions to guide Gemini's response.
20+
- unsafe (bool, optional): If set to true, disables all safety settings for the
21+
content generation, allowing potentially harmful content. Use with caution.
22+
23+
For example, for a text-only prompt:
24+
25+
responses = gemini.generate_content(
26+
model="gemini-1.5-flash",
27+
contents=[
28+
("user", "Once upon a time,"),
29+
("model", "there was a brave knight."),
30+
("user", "What happened next?")
31+
],
32+
system_instructions="You are a creative story writer. Write a short story based on the provided prompt."
33+
)
34+
35+
To ask a question about an image:
36+
37+
image_data = ... # read image file content as bytes
38+
responses = gemini.generate_content(
39+
model="gemini-1.5-flash",
40+
contents=[
41+
("user", "Describe this image in detail.")
42+
],
43+
image=image_data
44+
)
45+
46+
The responses variable will contain a list of generated responses, where each response
47+
is a list of strings representing the parts of the generated content.
48+
*/
49+
package gemini
50+
51+
import (
52+
_ "embed"
53+
"sync"
54+
55+
"go.astrophena.name/tools/internal/starlark/lib/internal"
56+
)
57+
58+
//go:embed doc.go
59+
var doc []byte
60+
61+
var Documentation = sync.OnceValue(func() string {
62+
return internal.ParseDocComment(doc)
63+
})

internal/starlark/lib/gemini/gemini.go

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Use of this source code is governed by the ISC
33
// license that can be found in the LICENSE.md file.
44

5-
// Package gemini contains a Starlark module that exposes Gemini API.
65
package gemini
76

87
import (
@@ -18,47 +17,6 @@ import (
1817
)
1918

2019
// Module returns a Starlark module that exposes Gemini API.
21-
//
22-
// This module provides a single function, generate_content, which uses the
23-
// Gemini API to generate text, optionally with an image as context.
24-
//
25-
// It accepts the following keyword arguments:
26-
//
27-
// - model (str): The name of the model to use for generation (e.g., "gemini-1.5-flash").
28-
// - contents (list of (str, str) tuples): A list of (role, text) tuples representing
29-
// the conversation history. Valid roles are typically "user" and "model".
30-
// - image (bytes, optional): The raw bytes of an image to include. The image is
31-
// inserted as a new part just before the last part of the 'contents'.
32-
// This is useful for multimodal prompts (e.g., asking a question about an image).
33-
// - system_instructions (str, optional): System instructions to guide Gemini's response.
34-
// - unsafe (bool, optional): If set to true, disables all safety settings for the
35-
// content generation, allowing potentially harmful content. Use with caution.
36-
//
37-
// For example, for a text-only prompt:
38-
//
39-
// responses = gemini.generate_content(
40-
// model="gemini-1.5-flash",
41-
// contents=[
42-
// ("user", "Once upon a time,"),
43-
// ("model", "there was a brave knight."),
44-
// ("user", "What happened next?")
45-
// ],
46-
// system_instructions="You are a creative story writer. Write a short story based on the provided prompt."
47-
// )
48-
//
49-
// To ask a question about an image:
50-
//
51-
// image_data = ... # read image file content as bytes
52-
// responses = gemini.generate_content(
53-
// model="gemini-1.5-flash",
54-
// contents=[
55-
// ("user", "Describe this image in detail.")
56-
// ],
57-
// image=image_data
58-
// )
59-
//
60-
// The responses variable will contain a list of generated responses, where each response
61-
// is a list of strings representing the parts of the generated content.
6220
func Module(client *gemini.Client) *starlarkstruct.Module {
6321
m := &module{client: client}
6422
return &starlarkstruct.Module{
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// © 2025 Ilya Mateyko. All rights reserved.
2+
// Use of this source code is governed by the ISC
3+
// license that can be found in the LICENSE.md file.
4+
5+
package internal
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"strings"
11+
)
12+
13+
func ParseDocComment(src []byte) string {
14+
s := bufio.NewScanner(bytes.NewReader(src))
15+
var (
16+
doc string
17+
inComment bool
18+
)
19+
for s.Scan() {
20+
line := s.Text()
21+
if line == "/*" {
22+
inComment = true
23+
continue
24+
}
25+
if line == "*/" {
26+
// Comment ended, stop scanning.
27+
break
28+
}
29+
if inComment {
30+
if strings.HasPrefix(s.Text(), "Package") {
31+
continue
32+
}
33+
doc += s.Text() + "\n"
34+
}
35+
}
36+
if err := s.Err(); err != nil {
37+
panic(err)
38+
}
39+
return doc
40+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// © 2025 Ilya Mateyko. All rights reserved.
2+
// Use of this source code is governed by the ISC
3+
// license that can be found in the LICENSE.md file.
4+
5+
/*
6+
Package kvcache implements a Starlark module for a simple key-value cache with time-to-live (TTL) expiration based on last access time.
7+
8+
This module provides two functions:
9+
10+
- get(key: str) -> value | None: Retrieves the value associated with the
11+
given string key. Returns the stored value if the key exists and has
12+
not expired. Returns None if the key is not found or if the entry
13+
has expired. Accessing a key resets its TTL timer.
14+
- set(key: str, value: any): Stores the given value under the specified
15+
string key. Any existing value for the key is overwritten. Storing a
16+
value resets the TTL timer for that key.
17+
*/
18+
package kvcache
19+
20+
import (
21+
_ "embed"
22+
"sync"
23+
24+
"go.astrophena.name/tools/internal/starlark/lib/internal"
25+
)
26+
27+
//go:embed doc.go
28+
var doc []byte
29+
30+
var Documentation = sync.OnceValue(func() string {
31+
return internal.ParseDocComment(doc)
32+
})

internal/starlark/lib/kvcache/kvcache.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Use of this source code is governed by the ISC
33
// license that can be found in the LICENSE.md file.
44

5-
// Package kvcache implements a Starlark module for a simple key-value cache
6-
// with time-to-live (TTL) expiration based on last access time.
75
package kvcache
86

97
import (
@@ -19,16 +17,6 @@ import (
1917

2018
// Module returns a Starlark module that exposes key-value caching functionality.
2119
//
22-
// This module provides two functions:
23-
//
24-
// - get(key: str) -> value | None: Retrieves the value associated with the
25-
// given string key. Returns the stored value if the key exists and has
26-
// not expired. Returns None if the key is not found or if the entry
27-
// has expired. Accessing a key resets its TTL timer.
28-
// - set(key: str, value: any): Stores the given value under the specified
29-
// string key. Any existing value for the key is overwritten. Storing a
30-
// value resets the TTL timer for that key.
31-
//
3220
// The ttl argument specifies the time-to-live duration. A cache entry will
3321
// expire if it hasn't been accessed (via get) or updated (via set) for
3422
// longer than this duration.

0 commit comments

Comments
 (0)