launchpadlib 0.1.5

Rust library for accessing Launchpad.net
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
fn guess_type_name(param_name: &str) -> Option<String> {
    match param_name {
        n if n.ends_with("_count") => Some("usize"),
        n if n.ends_with("_url") => Some("url::Url"),
        n if n.starts_with("is_") => Some("bool"),
        "http_etag" => Some("String"),
        "description" => Some("String"),
        "scopes" => Some("Vec<String>"),
        "start" | "total_size" => Some("usize"),
        "entries" => Some("Vec<serde_json::Value>"),
        "component_name" => Some("String"),
        "pocket" => Some("String"),
        "title" => Some("String"),
        "authorized_size" => Some("usize"),
        "display_name" | "displayname" => Some("String"),
        "external_dependencies" => Some("String"),
        "name" => Some("String"),
        "private" => Some("bool"),
        "publish" => Some("bool"),
        "reference" => Some("String"),
        "relative_build_score" => Some("f64"),
        "require_virtualization" => Some("bool"),
        "active" => Some("bool"),
        "address" => Some("String"),
        "advertise_by_hash" => Some("bool"),
        "allow_internet" => Some("bool"),
        "allowspoilt" => Some("bool"),
        "architecture_specific" => Some("bool"),
        "architecture_tag" => Some("String"),
        "arch_tag" => Some("String"),
        "as_quoted_email" => Some("String"),
        "auto_build" => Some("bool"),
        "auto_build_channels" => Some("Vec<String>"),
        "backports_not_automatic" => Some("bool"),
        "base_url_aliases" => Some("Vec<url::Url>"),
        "base_version" => Some("debversion::Version"),
        "binary_package_name" => Some("String"),
        "binary_package_version" => Some("debversion::Version"),
        "branch_name" => Some("String"),
        "body_text" => Some("String"),
        "bug_reported_acknowledgement" => Some("bool"),
        "bug_reporting_guidelines" => Some("String"),
        "bug_target_display_name" => Some("String"),
        "bug_target_name" => Some("String"),
        "build_channels" => Some("Vec<String>"),
        "build_daily" => Some("bool"),
        "build_path" => Some("String"),
        "build_snap_channels" => Some("Vec<String>"),
        "build_source_tarball" => Some("url::Url"),
        "bzr_identity" => Some("String"),
        "can_be_cancelled" => Some("bool"),
        "can_be_rescored" => Some("bool"),
        "can_be_retried" => Some("bool"),
        "can_expire" => Some("bool"),
        "can_infer_distro_series" => Some("bool"),
        "can_upload_to_store" => Some("bool"),
        "changelog" => Some("String"),
        "changeslist" => Some("String"),
        "channels" => Some("Vec<String>"),
        "code" => Some("String"),
        "code_name" => Some("String"),
        "comment" => Some("String"),
        "commercial_subscription_is_due" => Some("bool"),
        "commit_message" => Some("String"),
        "commit_sha1" => Some("String"),
        "component_names" => Some("Vec<String>"),
        "conflicts" => Some("Vec<String>"),
        "contact_details" => Some("String"),
        "content" => Some("String"),
        "content_type" => Some("String"),
        "count" => Some("usize"),
        "country_dns_mirror" => Some("String"),
        "custom_file_urls" => Some("Vec<url::Url>"),
        "cvs_module" => Some("String"),
        "cvs_root" => Some("String"),
        "deb_version_template" => Some("String"),
        "default_branch" => Some("String"),
        "default_membership_period" => Some("usize"),
        "default_renewal_period" => Some("usize"),
        "dependencies" => Some("Vec<String>"),
        "development_series_alias" => Some("String"),
        "diffstat" => Some("String"),
        "display_arches" => Some("Vec<String>"),
        "display_version" => Some("String"),
        "distroseries" => Some("String"),
        "distro_series_name" => Some("String"),
        "domain_name" => Some("String"),
        "email" => Some("String"),
        "enabled" => Some("bool"),
        "english_name" => Some("String"),
        "error_message" => Some("String"),
        "error_output" => Some("String"),
        "event_type" => Some("String"),
        "event_types" => Some("Vec<String>"),
        "explicit" => Some("bool"),
        "explicitly_private" => Some("bool"),
        "exported_in_languagepacks" => Some("bool"),
        "failnotes" => Some("String"),
        "features" => Some("Vec<String>"),
        "filename" | "file_extension" => Some("String"),
        "find_all_tags" => Some("bool"),
        "fingerprint" => Some("String"),
        "freshmeat_project" => Some("String"),
        "fullseriesname" => Some("String"),
        "git_identity" => Some("String"),
        "git_path" => Some("String"),
        "git_ref_pattern" => Some("String"),
        "git_refs" => Some("Vec<String>"),
        t if t.starts_with("has_") => Some("bool"),
        "heat" => Some("f64"),
        "hide_email_addresses" => Some("bool"),
        "homepage_content" => Some("Option<String>"),
        "id" => Some("String"),
        "importances" => Some("Vec<String>"),
        t if t.starts_with("include_") => Some("bool"),
        "index_compressors" => Some("Vec<String>"),
        "information_types" => Some("Vec<String>"),
        "iso3166code2" => Some("String"),
        "iso3166code3" => Some("String"),
        "jabberid" => Some("String"),
        "karma" => Some("i64"),
        "keep_binary_files_days" => Some("usize"),
        "keyid" => Some("String"),
        "keytext" => Some("String"),
        "landmarks" => Some("Vec<String>"),
        "language_pack_full_export_requested" => Some("bool"),
        "last_change_comment" => Some("String"),
        "last_scanned_id" => Some("i64"),
        "latest_published_component_name" => Some("String"),
        "latitude" | "longitude" => Some("Option<f64>"),
        "license_approved" => Some("bool"),
        "license_info" => Some("String"),
        "licenses" => Some("Vec<String>"),
        "manual" => Some("String"),
        "merged_revision_id" => Some("String"),
        "merged_revno" => Some("i64"),
        "message" => Some("String"),
        "message_body" => Some("String"),
        "metadata" => Some("String"),
        "metadata_override" => Some("String"),
        "mirror_status_message" => Some("String"),
        "network" => Some("String"),
        "newvalue" => Some("String"),
        "nickname" => Some("String"),
        "number_of_duplicates" => Some("usize"),
        "official" => Some("bool"),
        "official_answers" => Some("bool"),
        "official_blueprints" => Some("bool"),
        "official_bugs" => Some("bool"),
        "official_bug_tags" => Some("bool"),
        "official_candidate" => Some("bool"),
        "official_codehosting" => Some("bool"),
        "official_packages" => Some("bool"),
        "oldvalue" => Some("String"),
        "open_resources" => Some("bool"),
        "other_users_affected_count_with_dupes" => Some("usize"),
        "owner_default" => Some("bool"),
        "package_set_name" => Some("String"),
        "parent_source_version" => Some("String"),
        "path" => Some("String"),
        "payload" => Some("String"),
        "pending" => Some("bool"),
        "permission" => Some("String"),
        "phased_update_percentage" => Some("usize"),
        "plural_expression" => Some("String"),
        "plural_forms" => Some("String"),
        "prerequisite_git_path" => Some("String"),
        "prerequisite_revision_id" => Some("String"),
        "priority" => Some("String"),
        "priority_name" => Some("String"),
        "private_bugs" => Some("bool"),
        "programming_lang" | "programming_language" => Some("String"),
        "project_reviewed" => Some("bool"),
        "properties" => Some("Vec<String>"),
        "proposed_not_automatic" => Some("bool"),
        "proposition" => Some("String"),
        "publish_by_hash" => Some("bool"),
        "qualifies_for_free_hosting" => Some("bool"),
        "recipe_text" => Some("String"),
        "redirect_default_traversal" => Some("String"),
        "redirect_release_uploads" => Some("bool"),
        "release_finder_url_pattern" => Some("String"),
        "release_notes" => Some("String"),
        "remote_bug" => Some("String"),
        "remote_importance" => Some("String"),
        "remote_product" => Some("String"),
        "remote_status" => Some("String"),
        "removal_comment" => Some("String"),
        "require_virtualized" => Some("bool"),
        "restricted_resources" => Some("bool"),
        "results" => Some("Vec<String>"),
        "result_summary" => Some("String"),
        "reviewed" => Some("bool"),
        "reviewed_revid" => Some("String"),
        "reviewer_whiteboard" => Some("String"),
        "review_type" => Some("String"),
        "revision_id" => Some("String"),
        "score" => Some("f64"),
        "section_name" => Some("String"),
        "security_contact" => Some("String"),
        "security_related" => Some("bool"),
        "sequence" => Some("usize"),
        "signing_key_fingerprint" => Some("String"),
        "sourceforge_project" => Some("String"),
        "source_git_path" => Some("String"),
        "source_package_name" => Some("String"),
        "sourcepackagename" => Some("String"),
        "source_package_version" => Some("debversion::Version"),
        "source_revision_id" => Some("String"),
        "source_version" => Some("String"),
        "stages" => Some("Vec<String>"),
        "stale" => Some("bool"),
        "statuses" => Some("Vec<String>"),
        "store_channels" => Some("Vec<String>"),
        "store_name" => Some("String"),
        "store_upload" => Some("String"),
        "store_upload_error_message" => Some("String"),
        "store_upload_error_messages" => Some("Vec<String>"),
        "store_upload_revision" => Some("String"),
        "subject" => Some("String"),
        "successful" => Some("bool"),
        "suite_names" => Some("Vec<String>"),
        "summary" => Some("String"),
        "supported" => Some("bool"),
        "supports_mirrors" => Some("bool"),
        "supports_ppas" => Some("bool"),
        "supports_virtualized" => Some("bool"),
        "suppress_subscription_notifications" => Some("bool"),
        "tag" => Some("String"),
        "tags" => Some("Vec<String>"),
        "target_architectures" => Some("Vec<String>"),
        "target_default" => Some("bool"),
        "target_git_path" => Some("String"),
        "target_revision_id" => Some("String"),
        "team_description" => Some("String"),
        "text" => Some("String"),
        "time_zone" => Some("String"),
        "token" => Some("String"),
        "translation_domain" => Some("String"),
        "unique_key" => Some("String"),
        "unique_name" => Some("String"),
        "uri" => Some("url::Url"),
        "url" => Some("url::Url"),
        "usable_distro_series" => Some("bool"),
        "users_affected_count_with_dupes" => Some("usize"),
        "version" => Some("debversion::Version"),
        "virtualized" => Some("bool"),
        "visible" => Some("bool"),
        "vm_host" => Some("String"),
        "vote_tag" => Some("String"),
        "whatchanged" => Some("String"),
        "whiteboard" => Some("String"),
        "wiki" => Some("String"),
        "wikiname" => Some("String"),
        _ => None,
    }
    .map(|s| s.to_string())
}

