Skip to content

Commit aa23ce4

Browse files
authored
new approach for snippet injection (googleapis#2838)
The current snippet injector does not work properly with google-java-format, because GJF formats short javadoc comments on one line, eg "/** comment */". However, the injector script looks for "/**" on a line by itself. The script will also not work if/when we move to Java 8, due to lack of parser support. This PR takes a different approach of not caring about Java syntax and copy-paste everything in the SNIPPET block. While less powerful, it is more robust. As written, the script is also easier to use. There's no need to tell it what file contains snippets and where to copy the snippets to. The script recursively scan given directories. Updates googleapis#2413. * license * Add test case for getSnip * Add support for cloud region tags to snippet.go
1 parent b6d45df commit aa23ce4

File tree

6 files changed

+455
-48
lines changed

6 files changed

+455
-48
lines changed

google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -753,13 +753,19 @@ public int hashCode() {
753753
* Updates dataset information.
754754
*
755755
* <p>Example of updating a dataset by changing its description.
756-
* <pre> {@code
757-
* String datasetName = "my_dataset_name";
758-
* String newDescription = "some_new_description";
759-
* Dataset oldDataset = bigquery.getDataset(datasetName);
760-
* DatasetInfo datasetInfo = oldDataset.toBuilder().setDescription(newDescription).build();
761-
* Dataset newDataset = bigquery.update(datasetInfo);
762-
* }</pre>
756+
* <!--SNIPPET bigquery_update_table_description-->
757+
* <pre>{@code
758+
* // String datasetName = "my_dataset_name";
759+
* // String tableName = "my_table_name";
760+
* // String newDescription = "new_description";
761+
*
762+
* Table beforeTable = bigquery.getTable(datasetName, tableName);
763+
* TableInfo tableInfo = beforeTable.toBuilder()
764+
* .setDescription(newDescription)
765+
* .build();
766+
* Table afterTable = bigquery.update(tableInfo);
767+
* }</pre>
768+
* <!--SNIPPET bigquery_update_table_description-->
763769
*
764770
* @throws BigQueryException upon failure
765771
*/
@@ -785,7 +791,7 @@ public int hashCode() {
785791
* String datasetName = "my_dataset_name";
786792
* String tableName = "my_table_name";
787793
* Table beforeTable = bigquery.getTable(datasetName, tableName);
788-
*
794+
*
789795
* // Set table to expire 5 days from now.
790796
* long expirationMillis = DateTime.now().plusDays(5).getMillis();
791797
* TableInfo tableInfo = beforeTable.toBuilder()
@@ -1104,7 +1110,7 @@ TableResult listTableData(
11041110
* // BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();
11051111
* String query = "SELECT corpus FROM `bigquery-public-data.samples.shakespeare` GROUP BY corpus;";
11061112
* QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(query).build();
1107-
*
1113+
*
11081114
* // Print the results.
11091115
* for (FieldValueList row : bigquery.query(queryConfig).iterateAll()) {
11101116
* for (FieldValue val : row) {

google-cloud-clients/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/MessageReceiver.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,31 @@
2222
public interface MessageReceiver {
2323
/**
2424
* Called when a message is received by the subscriber. The implementation must arrange for {@link
25-
* AckReplyConsumer#ack()} or {@link
26-
* AckReplyConsumer#nack()} to be called after processing the {@code message}.
25+
* AckReplyConsumer#ack()} or {@link AckReplyConsumer#nack()} to be called after processing the
26+
* {@code message}.
27+
* <!--SNIPPET receiveMessage-->
2728
*
28-
* <p>This {@code MessageReceiver} passes all messages to a {@code BlockingQueue}.
29-
* This method can be called concurrently from multiple threads,
30-
* so it is important that the queue be thread-safe.
29+
* <pre>{@code
30+
* // This {@code MessageReceiver} passes all messages to a {@link BlockingQueue}. This method can
31+
* // be called concurrently from multiple threads, so it is important that the queue be
32+
* // thread-safe.
33+
* //
34+
* // This example is for illustration. Implementations may directly process messages instead of
35+
* // sending them to queues.
36+
* MessageReceiver receiver =
37+
* new MessageReceiver() {
38+
* public void receiveMessage(final PubsubMessage message, final AckReplyConsumer consumer) {
39+
* if (blockingQueue.offer(message)) {
40+
* consumer.ack();
41+
* } else {
42+
* consumer.nack();
43+
* }
44+
* }
45+
* };
3146
*
32-
* This example is for illustration. Implementations may directly process messages
33-
* instead of sending them to queues.
34-
* <pre> {@code
35-
* MessageReceiver receiver = new MessageReceiver() {
36-
* public void receiveMessage(final PubsubMessage message, final AckReplyConsumer consumer) {
37-
* if (blockingQueue.offer(message)) {
38-
* consumer.ack();
39-
* } else {
40-
* consumer.nack();
41-
* }
42-
* }
43-
* };
4447
* }</pre>
4548
*
49+
* <!--SNIPPET receiveMessage-->
4650
*/
4751
void receiveMessage(final PubsubMessage message, final AckReplyConsumer consumer);
4852
}

google-cloud-examples/src/main/java/com/google/cloud/examples/bigquery/snippets/BigQuerySnippets.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ public Dataset updateDataset(String datasetName, String newDescription) {
121121
/**
122122
* Example of updating a table by changing its description.
123123
*/
124-
// [TARGET update(TableInfo, TableOption...)]
125-
// [VARIABLE "my_dataset_name"]
126-
// [VARIABLE "my_table_name"]
127-
// [VARIABLE "new_description"]
128124
public Table updateTableDescription(String datasetName, String tableName, String newDescription) {
129125
// [START bigquery_update_table_description]
126+
// String datasetName = "my_dataset_name";
127+
// String tableName = "my_table_name";
128+
// String newDescription = "new_description";
129+
130130
Table beforeTable = bigquery.getTable(datasetName, tableName);
131131
TableInfo tableInfo = beforeTable.toBuilder()
132132
.setDescription(newDescription)

google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/MessageReceiverSnippets.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,25 @@ public MessageReceiverSnippets(BlockingQueue<PubsubMessage> blockingQueue) {
3636
this.blockingQueue = blockingQueue;
3737
}
3838

39-
/**
40-
* This {@code MessageReceiver} passes all messages to a {@link BlockingQueue}.
41-
* This method can be called concurrently from multiple threads,
42-
* so it is important that the queue be thread-safe.
43-
*
44-
* This example is for illustration. Implementations may directly process messages
45-
* instead of sending them to queues.
46-
*/
47-
// [TARGET receiveMessage(PubsubMessage, AckReplyConsumer)]
4839
public MessageReceiver messageReceiver() {
49-
MessageReceiver receiver = new MessageReceiver() {
50-
public void receiveMessage(final PubsubMessage message, final AckReplyConsumer consumer) {
51-
if (blockingQueue.offer(message)) {
52-
consumer.ack();
53-
} else {
54-
consumer.nack();
55-
}
56-
}
57-
};
40+
// SNIPPET receiveMessage
41+
// This {@code MessageReceiver} passes all messages to a {@link BlockingQueue}. This method can
42+
// be called concurrently from multiple threads, so it is important that the queue be
43+
// thread-safe.
44+
//
45+
// This example is for illustration. Implementations may directly process messages instead of
46+
// sending them to queues.
47+
MessageReceiver receiver =
48+
new MessageReceiver() {
49+
public void receiveMessage(final PubsubMessage message, final AckReplyConsumer consumer) {
50+
if (blockingQueue.offer(message)) {
51+
consumer.ack();
52+
} else {
53+
consumer.nack();
54+
}
55+
}
56+
};
57+
// SNIPPET receiveMessage
5858
return receiver;
5959
}
6060
}

utilities/snippets.go

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"bytes"
19+
"flag"
20+
"fmt"
21+
"io/ioutil"
22+
"log"
23+
"os"
24+
"path/filepath"
25+
"runtime/pprof"
26+
"strings"
27+
)
28+
29+
func init() {
30+
log.SetFlags(0)
31+
log.SetPrefix("snippet: ")
32+
}
33+
34+
func main() {
35+
cpuprof := flag.String("cpuprofile", "", "write CPU profile to this file")
36+
flag.Parse()
37+
38+
if cp := *cpuprof; cp != "" {
39+
f, err := os.Create(cp)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
defer f.Close()
44+
45+
pprof.StartCPUProfile(f)
46+
defer pprof.StopCPUProfile()
47+
}
48+
49+
files := map[string]string{}
50+
walkFn := func(path string, info os.FileInfo, err error) error {
51+
if err != nil {
52+
return err
53+
}
54+
if !info.Mode().IsRegular() || filepath.Ext(path) != ".java" {
55+
return nil
56+
}
57+
b, err := ioutil.ReadFile(path)
58+
if err != nil {
59+
return err
60+
}
61+
files[path] = string(b)
62+
return nil
63+
}
64+
for _, dir := range flag.Args() {
65+
if err := filepath.Walk(dir, walkFn); err != nil {
66+
log.Fatal(err)
67+
}
68+
}
69+
70+
snip := map[string]string{}
71+
for file, txt := range files {
72+
if err := getSnip(file, txt, snip); err != nil {
73+
log.Fatal(err)
74+
}
75+
if err := getCloud(file, txt, snip); err != nil {
76+
log.Fatal(err)
77+
}
78+
}
79+
80+
rd := rewriteData{
81+
rewrite: map[string]string{},
82+
used: map[string]bool{},
83+
}
84+
for file, txt := range files {
85+
if err := writeSnip(file, txt, snip, rd); err != nil {
86+
log.Fatal(err)
87+
}
88+
}
89+
90+
for file, txt := range rd.rewrite {
91+
if err := ioutil.WriteFile(file, []byte(txt), 0644); err != nil {
92+
log.Fatal(err)
93+
}
94+
}
95+
96+
for key := range snip {
97+
if !rd.used[key] {
98+
log.Printf("unused snippet: %q", key)
99+
}
100+
}
101+
}
102+
103+
func getCloud(file, txt string, snip map[string]string) error {
104+
const cloudPrefix = "// [START "
105+
const cloudSuffix = "// [END %s]"
106+
107+
ftxt := txt
108+
for {
109+
if p := strings.Index(txt, cloudPrefix); p >= 0 {
110+
txt = txt[p:]
111+
} else {
112+
return nil
113+
}
114+
115+
var tag string
116+
if p := strings.Index(txt, "]\n"); p >= 0 {
117+
// "// [START foo]" -> "foo"
118+
tag = txt[10:p]
119+
txt = txt[p+1:]
120+
} else {
121+
tag = txt
122+
txt = ""
123+
}
124+
125+
endTag := fmt.Sprintf(cloudSuffix, tag)
126+
if p := strings.Index(txt, endTag); p >= 0 {
127+
key := fmt.Sprintf("<!--SNIPPET %s-->", tag)
128+
snipTxt := strings.Trim(txt[:p], "\n\r")
129+
if _, exist := snip[key]; exist {
130+
snip[key] = strings.Join([]string{snip[key], snipTxt}, "")
131+
}
132+
133+
snip[key] = snipTxt
134+
txt = txt[p+len(endTag):]
135+
} else {
136+
return fmt.Errorf("[START %s]:%d snippet %q not closed", file, lineNum(ftxt, txt), tag)
137+
}
138+
}
139+
}
140+
141+
func getSnip(file, txt string, snip map[string]string) error {
142+
const snipPrefix = "// SNIPPET "
143+
144+
ftxt := txt
145+
for {
146+
if p := strings.Index(txt, snipPrefix); p >= 0 {
147+
txt = txt[p:]
148+
} else {
149+
return nil
150+
}
151+
152+
var key string
153+
if p := strings.IndexByte(txt, '\n'); p >= 0 {
154+
key = txt[:p]
155+
txt = txt[p:]
156+
} else {
157+
key = txt
158+
txt = ""
159+
}
160+
161+
if p := strings.Index(txt, key); p >= 0 {
162+
// "// SNIPPET foo" -> "<!--SNIPPET foo-->"
163+
key = fmt.Sprintf("<!--%s-->", strings.TrimSpace(key[3:]))
164+
165+
if _, exist := snip[key]; exist {
166+
return fmt.Errorf("%s:%d snippet %q has already been defined", file, lineNum(ftxt, txt), key)
167+
}
168+
169+
snip[key] = strings.Trim(txt[:p], "\n\r")
170+
txt = txt[p+len(snipPrefix):]
171+
} else {
172+
return fmt.Errorf("%s:%d snippet %q not closed", file, lineNum(ftxt, txt), key)
173+
}
174+
}
175+
}
176+
177+
type rewriteData struct {
178+
rewrite map[string]string
179+
used map[string]bool
180+
}
181+
182+
func writeSnip(file, txt string, snip map[string]string, rd rewriteData) error {
183+
const snipPrefix = "<!--SNIPPET "
184+
185+
ftxt := txt
186+
var buf bytes.Buffer
187+
for {
188+
if p := strings.Index(txt, snipPrefix); p >= 0 {
189+
buf.WriteString(txt[:p])
190+
txt = txt[p:]
191+
} else if buf.Len() == 0 {
192+
return nil
193+
} else {
194+
buf.WriteString(txt)
195+
rd.rewrite[file] = buf.String()
196+
return nil
197+
}
198+
199+
var key string
200+
if p := strings.Index(txt, "-->"); p >= 0 {
201+
key = txt[:p+3]
202+
txt = txt[p+3:]
203+
}
204+
205+
rep, ok := snip[key]
206+
if ok {
207+
rd.used[key] = true
208+
} else {
209+
return fmt.Errorf("%s:%d snippet target %q undefined", file, lineNum(ftxt, txt), key)
210+
}
211+
212+
if p := strings.Index(txt, key); p >= 0 {
213+
buf.WriteString(key)
214+
buf.WriteString("\n * <pre>{@code\n *")
215+
buf.WriteString(strings.Replace(rep, "\n", "\n *", -1))
216+
buf.WriteString(" }</pre>\n * ")
217+
buf.WriteString(key)
218+
txt = txt[p+len(key):]
219+
} else {
220+
return fmt.Errorf("%s:%d snippet target %q not closed", file, lineNum(ftxt, txt), key)
221+
}
222+
}
223+
}
224+
225+
// Give strings s and suf where suf is a suffix of s, lineNum reports
226+
// the line number on which suf starts.
227+
func lineNum(s, suf string) int {
228+
pre := s[:len(s)-len(suf)]
229+
return strings.Count(pre, "\n") + 1
230+
}

0 commit comments

Comments
 (0)