Skip to content

Commit 056366b

Browse files
committed
Implemented a simple JSON output format (without spans).
See rust-lang#108 for details.
1 parent 19d78da commit 056366b

File tree

3 files changed

+142
-19
lines changed

3 files changed

+142
-19
lines changed

src/bin/cargo_semver.rs

+8
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ fn run(config: &cargo::Config, matches: &getopts::Matches) -> Result<()> {
107107

108108
let explain = matches.opt_present("e");
109109
let compact = matches.opt_present("compact");
110+
let json = matches.opt_present("json");
110111

111112
// Obtain WorkInfo for the "current"
112113
let current = if let Some(name_and_version) = matches.opt_str("C") {
@@ -122,6 +123,7 @@ fn run(config: &cargo::Config, matches: &getopts::Matches) -> Result<()> {
122123
};
123124
let name = current.package.name().to_owned();
124125

126+
// TODO: JSON output here
125127
if matches.opt_present("show-public") {
126128
let (current_rlib, current_deps_output) =
127129
current.rlib_and_dep_output(config, &name, true, matches)?;
@@ -224,6 +226,7 @@ fn run(config: &cargo::Config, matches: &getopts::Matches) -> Result<()> {
224226
.env("RUST_SEMVER_CRATE_VERSION", stable_version)
225227
.env("RUST_SEMVER_VERBOSE", format!("{}", explain))
226228
.env("RUST_SEMVER_COMPACT", format!("{}", compact))
229+
.env("RUST_SEMVER_JSON", format!("{}", json))
227230
.env(
228231
"RUST_SEMVER_API_GUIDELINES",
229232
if matches.opt_present("a") {
@@ -308,6 +311,11 @@ mod cli {
308311
"compact",
309312
"Only output the suggested version on stdout for further processing",
310313
);
314+
opts.optflag(
315+
"j",
316+
"json",
317+
"Output a JSON-formatted description of all collected data on stdout.",
318+
);
311319
opts.optopt(
312320
"s",
313321
"stable-path",

src/bin/rust_semverver.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ fn main() {
4646
env::var("RUST_SEMVER_VERBOSE") == Ok("true".to_string());
4747
let compact =
4848
env::var("RUST_SEMVER_COMPACT") == Ok("true".to_string());
49+
let json =
50+
env::var("RUST_SEMVER_JSON") == Ok("true".to_string());
4951
let api_guidelines =
5052
env::var("RUST_SEMVER_API_GUIDELINES") == Ok("true".to_string());
5153
let version = if let Ok(ver) = env::var("RUST_SEMVER_CRATE_VERSION") {
@@ -81,7 +83,11 @@ fn main() {
8183
if let [(_, old_def_id), (_, new_def_id)] = *crates.as_slice() {
8284
debug!("running semver analysis");
8385
let changes = run_analysis(tcx, old_def_id, new_def_id);
84-
changes.output(tcx.sess, &version, verbose, compact, api_guidelines);
86+
if json {
87+
changes.output_json(&version);
88+
} else {
89+
changes.output(tcx.sess, &version, verbose, compact, api_guidelines);
90+
}
8591
} else {
8692
tcx.sess.err("could not find `old` and `new` crates");
8793
}

src/changes.rs

+127-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ use std::{
2323
use syntax::symbol::Symbol;
2424
use syntax_pos::Span;
2525

26+
use serde::Serialize;
27+
use serde::ser::{Serializer, SerializeStruct};
28+
2629
/// The categories we use when analyzing changes between crate versions.
2730
///
2831
/// These directly correspond to the semantic versioning spec, with the exception that some
@@ -31,7 +34,7 @@ use syntax_pos::Span;
3134
/// exotic and/or unlikely scenarios, while we have a separate category for them.
3235
///
3336
/// [1]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md
34-
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
37+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
3538
pub enum ChangeCategory {
3639
/// A patch-level change - no change to the public API of a crate.
3740
Patch,
@@ -64,33 +67,60 @@ impl<'a> fmt::Display for ChangeCategory {
6467
}
6568
}
6669

70+
pub struct RSymbol(pub Symbol);
71+
72+
impl Serialize for RSymbol {
73+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
74+
where S: Serializer,
75+
{
76+
serializer.serialize_str(&format!("{}", self.0))
77+
}
78+
}
79+
6780
/// Different ways to refer to a changed item.
6881
///
6982
/// Used in the header of a change description to identify an item that was subject to change.
7083
pub enum Name {
7184
/// The changed item's name.
72-
Symbol(Symbol),
85+
Symbol(RSymbol),
7386
/// A textutal description of the item, used for trait impls.
7487
ImplDesc(String),
7588
}
7689

77-
impl<'a> fmt::Display for Name {
90+
impl Name {
91+
pub fn symbol(symbol: Symbol) -> Self {
92+
Name::Symbol(RSymbol(symbol))
93+
}
94+
}
95+
96+
impl fmt::Display for Name {
7897
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7998
match *self {
80-
Name::Symbol(name) => write!(f, "`{}`", name),
99+
Name::Symbol(ref name) => write!(f, "`{}`", name.0),
81100
Name::ImplDesc(ref desc) => write!(f, "`{}`", desc),
82101
}
83102
}
84103
}
85104

105+
impl Serialize for Name {
106+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107+
where S: Serializer,
108+
{
109+
match *self {
110+
Name::Symbol(ref name) => serializer.serialize_str(&format!("{}", name.0)),
111+
Name::ImplDesc(ref desc) => serializer.serialize_str(&format!("{}", desc)),
112+
}
113+
}
114+
}
115+
86116
/// A change record of newly introduced or removed paths to an item.
87117
///
88118
/// NB: `Eq` and `Ord` instances are constructed to only regard the span of the associated item
89119
/// definition. All other spans are only present for later display of the change record.
90120
pub struct PathChange {
91121
/// The name of the item - this doesn't use `Name` because this change structure only gets
92122
/// generated for removals and additions of named items, not impls.
93-
name: Symbol,
123+
name: RSymbol,
94124
/// The definition span of the item.
95125
def_span: Span,
96126
/// The set of spans of added exports of the item.
@@ -103,7 +133,7 @@ impl PathChange {
103133
/// Construct a new empty path change record for an item.
104134
fn new(name: Symbol, def_span: Span) -> Self {
105135
Self {
106-
name,
136+
name: RSymbol(name),
107137
def_span,
108138
additions: BTreeSet::new(),
109139
removals: BTreeSet::new(),
@@ -142,7 +172,7 @@ impl PathChange {
142172
return;
143173
}
144174

145-
let msg = format!("path changes to `{}`", self.name);
175+
let msg = format!("path changes to `{}`", self.name.0);
146176
let mut builder = if cat == Breaking {
147177
session.struct_span_err(self.def_span, &msg)
148178
} else {
@@ -189,6 +219,18 @@ impl Ord for PathChange {
189219
}
190220
}
191221

222+
impl Serialize for PathChange {
223+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224+
where S: Serializer,
225+
{
226+
let mut state = serializer.serialize_struct("PathChange", 3)?;
227+
state.serialize_field("name", &self.name)?;
228+
state.serialize_field("additions", &!self.additions.is_empty())?;
229+
state.serialize_field("removals", &!self.removals.is_empty())?;
230+
state.end()
231+
}
232+
}
233+
192234
/// The types of changes we identify between items present in both crate versions.
193235
#[derive(Clone, Debug)]
194236
pub enum ChangeType<'tcx> {
@@ -605,6 +647,14 @@ impl<'a> fmt::Display for ChangeType<'a> {
605647
}
606648
}
607649

650+
impl<'tcx> Serialize for ChangeType<'tcx> {
651+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
652+
where S: Serializer,
653+
{
654+
serializer.serialize_str(&format!("{}", self))
655+
}
656+
}
657+
608658
/// A change record of an item present in both crate versions.
609659
///
610660
/// NB: `Eq` and `Ord` instances are constucted to only regard the *new* span of the associated
@@ -758,6 +808,21 @@ impl<'tcx> Ord for Change<'tcx> {
758808
}
759809
}
760810

811+
impl<'tcx> Serialize for Change<'tcx> {
812+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
813+
where S: Serializer,
814+
{
815+
let mut state = serializer.serialize_struct("Change", 3)?;
816+
state.serialize_field("name", &self.name)?;
817+
state.serialize_field("max_category", &self.max)?;
818+
819+
let changes: Vec<_> = self.changes.iter().map(|(t, _)| t.clone()).collect();
820+
821+
state.serialize_field("changes", &changes)?;
822+
state.end()
823+
}
824+
}
825+
761826
/// The total set of changes recorded for two crate versions.
762827
#[derive(Default)]
763828
pub struct ChangeSet<'tcx> {
@@ -811,7 +876,7 @@ impl<'tcx> ChangeSet<'tcx> {
811876
new_span: Span,
812877
output: bool,
813878
) {
814-
let change = Change::new(Name::Symbol(name), new_span, output);
879+
let change = Change::new(Name::symbol(name), new_span, output);
815880

816881
self.spans.insert(old_span, old_def_id);
817882
self.spans.insert(new_span, new_def_id);
@@ -874,15 +939,7 @@ impl<'tcx> ChangeSet<'tcx> {
874939
.map_or(false, Change::trait_item_breaking)
875940
}
876941

877-
/// Format the contents of a change set for user output.
878-
pub fn output(
879-
&self,
880-
session: &Session,
881-
version: &str,
882-
verbose: bool,
883-
compact: bool,
884-
api_guidelines: bool,
885-
) {
942+
fn get_new_version(&self, version: &str) -> Option<String> {
886943
if let Ok(mut new_version) = Version::parse(version) {
887944
if new_version.major == 0 {
888945
new_version.increment_patch();
@@ -894,6 +951,41 @@ impl<'tcx> ChangeSet<'tcx> {
894951
}
895952
}
896953

954+
Some(format!("{}", new_version))
955+
} else {
956+
None
957+
}
958+
}
959+
960+
pub fn output_json(&self, version: &str) {
961+
#[derive(Serialize)]
962+
struct Output<'a, 'tcx> {
963+
old_version: String,
964+
new_version: String,
965+
changes: &'a ChangeSet<'tcx>,
966+
}
967+
968+
let new_version = self.get_new_version(version).unwrap_or_else(|| "parse error".to_owned());
969+
970+
let output = Output {
971+
old_version: version.to_owned(),
972+
new_version,
973+
changes: self,
974+
};
975+
976+
println!("{}", serde_json::to_string(&output).unwrap());
977+
}
978+
979+
/// Format the contents of a change set for user output.
980+
pub fn output(
981+
&self,
982+
session: &Session,
983+
version: &str,
984+
verbose: bool,
985+
compact: bool,
986+
api_guidelines: bool,
987+
) {
988+
if let Some(new_version) = self.get_new_version(version) {
897989
if compact {
898990
println!("{}", new_version);
899991
} else {
@@ -932,6 +1024,23 @@ impl<'tcx> ChangeSet<'tcx> {
9321024
}
9331025
}
9341026

1027+
impl<'tcx> Serialize for ChangeSet<'tcx> {
1028+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1029+
where S: Serializer,
1030+
{
1031+
let mut state = serializer.serialize_struct("ChangeSet", 3)?;
1032+
1033+
let path_changes: Vec<_> = self.path_changes.values().collect();
1034+
state.serialize_field("path_changes", &path_changes)?;
1035+
1036+
let changes: Vec<_> = self.changes.values().filter(|c| c.output && !c.changes.is_empty()).collect();
1037+
state.serialize_field("changes", &changes)?;
1038+
1039+
state.serialize_field("max_category", &self.max)?;
1040+
state.end()
1041+
}
1042+
}
1043+
9351044
#[cfg(test)]
9361045
pub mod tests {
9371046
pub use super::*;
@@ -1150,7 +1259,7 @@ pub mod tests {
11501259
changes: Vec<(ChangeType_, Option<Span_>)>,
11511260
) -> Change<'a> {
11521261
let mut interner = Interner::default();
1153-
let mut change = Change::new(Name::Symbol(interner.intern("test")), s1, output);
1262+
let mut change = Change::new(Name::Symbol(RSymbol(interner.intern("test"))), s1, output);
11541263

11551264
for (type_, span) in changes {
11561265
change.insert(type_.inner(), span.map(|s| s.inner()));

0 commit comments

Comments
 (0)