fn accessor_rename(param_name: &str, type_name: &str) -> Option<String> {
    if let Some(prefix) = param_name.strip_suffix("_collection_link") {
        if !type_name.contains("PageResource") {
            return Some(prefix.to_string());
        }
    }

    param_name.strip_suffix("_link").map(|param_name| {
        if param_name == "self" {
            "self_"
        } else {
            param_name
        }
        .to_string()
    })
}

fn generate_representation_traits(
    _def: &wadl::ast::RepresentationDef,
    name: &str,
    _representation: &wadl::ast::RepresentationDef,
    _config: &wadl::codegen::Config,
) -> Option<Vec<String>> {
    if name.ends_with("Page") {
        let r = format!("{}Full", name.strip_suffix("Page").unwrap());
        let ret = vec![
            "impl crate::page::Page for ".to_string() + name + " {\n",
            "    type Item = ".to_string() + r.as_str() + ";\n",
            "    fn next<'a>(&'a self, client: &'a dyn wadl::Client) -> Result<Option<Self>, Error> { self.next_collection().map(|x| x.get(client)).transpose() }\n".to_string(),
            "    fn prev<'a>(&'a self, client: &'a dyn wadl::Client) -> Result<Option<Self>, Error> { self.prev_collection().map(|x| x.get(client)).transpose() }\n".to_string(),
            "    fn start(&self) -> usize { self.start }\n".to_string(),
            "    fn total_size(&self) -> Option<usize> { self.total_size.as_total_size() }\n".to_string(),
            "    fn entries(&self) -> Vec<".to_string()
                + r.as_str()
                + "> { self.entries.iter().map(|v| serde_json::from_value(v.clone()).unwrap()).collect() }\n",
            "}\n".to_string(),
        ];
        Some(ret)
    } else {
        None
    }
}

