1
$\begingroup$

I'll start with an example, as it's easier to explain what I need.

Say for example I've a Project folder called "MainFolder". In this folder there are a number of subfolders and nested subfolders. One of these folders is called e.g. "Data", and there is one file in it called "data1.m" In another folder (say "Script") there is a notebook ("script.nb") that imports the file "data1.m".

I'd like to write a code that will be part of the notebook "script.nb" that finds and imports the file "data1.m" wherever the folder Script and Data are.

This is because I may want to move the subfolders Script and Data in other folders without having to change the code.

The assumptions are: - the folder name "MainFolder" is fixed - all the project folders are in "MainFolder" - the folder name "Data"is fixed - the file name "data1.m" is fixed - there may be other "data1.m" files in other subfolders of the project, but in such a case the subfolders won't be called "Data" - the code must work on different machines with different OSs (Windows, macOS and Linux)

Here an example scheme of the folder structure, where I want to access "data1.m" wherever it is.

MainFolder

subfolder_1

subfolder_2

Data

data1.m

subfolder_3

subsubfolder_1

Script

script.nb

...

subfolder_n

I do have an idea of how to do that, but it seems to me that it is quite un-elegant and a bit verbose. I'll share that anyway despite the fact I don't think it's the best way of doing it:

 Select[
  FileNames["Data",
   FileNameJoin@#[[;; Position[#, "MainFolder"][[-1, 1]]]] &@
    FileNameSplit[NotebookDirectory[]],
   Infinity],
  DirectoryQ][[1]]
$\endgroup$
  • $\begingroup$ Does your current code achieve what you want? If it does, what would you like to improve specifically? Speed, robustness (in which case we need a case where it fails)... Otherwise this reads like a "make my code pretty" question. I don't necessarily oppose that, but in this case this is such a niche application that any effort would probably only apply to your particular problem. $\endgroup$ – MarcoB Jun 14 '18 at 16:00
  • $\begingroup$ @MarcoB Yes, you are right. At the beginning I didn't have a proper code for that, so I wrote the question (I just had a piece of code that didn't work properly in all the cases). Then I found the code I wrote (I could have actually written that as an answer, but at the end I just edited the post). However, I still think is not ideal, and I thought that there may be some built-in function that I ignore that can simplify the code. $\endgroup$ – Fraccalo Jun 14 '18 at 17:41
  • $\begingroup$ For example, there may be some function that I'm not aware of that does the part of going back to the MainFolder easily (something like the bash command cd */ but backwards instead of forward). And I think if there is, this may be of interest to other users as well :) $\endgroup$ – Fraccalo Jun 14 '18 at 17:42
  • $\begingroup$ Does MMA have a built in function for selecting the elements of a list from [[1]] to a given entry? e.g. list={a,b,c,d,e,f,g,h} ; function[list,e]={a,b,c,d,e} $\endgroup$ – Fraccalo Jun 14 '18 at 20:36
  • $\begingroup$ Not a built in that I can think of. You could use: list /. {a___, e, ___} :> {a, e}. However, what should happen if the element is duplicated in the list? Are the elements strings or symbols? $\endgroup$ – MarcoB Jun 14 '18 at 21:31
1
$\begingroup$

Perhaps the following:

With[
 {positionofMain = NestWhile[
    ParentDirectory,
    NotebookDirectory[],
    Not@*StringEndsQ["MainFolder"],
    1(*supply back only one result*), 100(*safety stop*)
    ]},
 First@
   FileNames[
     __ ~~ FileNameJoin[{"Data", "data1.m"}],
     positionofMain, Infinity
   ]
]

The NestWhile traverses the directory tree upwards towards its root until it finds MainFolder. This gives us a fully qualified path to that folder. We then drill down from there using FileNames looking for files that contain Data\data1.m in their path. The use of OS-independent built-ins like FileNameJoin to introduce the OS-appropriate filename separator should make this reasonably portable across operating systems.

FileNames returns a list, but your problem indicated that the desired file/folder combination is unique, so we can blindly take the first element. That's the fully qualified path to the file you want to load.

$\endgroup$
  • $\begingroup$ Thanks! I really like the use of NestWhile for doing this. It seems that the code you propose brakes if "Data" is a subfolder of MainFolder, while it runs smoothly when "Data" is a subsubfolder. That can probably be fixed using BlankNullSequence ___ instead of BlankSequence __ :D $\endgroup$ – Fraccalo Jun 14 '18 at 20:34
  • $\begingroup$ @Fraccalo Yep I think so too. Glad you like it. $\endgroup$ – MarcoB Jun 14 '18 at 21:26
1
$\begingroup$

Combining what I proposed in the OP, @MarcoB's answer and a new idea I had (using SplitBy, that simplify my original code a bit):

With[{positionofMain = 
   FileNameJoin //@ 
    SplitBy[FileNameSplit[NotebookDirectory[]], # == "MainFolder" &][[
     1 ;; 2]]},
 First@FileNames[___ ~~ FileNameJoin[{"Data", "data1.m"}], 
   positionofMain, Infinity]]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.