-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revise exit status section #30
base: master
Are you sure you want to change the base?
Conversation
This commit revises the exit section to allow the user to specify custom status codes and messages to correspond to those codes.
Merged master so that this PR will apply cleanly. I also reverted deleting the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM! Few nits, but otherwise great!
bc3a0a4
to
a09fcd7
Compare
Fixed those nits—agreed on both points. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something but from a quick glance I think we can improve this design a bit :)
src/exit_status.rs
Outdated
pub struct ExitStatus { | ||
pub(crate) code: Option<i32>, | ||
pub(crate) description: Option<&'static str>, | ||
pub(crate) use_default_instead: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused. If you have a bunch of these and any one of them has this boolean flag set to true, you'll ignore all information and instead print a list of default statuses? I'd do this with an enum like enum ExitStatuses { DefaultStatuses, List(Vec<ExitStatus>) }
(with ExitStatus containing fields for code and description)
src/exit_status.rs
Outdated
} | ||
} | ||
|
||
pub fn default() -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are already deriving Default which generates an equivalent method
Changed to make Clippy happy
Agreed on both points. I refactored the code and am much happier with how it reads now that the default value is set using an enum instead of with a sentential value. Thanks for the tip! |
Nice! Sorry it took me a while to reply. I still don't know what the exact context for this is and how you'll use it, but one thing I'd definitely do is make a single method that allows you to get an iterator of exit status entries, for both cases. After a bit of fiddling I wrote this, that also moves the default list into a diff --git a/src/man.rs b/src/man.rs
index 53eda6c..177f0e9 100644
--- a/src/man.rs
+++ b/src/man.rs
@@ -19,33 +19,41 @@ pub struct Manual {
#[derive(Debug, Clone)]
enum ExitStatuses {
- DefaultStatuses([ExitStatus; 3]),
+ DefaultStatuses,
CustomStatuses(Vec<ExitStatus>),
}
+static DEFAULT_STATUSES: [ExitStatus; 3] = [
+ ExitStatus {
+ code: Some(0),
+ description: Some("Successful program execution."),
+ },
+ ExitStatus {
+ code: Some(1),
+ description: Some("Unsuccessful program execution."),
+ },
+ ExitStatus {
+ code: Some(101),
+ description: Some("The program panicked."),
+ },
+];
+
impl ExitStatuses {
fn push(&mut self, new_status: ExitStatus) {
- if let ExitStatuses::CustomStatuses(mut vec) = self.to_owned() {
+ if let ExitStatuses::CustomStatuses(vec) = self {
vec.push(new_status);
- *self = ExitStatuses::CustomStatuses(vec);
}
}
fn set_to_default(&mut self) {
- *self = ExitStatuses::DefaultStatuses([
- ExitStatus {
- code: Some(0),
- description: Some("Successful program execution."),
- },
- ExitStatus {
- code: Some(1),
- description: Some("Unsuccessful program execution."),
- },
- ExitStatus {
- code: Some(101),
- description: Some("The program panicked."),
- },
- ]);
+ *self = ExitStatuses::DefaultStatuses;
+ }
+
+ fn iter(&self) -> impl Iterator<Item = &ExitStatus> {
+ match self {
+ ExitStatuses::CustomStatuses(vec) => vec.iter(),
+ ExitStatuses::DefaultStatuses => DEFAULT_STATUSES.iter(),
+ }
}
}
@@ -397,29 +405,19 @@ fn env(page: Roff, environment: &[Env]) -> Roff {
/// ```
fn exit_status(page: Roff, exit_statuses: &ExitStatuses) -> Roff {
let mut arr = vec![];
- match exit_statuses {
- ExitStatuses::DefaultStatuses(default_statuses) => {
- for status in default_statuses.iter() {
- let code = format!("{}", status.code.expect("Set as part of default"));
- let mut description = String::from(status.description.unwrap());
- description.push_str("\n\n");
- arr.push(list(&[bold(&code)], &[description]));
- }
- }
- ExitStatuses::CustomStatuses(custom_statuses) => {
- if custom_statuses.is_empty() {
- return page;
- }
- for status in custom_statuses.iter() {
- let code =
- format!("{}", status.code.expect("Always set when not default"));
- let mut description = String::from(status.description.unwrap_or(""));
- description.push_str("\n\n");
- arr.push(list(&[bold(&code)], &[description]));
- }
- }
+
+ for status in exit_statuses.iter() {
+ let code = format!("{}", status.code.unwrap());
+ let mut description = String::from(status.description.unwrap());
+ description.push_str("\n\n");
+ arr.push(list(&[bold(&code)], &[description]));
+ }
+
+ if !arr.is_empty() {
+ page.section("exit status", &arr)
+ } else {
+ page
}
- page.section("exit status", &arr)
}
/// Create a custom section. I'm a bit unsure about the two unwraps in the for loop: It seems that both code and description are optional, but here they are required? What do you want to do? Make sure that all exit codes have code+description, or use fallback values here, or actually have this library crash your program when one of the options is |
No need to apologize—I thought you replied very promptly so if this was you taking a while to reply, then I can't even imagine what you're like when you're fast :)
I really like this change! I implemented it in the PR and will keep that pattern in mind for future code.
I think that the way we have it in the PR now correctly captures the semantics we want. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, "quick reply" to me means something like "it's still the same day for you" :)
I think this is a very interesting API decision here, so I have more nits. It's fine as-is but I wanted to let you know what I thought.
I think that the way we have it in the PR now correctly captures the semantics we want. code is None if the default method is called—which is how we can detect that the exit status was initialized with default rather than new. If the exit status was initialize with new, then it's safe to unwrap. (I changed it to expect to clarify this point).
It sounds like you want to have two variants of ExitStatus
-- one that tells you to use the default set, and on that is an actual exit status. You should use an enum for this, too: This would let you express your intent more closely and also get rid of the possibility of accidentally doing ExitStatus::default().description("foo")
-- because what does that even mean?
I'd probably do this differently. It's not wrong, but I think there is still one impedance mismatch: You call ExitStatus::default()
to reset ExitStatuses
. ExitStatus
should not have that power/responsibility, I think. Off the top of my head I'd make the code
field mandatory and add a method use_default_exit_statuses()
to ExitStatuses
.
pub fn exit_status(mut self, exit_status: ExitStatus) -> Self { | ||
match exit_status { | ||
ExitStatus { code: None, .. } => { | ||
self.exit_statuses.set_to_default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so this line is what I was missing! I didn't get when you would ever set the exit status list to the default list – I assumed it was the set you were starting with and then when you add a custom one it gets overwritten.
This behavior here is surprising to me: When you call this public function, that has no docs, with a "special" value (code = None
), it does an unusual thing. What's also weird: To get this special input you'd have to call default()
, but from what I can tell this "special" mode should not be the "default".
Hmm, all of that is very interesting—I feel like I'm learning a lot from this conversation, so thanks for taking the time. I'm not entirely sure what API you're suggesting when you said
The current (pre-PR) API is that the user has no control over the exit status: exit statuses of 0, 1, and 101 are always printed, along with matching descriptions. As @yoshuawuyts and I discussed in #25, we think that most users will want to use those three statuses, so we'd like to keep the process of using them as concise/ergonomic as possible. The API we settled on in #25 was let page = Manual::new("basic")
.exit_status(ExitStatus::default())
.render(); to display those three exit statuses and let page = Manual::new("basic")
.exit_status(
ExitStatus::new(1)
.description("Unsuccessful program execution.")
)
.exit_status(
ExitStatus::new(3)
.description("Invalid config.")
)
.render(); to display exit statuses different from those three. Are you suggesting a change from that API? If we do want to change the API, we could do the following: let page = Manual::new("basic")
.exit_status(ExitStatus::with_default_values())
.render(); to display those three exit statuses and let page = Manual::new("basic")
.exit_status(
ExitStatus::with_code(1)
.description("Unsuccessful program execution.")
)
.exit_status(
ExitStatus::with_code(3)
.description("Invalid config.")
)
.render(); However, that change wouldn't address your point when you said:
But (unless I'm misunderstanding) the (Whatever we decide, I agree that the API docs should be improved for this section) |
Cool! I'm happy to!
After reading #25: Yes, I disagree with that API. My main issue is that the Let me write a comment on #25 with a summary of the API I'd like to suggest. |
I like the API you proposed in #25. I added a slight amendment inspired by how clap-rs handles a similar issue and provided details in that thread. |
This PR revises the exit section to allow the user to specify custom status codes and messages to correspond to those codes, implementing the API discussed in #25.