fn accessor_visibility(_param_name: &str, param_type: &str) -> Option<String> {
    if param_type.ends_with("PageResource") {
        Some("".to_string())
    } else {
        Some("pub".to_string())
    }
}

fn method_visibility(method_name: &str, return_type: &str) -> Option<String> {
    if !(method_name == "get" && return_type.ends_with("Page")) {
        Some("pub".to_string())
    } else {
        Some("".to_string())
    }
}

fn resource_type_visibility(resource_type_name: &str) -> Option<String> {
    if resource_type_name.ends_with("PageResource") {
        Some("".to_string())
    } else {
        Some("pub".to_string())
    }
}

fn extend_accessor(param: &wadl::ast::Param, accessor_name: &str, type_name: &str, config: &wadl::codegen::Config) -> Vec<String> {
    if let Some(field_name) = accessor_name.strip_suffix("_collection") {
        // find the bit in between the last < and the first >
        let bn = type_name.rfind('<').map(|i| &type_name[i + 1..]).unwrap_or(type_name).trim_end_matches('>');
        let inner_type = &bn[bn.rfind(' ').map_or(0, |x| x+1)..];
        let pr = if let Some(prefix) = inner_type.strip_suffix("PageResource") {
            prefix.to_string()
        } else {
            return vec![];
        };
        let mut lines = vec![];
        for doc in &param.doc {
            lines.extend(wadl::codegen::generate_doc(doc, 1, config));
        }
        let page_type = match pr.as_str() {
            "People" => "Person".to_string(),
            t if t.ends_with("Countries") || t.ends_with("Repositories") || t.ends_with("Entries") => format!("{}y", t.strip_suffix("ies").unwrap()),
            t if t.ends_with("ieses") => format!("{}ies", t.strip_suffix("ieses").unwrap()),
            "Archives" | "Bugs" | "BugTrackers" | "CharmBases" | "CharmRecipes" | "Distributions" | "Builders" | "Languages" | "Cves" | "Projects" | "Processors" | "Polls" | "Packagesets" | "Specifications" | "Snaps" | "SnapBases" | "Questions" => pr.strip_suffix('s').unwrap().to_string(),
            t if t.ends_with("Blobs") || t.ends_with("Groups") => t.strip_suffix('s').unwrap().to_string(),
            "Livefses" => "Livefs".to_string(),
            "Branches" => "Branch".to_string(),
            t => t.to_string()
        } + "Page";
        lines.extend(if type_name.starts_with("Option<") {
            vec![
                    format!("    pub fn {}<'a>(&'a self, client: &'a dyn wadl::Client) -> Result<Option<crate::page::PagedCollection<'a, {}>>, Error> {{\n", field_name, page_type),
                    format!("        self.{}_collection().map(|x| Ok(crate::page::PagedCollection::new(client, x.get(client)?))).transpose()\n", field_name),
                    format!("    }}\n"),
            ]
        } else {
            vec![
                format!("    pub fn {}<'a>(&'a self, client: &'a dyn wadl::Client) -> Result<crate::page::PagedCollection<'a, {}>, Error> {{\n", field_name, page_type),
                format!("        Ok(crate::page::PagedCollection::new(client, self.{}_collection().get(client)?))\n", field_name),
                format!("    }}\n"),
            ]
        });
        lines
    } else {
        vec![]
    }
}

fn extend_method(resource_type: &str, name: &str, ret_type: &str, _config: &wadl::codegen::Config) -> Vec<String> {
    if !resource_type.ends_with("-page-resource") && name == "get" && ret_type.contains("Page") {
        vec![
            format!("    /// Get a paged collection of {}.\n", ret_type),
            format!("    pub fn iter<'a>(&'a self, client: &'a dyn wadl::Client) -> Result<crate::page::PagedCollection<'a, {}>, Error> {{\n", ret_type),
            format!("        Ok(crate::page::PagedCollection::new(client, self.get(client)?))\n"),
            format!("    }}\n"),
        ]
    } else {
        vec![]
    }
}

fn map_type_for_response(method: &str, type_name: &str) -> Option<(String, String)> {
    if !type_name.ends_with("Page") {
        return None;
    }

    if method == "get" {
        return None;
    }

    Some((format!("crate::page::PagedCollection<'a, {}>", type_name), "|x| crate::page::PagedCollection::new(client, x)".to_string()))
}

fn deprecated_param(param: &wadl::ast::Param) -> bool {
    if let Some(doc) = param.doc.as_ref() {
        doc.content.contains("[DEPRECATED]")
    } else {
        false
    }
}

const VERSIONS: &[&str] = &["1.0", "devel", "beta"];

fn main() {
    #[allow(clippy::needless_update)]
    let config = wadl::codegen::Config {
        guess_type_name: Some(Box::new(guess_type_name)),
        param_accessor_rename: Some(Box::new(accessor_rename)),
        generate_representation_traits: Some(Box::new(generate_representation_traits)),
        strip_code_examples: true,
        accessor_visibility: Some(Box::new(accessor_visibility)),
        resource_type_visibility: Some(Box::new(resource_type_visibility)),
        extend_accessor: Some(Box::new(extend_accessor)),
        extend_method: Some(Box::new(extend_method)),
        method_visibility: Some(Box::new(method_visibility)),
        map_type_for_response: Some(Box::new(map_type_for_response)),
        deprecated_param: Some(Box::new(deprecated_param)),
        ..Default::default()
    };

    for version in VERSIONS {
        let wadl = if let Ok(text) = std::fs::read_to_string(format!(
            "{}/wadl/{}.wadl",
            env!("CARGO_MANIFEST_DIR"),
            version
        )) {
            text
        } else {
            let url = format!("https://api.launchpad.net/{}/", version);
            reqwest::blocking::Client::new()
                .request(reqwest::Method::GET, &url)
                .header("Accept", "application/vd.sun.wadl+xml")
                .send()
                .unwrap()
                .error_for_status()
                .unwrap()
                .text()
                .unwrap()
        };

        let wadl_app = wadl::parse_string(wadl.as_str()).unwrap();
        let code = wadl::codegen::generate(&wadl_app, &config);
        let target_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
            .canonicalize()
            .unwrap();
        let generated = target_dir.join("generated");
        std::fs::create_dir_all(&generated).unwrap();
        let path = generated.join(format!("{}.rs", version.replace('.', "_")));
        std::fs::write(path, code).unwrap();
    }
}