@@ -10,6 +10,7 @@ use std::fmt;
1010use std:: fmt:: Debug ;
1111use std:: fmt:: Display ;
1212use std:: hash:: Hash ;
13+ use std:: path:: Component ;
1314use std:: path:: Path ;
1415
1516use dupe:: Dupe ;
@@ -172,17 +173,7 @@ impl ModuleName {
172173 Self :: from_string ( itertools:: join ( parts, "." ) )
173174 }
174175
175- pub fn from_relative_path ( path : & Path ) -> anyhow:: Result < Self > {
176- let mut components = Vec :: new ( ) ;
177- for raw_component in path. components ( ) {
178- if let Some ( component) = raw_component. as_os_str ( ) . to_str ( ) {
179- components. push ( component)
180- } else {
181- return Err ( anyhow:: anyhow!( PathConversionError :: ComponentNotUTF8 {
182- component: raw_component. as_os_str( ) . to_owned( ) ,
183- } ) ) ;
184- }
185- }
176+ fn from_relative_path_components ( mut components : Vec < & str > ) -> anyhow:: Result < Self > {
186177 let last_element = components. pop ( ) ;
187178 match last_element {
188179 None => { }
@@ -201,6 +192,39 @@ impl ModuleName {
201192 Ok ( ModuleName :: from_parts ( components) )
202193 }
203194
195+ pub fn from_relative_path ( path : & Path ) -> anyhow:: Result < Self > {
196+ let mut components = Vec :: new ( ) ;
197+ for raw_component in path. components ( ) {
198+ if let Some ( component) = raw_component. as_os_str ( ) . to_str ( ) {
199+ components. push ( component)
200+ } else {
201+ return Err ( anyhow:: anyhow!( PathConversionError :: ComponentNotUTF8 {
202+ component: raw_component. as_os_str( ) . to_owned( ) ,
203+ } ) ) ;
204+ }
205+ }
206+ Self :: from_relative_path_components ( components)
207+ }
208+
209+ pub fn relative_module_name_between ( from : & Path , to : & Path ) -> Option < ModuleName > {
210+ let relative_path = pathdiff:: diff_paths ( to, from. parent ( ) ?) ?;
211+ // In the following loop, we aim to generate a list of components that can be joined by `.`
212+ // to form a correct relative import module name,
213+ let mut components = vec ! [ "" ] ;
214+ for raw_component in relative_path. as_path ( ) . components ( ) {
215+ match & raw_component {
216+ // For each parent, we should create a `.`.
217+ // The `.` is already provided during the join, so we only need an empty component.
218+ Component :: ParentDir => components. push ( "" ) ,
219+ Component :: CurDir => { }
220+ Component :: Prefix ( _) | Component :: RootDir | Component :: Normal ( _) => {
221+ components. push ( raw_component. as_os_str ( ) . to_str ( ) ?) ;
222+ }
223+ } ;
224+ }
225+ Self :: from_relative_path_components ( components) . ok ( )
226+ }
227+
204228 pub fn append ( self , name : & Name ) -> Self {
205229 Self :: from_string ( format ! ( "{}.{}" , self . as_str( ) , name) )
206230 }
@@ -334,4 +358,18 @@ mod tests {
334358 assert_conversion_error ( "foo/bar/baz" ) ;
335359 assert_conversion_error ( "foo/bar/__init__.derp" ) ;
336360 }
361+
362+ #[ test]
363+ fn test_relative_module_name_between ( ) {
364+ fn assert_module_name ( from : & str , to : & str , expected : & str ) {
365+ let from = Path :: new ( from) ;
366+ let to = Path :: new ( to) ;
367+ let actual = ModuleName :: relative_module_name_between ( from, to) ;
368+ assert_eq ! ( Some ( ModuleName :: from_str( expected) ) , actual) ;
369+ }
370+ assert_module_name ( "foo/bar.py" , "foo/baz.py" , ".baz" ) ;
371+ assert_module_name ( "bar.py" , "foo/baz.py" , ".foo.baz" ) ;
372+ assert_module_name ( "foo/bar.py" , "baz.py" , "..baz" ) ;
373+ assert_module_name ( "foo/bar/boz.py" , "baz.py" , "...baz" ) ;
374+ }
337375}
0 commit comments