22
33use std:: collections:: HashMap ;
44use std:: fs:: File ;
5- use std:: path:: Path ;
5+ use std:: io:: Read ;
6+ use std:: path:: { Path , PathBuf } ;
67
8+ use detect_indent;
79use notion_fail:: { Fallible , ResultExt } ;
810use semver:: VersionReq ;
11+ use serde:: Serialize ;
912use serde_json;
1013
1114use serial;
1215
13- /// A Node manifest file .
14- pub struct Manifest {
15- /// The requested version of Node, under the `notion .node` key.
16+ /// A toolchain manifest.
17+ pub struct ToolchainManifest {
18+ /// The requested version of Node, under the `toolchain .node` key.
1619 pub node : VersionReq ,
17- /// The requested version of Yarn, under the `notion.yarn` key.
20+ /// The pinned version of Node as a string.
21+ pub node_str : String ,
22+ /// The requested version of Yarn, under the `toolchain.yarn` key.
1823 pub yarn : Option < VersionReq > ,
24+ /// The pinned version of Yarn as a string.
25+ pub yarn_str : Option < String > ,
26+ }
27+
28+ /// A Node manifest file.
29+ pub struct Manifest {
30+ /// The `toolchain` section.
31+ pub toolchain : Option < ToolchainManifest > ,
1932 /// The `dependencies` section.
2033 pub dependencies : HashMap < String , String > ,
2134 /// The `devDependencies` section.
@@ -24,11 +37,74 @@ pub struct Manifest {
2437
2538impl Manifest {
2639 /// Loads and parses a Node manifest for the project rooted at the specified path.
27- pub fn for_dir ( project_root : & Path ) -> Fallible < Option < Manifest > > {
40+ pub fn for_dir ( project_root : & Path ) -> Fallible < Manifest > {
41+ // if package.json doesn't exist, this fails, OK
2842 let file = File :: open ( project_root. join ( "package.json" ) ) . unknown ( ) ?;
2943 let serial: serial:: manifest:: Manifest = serde_json:: de:: from_reader ( file) . unknown ( ) ?;
3044 serial. into_manifest ( )
3145 }
46+
47+ /// Returns whether this manifest contains a toolchain section (at least Node is pinned).
48+ pub fn has_toolchain ( & self ) -> bool {
49+ self . toolchain . is_some ( )
50+ }
51+
52+ /// Returns the pinned version of Node as a VersionReq, if any.
53+ pub fn node ( & self ) -> Option < VersionReq > {
54+ self . toolchain . as_ref ( ) . map ( |t| t. node . clone ( ) )
55+ }
56+
57+ /// Returns the pinned verison of Node as a String, if any.
58+ pub fn node_str ( & self ) -> Option < String > {
59+ self . toolchain . as_ref ( ) . map ( |t| t. node_str . clone ( ) )
60+ }
61+
62+ /// Returns the pinned verison of Yarn as a VersionReq, if any.
63+ pub fn yarn ( & self ) -> Option < VersionReq > {
64+ self . toolchain
65+ . as_ref ( )
66+ . map ( |t| t. yarn . clone ( ) )
67+ . unwrap_or ( None )
68+ }
69+
70+ /// Returns the pinned verison of Yarn as a String, if any.
71+ pub fn yarn_str ( & self ) -> Option < String > {
72+ self . toolchain
73+ . as_ref ( )
74+ . map ( |t| t. yarn_str . clone ( ) )
75+ . unwrap_or ( None )
76+ }
77+
78+ /// Writes the input ToolchainManifest to package.json, adding the "toolchain" key if
79+ /// necessary.
80+ pub fn update_toolchain (
81+ toolchain : serial:: manifest:: ToolchainManifest ,
82+ package_file : PathBuf ,
83+ ) -> Fallible < ( ) > {
84+ // parse the entire package.json file into a Value
85+ let file = File :: open ( & package_file) . unknown ( ) ?;
86+ let mut v: serde_json:: Value = serde_json:: from_reader ( file) . unknown ( ) ?;
87+
88+ // detect indentation in package.json
89+ let mut contents = String :: new ( ) ;
90+ let mut indent_file = File :: open ( & package_file) . unknown ( ) ?;
91+ indent_file. read_to_string ( & mut contents) . unknown ( ) ?;
92+ let indent = detect_indent:: detect_indent ( & contents) ;
93+
94+ if let Some ( map) = v. as_object_mut ( ) {
95+ // update the "toolchain" key
96+ let toolchain_value = serde_json:: to_value ( toolchain) . unknown ( ) ?;
97+ map. insert ( "toolchain" . to_string ( ) , toolchain_value) ;
98+
99+ // serialize the updated contents back to package.json
100+ let file = File :: create ( package_file) . unknown ( ) ?;
101+ let formatter =
102+ serde_json:: ser:: PrettyFormatter :: with_indent ( indent. indent ( ) . as_bytes ( ) ) ;
103+ let mut ser = serde_json:: Serializer :: with_formatter ( file, formatter) ;
104+ map. serialize ( & mut ser) . unknown ( ) ?;
105+ }
106+ Ok ( ( ) )
107+ }
32108}
33109
34110// unit tests
@@ -51,39 +127,21 @@ pub mod tests {
51127 #[ test]
52128 fn gets_node_version ( ) {
53129 let project_path = fixture_path ( "basic" ) ;
54- let version = match Manifest :: for_dir ( & project_path) {
55- Ok ( manifest) => manifest. unwrap ( ) . node ,
56- _ => panic ! (
57- "Error: Could not get manifest for project {:?}" ,
58- project_path
59- ) ,
60- } ;
130+ let version = Manifest :: for_dir ( & project_path) . expect ( "Could not get manifest" ) . node ( ) . unwrap ( ) ;
61131 assert_eq ! ( version, VersionReq :: parse( "=6.11.1" ) . unwrap( ) ) ;
62132 }
63133
64134 #[ test]
65135 fn gets_yarn_version ( ) {
66136 let project_path = fixture_path ( "basic" ) ;
67- let version = match Manifest :: for_dir ( & project_path) {
68- Ok ( manifest) => manifest. unwrap ( ) . yarn ,
69- _ => panic ! (
70- "Error: Could not get manifest for project {:?}" ,
71- project_path
72- ) ,
73- } ;
137+ let version = Manifest :: for_dir ( & project_path) . expect ( "Could not get manifest" ) . yarn ( ) ;
74138 assert_eq ! ( version. unwrap( ) , VersionReq :: parse( "=1.2" ) . unwrap( ) ) ;
75139 }
76140
77141 #[ test]
78142 fn gets_dependencies ( ) {
79143 let project_path = fixture_path ( "basic" ) ;
80- let dependencies = match Manifest :: for_dir ( & project_path) {
81- Ok ( manifest) => manifest. unwrap ( ) . dependencies ,
82- _ => panic ! (
83- "Error: Could not get manifest for project {:?}" ,
84- project_path
85- ) ,
86- } ;
144+ let dependencies = Manifest :: for_dir ( & project_path) . expect ( "Could not get manifest" ) . dependencies ;
87145 let mut expected_deps = HashMap :: new ( ) ;
88146 expected_deps. insert ( "@namespace/some-dep" . to_string ( ) , "0.2.4" . to_string ( ) ) ;
89147 expected_deps. insert ( "rsvp" . to_string ( ) , "^3.5.0" . to_string ( ) ) ;
@@ -93,13 +151,7 @@ pub mod tests {
93151 #[ test]
94152 fn gets_dev_dependencies ( ) {
95153 let project_path = fixture_path ( "basic" ) ;
96- let dev_dependencies = match Manifest :: for_dir ( & project_path) {
97- Ok ( manifest) => manifest. unwrap ( ) . dev_dependencies ,
98- _ => panic ! (
99- "Error: Could not get manifest for project {:?}" ,
100- project_path
101- ) ,
102- } ;
154+ let dev_dependencies = Manifest :: for_dir ( & project_path) . expect ( "Could not get manifest" ) . dev_dependencies ;
103155 let mut expected_deps = HashMap :: new ( ) ;
104156 expected_deps. insert (
105157 "@namespaced/something-else" . to_string ( ) ,
@@ -108,4 +160,19 @@ pub mod tests {
108160 expected_deps. insert ( "eslint" . to_string ( ) , "~4.8.0" . to_string ( ) ) ;
109161 assert_eq ! ( dev_dependencies, expected_deps) ;
110162 }
163+
164+ #[ test]
165+ fn node_for_no_toolchain ( ) {
166+ let project_path = fixture_path ( "no_toolchain" ) ;
167+ let manifest = Manifest :: for_dir ( & project_path) . expect ( "Could not get manifest" ) ;
168+ assert_eq ! ( manifest. node( ) , None ) ;
169+ }
170+
171+ #[ test]
172+ fn yarn_for_no_toolchain ( ) {
173+ let project_path = fixture_path ( "no_toolchain" ) ;
174+ let manifest = Manifest :: for_dir ( & project_path) . expect ( "Could not get manifest" ) ;
175+ assert_eq ! ( manifest. yarn( ) , None ) ;
176+ }
177+
111178}
0 commit comments