Skip to content
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

fix issue #8268 (joinPaths); add lots of tests and make use of use runnableExamples #8351

Closed
wants to merge 5 commits into from

Conversation

timotheecour
Copy link
Member

@timotheecour timotheecour commented Jul 18, 2018

/cc @dom96

  • fixes https://github.com/nim-lang/Nim/issues/8268 by adding an optional absOverrides to joinPath (by default, same behavior as before so no breaking change)
  • correctly handles boundary bw head and tail so that there's a unique dirsep between
  • adds normalizePathEnd (also useful in other places, can be refactored later)
  • adds lots of tests (and fixes other things that were broken with joinPaths)
  • add countWhile and rootPrefixLength (simplifies and fixes a lot of code)

eg motivation

joinPath(foo, bar) is often a bug when bar is absolute, for eg:
nimterop/nimterop@a8bb2dc#r31391178

don't joinPath if path.isAbsolute

@timotheecour timotheecour force-pushed the joinPath_abs branch 3 times, most recently from cbc9e4d to fc1ffb0 Compare July 18, 2018 00:17
@Varriount
Copy link
Contributor

Why is normalizePathEnd public? Even if required by other procedures in the os module, it seems somewhat niche.

doAssert joinPath("usr///", "//lib") == "usr/lib" ## `//` gets compressed
doAssert joinPath("//", "lib") == "/lib" ## ditto
when defined(Windows):
doAssert joinPath("""C:\foo""", """D:\bar""") == """C:\foo\bar"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be able to swap the triple quotes out for raw strings:

doAssert joinPath(r"C:\foo", r"D:\bar") == r"C:\foo\bar"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, thx for the tip!

## assert joinPath("usr/", "/lib") == "usr/lib"
## If ``tail`` is absolute or ``head`` is empty, returns ``tail``. If
## ``tail`` is empty returns ``head``. Else, returns the concatenation with
## normalized spearator between ``head`` and ``tail``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: spearator -> separator

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@timotheecour timotheecour force-pushed the joinPath_abs branch 2 times, most recently from 27aca68 to 16eebf9 Compare July 18, 2018 21:58
@timotheecour
Copy link
Member Author

Why is normalizePathEnd public? Even if required by other procedures in the os module, it seems somewhat niche.

I think it's useful, I would want to use it in my code

@timotheecour
Copy link
Member Author

PTAL, now green

@Varriount
Copy link
Contributor

@dom96 What are your thoughts? Aside from my thoughts on normalizePathEnd, this PR looks ready.

@@ -157,64 +158,173 @@ const
## The character which separates the base filename from the extension;
## for example, the '.' in ``os.nim``.

# TODO: MOVE to strutils, sequtils, or algorithms
proc countWhile*(str: string, pred : proc(a:char):bool, prefix = true): int =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use parseutils.parseWhile for this, or just str.len-str.replace("/", "").len

If you want more complicated counting then just use a for loop...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseWhile is not as flexible (eg I need arbitrary lambda; and I need to go from both ends).

str.len-str.replace("/", "").len won't work with my other usages of countWhile.

If you want more complicated counting then just use a for loop...

countWhile leads to simpler code

return lenS

# TODO: once slices are supported, define instead `rootPrefix`
proc rootPrefixLength*(path: string) : int =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once slices are supported? Just return the prefix and call len on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rootPrefixLength avoids allocating. If/when we have slices, this wouldn't be a problem; until then it is.

doAssert r"C:".rootPrefixLength == 2
doAssert r"C:\foo".rootPrefixLength == 3
doAssert r"//foo".rootPrefixLength == 2
doAssert r"C:\\foo".rootPrefixLength == 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm concerned this is abusing runnableExamples as exhaustive testing again. Examples should be one or two (one per platform). Not a long list of edge cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed; moved out from runnableExamples

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed; moved out of runnableExamples

if path[0] == ':':
result = 0
else:
result = countWhile(path, a => a != ':')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is macos pre-mac-os-x only? If it covers latest macOS then this looks wrong to me.

Copy link
Contributor

@haltcase haltcase Jul 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC macos is the legacy classic Mac, macosx is, well, Mac OS X. This is kind of confusing though because since 10.12 Sierra they rebranded it as macOS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this is far too confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is too confusing? the code in PR or macos? I already suggested removing support for macos https://forum.nim-lang.org/t/4039 "should we keep supporting macos? (as opposed to macosx)"

doAssert "/".isAbsolute
doAssert(not "a/".isAbsolute)
when defined(Windows):
doAssert "C:\\foo".isAbsolute
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many examples here too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

## assert joinPath("", "lib") == "lib"
## assert joinPath("", "/lib") == "/lib"
## assert joinPath("usr/", "/lib") == "usr/lib"
## If ``tail`` is absolute and ``absOverrides`` is true, or ``head`` is empty,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure about the use case for this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rationale is given in #8268 : most languages use the behavior absOverrides=true whereas before this PR nim was using the equivalent of absOverrides=false; so with this PR, 0 code is broken but user has option of using absOverrides=true

@dom96
Copy link
Contributor

dom96 commented Jul 19, 2018

I also think that normalizePathEnd is niche and shouldn't be there. I'm unsure about what it's purpose is in joinPath.

@dom96
Copy link
Contributor

dom96 commented Jul 19, 2018

All in all there is too much going on here. I would prefer if you just created a simple PR that fixes the underlying issue instead of introducing two new procedures into the mix, which as far as I'm concerned are not needed. I'm pretty sure for most purposes normalizePathEnd can just be if path[^1] == '/': path[0 .. ^2]

@timotheecour
Copy link
Member Author

timotheecour commented Jul 20, 2018

/cc @Araq @dom96

I'm pretty sure for most purposes normalizePathEnd can just be if path[^1] == '/': path[0 .. ^2]

doesn't work with path = "foo//" and other edge cases. The way I wrote it via normalizePathEnd is robust and can be reused instead of being wrong some of the time (and doing it by hand each time); I found a lot of bugs in ospaths module, common root cause is lack of encapsulation/helper functions like these.

I also think that normalizePathEnd is niche and shouldn't be there. I'm unsure about what it's purpose is in joinPath.

I have other upcoming PR's that fix pre-existing bugs with ospaths that rely on normalizePathEnd, countWhile, rootPrefixLength. Having these helpers makes code simpler, more reusable, less buggy.

I'm willing to address the comments that make sense to me:

  • moving some examples to tos.nim instead of runnableExamples
  • moving countWhile to strutils.nim
  • any other comment that makes sense to me

but I'm not ok with removing these helpers. Let me know if you want me to go ahead with this plan otherwise I'll just apply these fixes in a private library and someone else can fix these issues in ospaths (there are many, see a list #8177 + others I haven't reported).

@Araq
Copy link
Member

Araq commented Jul 20, 2018

but I'm not ok with removing these helpers. Let me know if you want me to go ahead with this plan otherwise I'll just apply these fixes in a private library and someone else can fix these issues in ospaths (there are many, see a list #8177 + others I haven't reported).

I'm not ok that you have to rewrite large parts of mostly working code with your own ideas of how things should be done in Nim/D and leaving a track record of well meaning, but time-consuming RFCs/proposals as a side effect that take away from our manpower. I really appreciate your help, but if in the end your help makes us all more unproductive than we were before, I have to decline it.

Let's assume ospaths.nim has 20 bugs (believe it or not, many people use it effectively, today!). Every fix is maybe 5 lines of code, every fix can be a tiny PR that keeps the CIs green. That means the core dev get to review 20 little PRs that can be accepted in maybe one hour. These PRs also probably wouldn't add new dependencies between the stdlib modules, btw.

Your way of working: Write 20 RFCs instead, half of them about perceived more general language deficiencies. And here is the bad part of it: More/different language features just move problems around most of the time. For example, I don't want to use D's superior const system, I know from experience it's not worth it, given its complexity, it doesn't find enough real bugs. (Same holds for Nim's tags effect system.) I don't want to use D's superior "copy-free" slicing system either, it introduces problematic aliasing.

@timotheecour timotheecour force-pushed the joinPath_abs branch 3 times, most recently from 138c550 to 73c2851 Compare August 27, 2018 00:51
@timotheecour timotheecour changed the title fix issue #8268 (joinPaths);add normalizePathEnd; add lots of tests fix issue #8268 (joinPaths); add lots of tests and make use of use runnableExamples Aug 27, 2018
@timotheecour
Copy link
Member Author

timotheecour commented Aug 27, 2018

/cc @Araq PTAL

  • no new dependency added (removed the import sugar I had added)
  • no new public API (made countWhile private; and incorporated all comments from [TODO] strutils.countWhile: counts while a predicate is true, starting from either end #8673 except s/proc/set/ ; it's private so shd be fine ; I would've preferred if [TODO] strutils.countWhile: counts while a predicate is true, starting from either end #8673 hadn't been closed but, moving on...)
  • runnableExamples kept to reasonable size
  • moved tests to tests/stdlib/tospaths.nim except ones that refer to private symbols
  • some code had to be moved in this PR (eg normalizePathEnd which was already committed from a previous PR) ; you can verify for yourself that the move doesn't introduce changes using git diff --color-moved ; I tried using {.reorder:on.} but it exposed bugs in reorder:on implementation (will send a bug for that) ; also tried using function declaration but also led to other issues; so I just moved up some code.

PS: I'm not gonna attempt defending my POV and instead will focus here on moving forward with this PR

@Araq
Copy link
Member

Araq commented Jan 10, 2019

Needs to be redone, os.nim changed significantly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OS (stdlib) Related to OS modules in standard library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants