Skip to content

Commit 77149d3

Browse files
committed
Mark default features with a *
Fixes rust-lang#2531 by marking every (transitive) default feature with a `*`. The features list is already sorted according to depth-first `"default"` order first, and according to the number of sub-features. I changed that so the secondary sort order is alphabetic, as that might make more sense than sorting by number of sub-features.
1 parent bbf1c3f commit 77149d3

File tree

2 files changed

+130
-78
lines changed

2 files changed

+130
-78
lines changed

src/web/features.rs

+115-66
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@ use std::collections::{HashMap, VecDeque};
1616

1717
const DEFAULT_NAME: &str = "default";
1818

19+
#[derive(Debug, Clone, Serialize)]
20+
struct DocsFeature {
21+
name: String,
22+
subfeatures: Vec<String>,
23+
is_default: bool,
24+
}
25+
26+
type AllFeatures = HashMap<String, DocsFeature>;
27+
1928
#[derive(Debug, Clone, Serialize)]
2029
struct FeaturesPage {
2130
metadata: MetaData,
22-
features: Option<Vec<Feature>>,
31+
all_features: AllFeatures,
32+
sorted_features: Option<Vec<String>>,
2333
default_len: usize,
2434
canonical_url: CanonicalUrl,
2535
is_latest_url: bool,
@@ -66,18 +76,21 @@ pub(crate) async fn build_features_handler(
6676
.await?
6777
.ok_or_else(|| anyhow!("missing release"))?;
6878

69-
let mut features = None;
79+
let mut all_features = HashMap::new();
80+
let mut sorted_features = None;
7081
let mut default_len = 0;
7182

7283
if let Some(raw_features) = row.features {
7384
let result = order_features_and_count_default_len(raw_features);
74-
features = Some(result.0);
75-
default_len = result.1;
85+
all_features = result.0;
86+
sorted_features = Some(result.1);
87+
default_len = result.2;
7688
}
7789

7890
Ok(FeaturesPage {
7991
metadata,
80-
features,
92+
all_features,
93+
sorted_features,
8194
default_len,
8295
is_latest_url: req_version.is_latest(),
8396
canonical_url: CanonicalUrl::from_path(format!("/crate/{}/latest/features", &name)),
@@ -86,40 +99,64 @@ pub(crate) async fn build_features_handler(
8699
.into_response())
87100
}
88101

89-
fn order_features_and_count_default_len(raw: Vec<Feature>) -> (Vec<Feature>, usize) {
90-
let mut feature_map = get_feature_map(raw);
91-
let mut features = get_tree_structure_from_default(&mut feature_map);
92-
let mut remaining = Vec::from_iter(feature_map.into_values());
93-
remaining.sort_by_key(|feature| feature.subfeatures.len());
102+
fn order_features_and_count_default_len(raw: Vec<Feature>) -> (AllFeatures, Vec<String>, usize) {
103+
let mut all_features = get_all_features(raw);
104+
let sorted_features = get_sorted_features(&mut all_features);
94105

95-
let default_len = features.len();
106+
let default_len = all_features.values().filter(|f| f.is_default).count();
96107

97-
features.extend(remaining.into_iter().rev());
98-
(features, default_len)
108+
(all_features, sorted_features, default_len)
99109
}
100110

101-
fn get_tree_structure_from_default(feature_map: &mut HashMap<String, Feature>) -> Vec<Feature> {
102-
let mut features = Vec::new();
103-
let mut queue: VecDeque<String> = VecDeque::new();
104-
105-
queue.push_back(DEFAULT_NAME.into());
106-
while !queue.is_empty() {
107-
let name = queue.pop_front().unwrap();
108-
if let Some(feature) = feature_map.remove(&name) {
111+
/// This flags all features as being reachable from `"default"`,
112+
/// and returns them as a sorted list.
113+
///
114+
/// The sorting order depends on depth-first traversal of the default features,
115+
/// and alphabetically otherwise.
116+
fn get_sorted_features(all_features: &mut AllFeatures) -> Vec<String> {
117+
let mut sorted_features = Vec::new();
118+
let mut working_features: HashMap<&str, &mut DocsFeature> = all_features
119+
.iter_mut()
120+
.map(|(k, v)| (k.as_str(), v))
121+
.collect();
122+
123+
// this does a depth-first traversal starting at the special `"default"` feature
124+
let mut queue: VecDeque<&str> = VecDeque::new();
125+
queue.push_back(DEFAULT_NAME);
126+
127+
while let Some(name) = queue.pop_front() {
128+
if let Some(feature) = working_features.remove(name) {
109129
feature
110130
.subfeatures
111131
.iter()
112-
.for_each(|sub| queue.push_back(sub.clone()));
113-
features.push(feature);
132+
.for_each(|sub| queue.push_back(sub.as_str()));
133+
feature.is_default = true;
134+
sorted_features.push(feature.name.clone());
114135
}
115136
}
116-
features
137+
138+
// the rest of the features not reachable from `"default"` are sorted alphabetically
139+
let mut remaining = Vec::from_iter(working_features.into_values());
140+
remaining.sort_by(|f1, f2| f2.name.cmp(&f1.name));
141+
sorted_features.extend(remaining.into_iter().map(|f| f.name.clone()).rev());
142+
143+
sorted_features
117144
}
118145

119-
fn get_feature_map(raw: Vec<Feature>) -> HashMap<String, Feature> {
146+
/// Parses the raw [`Feature`] into a map of the more structured [`DocsFeature`].
147+
fn get_all_features(raw: Vec<Feature>) -> AllFeatures {
120148
raw.into_iter()
121149
.filter(|feature| !feature.is_private())
122-
.map(|feature| (feature.name.clone(), feature))
150+
.map(|feature| {
151+
(
152+
feature.name.clone(),
153+
DocsFeature {
154+
name: feature.name,
155+
subfeatures: feature.subfeatures,
156+
is_default: false,
157+
},
158+
)
159+
})
123160
.collect()
124161
}
125162

@@ -135,11 +172,11 @@ mod tests {
135172
let feature2 = Feature::new("feature2".into(), Vec::new());
136173

137174
let raw = vec![private1.clone(), feature2.clone()];
138-
let feature_map = get_feature_map(raw);
175+
let all_features = get_all_features(raw);
139176

140-
assert_eq!(feature_map.len(), 1);
141-
assert!(feature_map.contains_key(&feature2.name));
142-
assert!(!feature_map.contains_key(&private1.name));
177+
assert_eq!(all_features.len(), 1);
178+
assert!(all_features.contains_key(&feature2.name));
179+
assert!(!all_features.contains_key(&private1.name));
143180
}
144181

145182
#[test]
@@ -160,17 +197,23 @@ mod tests {
160197
feature2.clone(),
161198
feature1.clone(),
162199
];
163-
let mut feature_map = get_feature_map(raw);
164-
let default_tree = get_tree_structure_from_default(&mut feature_map);
165-
166-
assert_eq!(feature_map.len(), 1);
167-
assert_eq!(default_tree.len(), 4);
168-
assert!(feature_map.contains_key(&non_default.name));
169-
assert!(!feature_map.contains_key(&default.name));
170-
assert_eq!(default_tree[0], default);
171-
assert_eq!(default_tree[1], feature1);
172-
assert_eq!(default_tree[2], feature2);
173-
assert_eq!(default_tree[3], feature3);
200+
let mut all_features = get_all_features(raw);
201+
let sorted_features = get_sorted_features(&mut all_features);
202+
203+
assert_eq!(all_features.len(), 5);
204+
205+
assert_eq!(
206+
sorted_features,
207+
vec![
208+
"default".to_string(),
209+
"feature1".into(),
210+
"feature2".into(),
211+
"feature3".into(),
212+
"non-default".into()
213+
]
214+
);
215+
assert!(all_features["feature3"].is_default);
216+
assert!(!all_features["non-default"].is_default);
174217
}
175218

176219
#[test]
@@ -183,14 +226,16 @@ mod tests {
183226
let feature3 = Feature::new("feature3".into(), Vec::new());
184227

185228
let raw = vec![feature3.clone(), feature2.clone(), feature1.clone()];
186-
let mut feature_map = get_feature_map(raw);
187-
let default_tree = get_tree_structure_from_default(&mut feature_map);
188-
189-
assert_eq!(feature_map.len(), 3);
190-
assert_eq!(default_tree.len(), 0);
191-
assert!(feature_map.contains_key(&feature1.name));
192-
assert!(feature_map.contains_key(&feature2.name));
193-
assert!(feature_map.contains_key(&feature3.name));
229+
let mut all_features = get_all_features(raw);
230+
let sorted_features = get_sorted_features(&mut all_features);
231+
232+
assert_eq!(
233+
sorted_features,
234+
vec!["feature1".to_string(), "feature2".into(), "feature3".into()]
235+
);
236+
assert!(!all_features["feature1"].is_default);
237+
assert!(!all_features["feature2"].is_default);
238+
assert!(!all_features["feature3"].is_default);
194239
}
195240

196241
#[test]
@@ -199,14 +244,15 @@ mod tests {
199244
let non_default = Feature::new("non-default".into(), Vec::new());
200245

201246
let raw = vec![default.clone(), non_default.clone()];
202-
let mut feature_map = get_feature_map(raw);
203-
let default_tree = get_tree_structure_from_default(&mut feature_map);
204-
205-
assert_eq!(feature_map.len(), 1);
206-
assert_eq!(default_tree.len(), 1);
207-
assert!(feature_map.contains_key(&non_default.name));
208-
assert!(!feature_map.contains_key(&default.name));
209-
assert_eq!(default_tree[0], default);
247+
let mut all_features = get_all_features(raw);
248+
let sorted_features = get_sorted_features(&mut all_features);
249+
250+
assert_eq!(
251+
sorted_features,
252+
vec!["default".to_string(), "non-default".into()]
253+
);
254+
assert!(all_features["default"].is_default);
255+
assert!(!all_features["non-default"].is_default);
210256
}
211257

212258
#[test]
@@ -219,13 +265,14 @@ mod tests {
219265
let feature3 = Feature::new("feature3".into(), Vec::new());
220266

221267
let raw = vec![feature3.clone(), feature2.clone(), feature1.clone()];
222-
let (features, default_len) = order_features_and_count_default_len(raw);
268+
let (_all_features, sorted_features, default_len) =
269+
order_features_and_count_default_len(raw);
223270

224-
assert_eq!(features.len(), 3);
271+
assert_eq!(
272+
sorted_features,
273+
vec!["feature1".to_string(), "feature2".into(), "feature3".into()]
274+
);
225275
assert_eq!(default_len, 0);
226-
assert_eq!(features[0], feature1);
227-
assert_eq!(features[1], feature2);
228-
assert_eq!(features[2], feature3);
229276
}
230277

231278
#[test]
@@ -234,12 +281,14 @@ mod tests {
234281
let non_default = Feature::new("non-default".into(), Vec::new());
235282

236283
let raw = vec![default.clone(), non_default.clone()];
237-
let (features, default_len) = order_features_and_count_default_len(raw);
284+
let (_all_features, sorted_features, default_len) =
285+
order_features_and_count_default_len(raw);
238286

239-
assert_eq!(features.len(), 2);
287+
assert_eq!(
288+
sorted_features,
289+
vec!["default".to_string(), "non-default".into()]
290+
);
240291
assert_eq!(default_len, 1);
241-
assert_eq!(features[0], default);
242-
assert_eq!(features[1], non_default);
243292
}
244293

245294
#[test]

templates/crate/features.html

+15-12
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,16 @@
3434
<div class="pure-menu package-menu">
3535
<ul class="pure-menu-list">
3636
<li class="pure-menu-heading">Feature flags</li>
37-
{%- if features -%}
38-
{%- for feature in features -%}
37+
{%- if sorted_features -%}
38+
{%- for name in sorted_features -%}
39+
{%- set feature = all_features | get(key=name) -%}
3940
<li class="pure-menu-item">
40-
<a href="#{{ feature.name }}" class="pure-menu-link text-center">
41-
{{ feature.name }}
41+
<a href="#{{ name }}" class="pure-menu-link text-center">
42+
{{ name }} {%- if feature.is_default -%}*{%- endif -%}
4243
</a>
4344
</li>
4445
{%- endfor -%}
45-
{%- elif features is iterable -%}
46+
{%- elif sorted_features is iterable -%}
4647
<li class="pure-menu-item">
4748
<span class="documented-info">This release does not have any feature flags.</span>
4849
</li>
@@ -68,23 +69,25 @@ <h1>{{ metadata.name }}</h1>
6869
<a href="/crate/{{ metadata.name }}/{{ metadata.req_version }}/source/Cargo.toml.orig">Cargo.toml</a>
6970
in case the author documented the features in them.
7071
</div>
71-
{%- if features -%}
72-
<p>This version has <b>{{ features | length }}</b> feature flags, <b data-id="default-feature-len">{{ default_len }}</b> of them enabled by <b>default</b>.</p>
73-
{%- for feature in features -%}
74-
<h3 id="{{ feature.name }}">{{ feature.name }}</h3>
72+
{%- if sorted_features -%}
73+
<p>This version has <b>{{ sorted_features | length }}</b> feature flags, <b data-id="default-feature-len">{{ default_len }}</b> of them enabled by <b>default</b>.</p>
74+
{%- for name in sorted_features -%}
75+
{%- set feature = all_features | get(key=name) -%}
76+
<h3 id="{{ name }}">{{ name }} {%- if feature.is_default -%}*{%- endif -%}</h3>
7577
<ul class="pure-menu-list">
7678
{%- if feature.subfeatures -%}
77-
{%- for subfeature in feature.subfeatures -%}
79+
{%- for name in feature.subfeatures -%}
80+
{%- set feature = all_features | get(key=name, default=false) -%}
7881
<li class="pure-menu-item">
79-
<span>{{ subfeature }}</span>
82+
<span>{{ name }} {%- if feature and feature.is_default -%}*{%- endif -%}</span>
8083
</li>
8184
{%- endfor -%}
8285
{%- else -%}
8386
<p>This feature flag does not enable additional features.</p>
8487
{%- endif -%}
8588
</ul>
8689
{%- endfor -%}
87-
{%- elif features is iterable -%}
90+
{%- elif sorted_features is iterable -%}
8891
<p data-id="empty-features">This release does not have any feature flags.</p>
8992
{%- else -%}
9093
<p data-id="null-features">

0 commit comments

Comments
 (0)