Identify a source file and load the associated package and groups if required.

This method does what the user expects when giving an argument to a Nit tool.

  • If path is an existing Nit source file (with the .nit extension), then the associated MModule is returned
  • If path is a directory (with a /), then the MModule of its default module is returned (if any)
  • If path is a simple identifier (eg. digraph), then the main module of the package digraph is searched in paths and returned.

Silently return null if path does not exists or cannot be identified. If null is returned, last_loader_error can be set to a specific error message.

On success, it returns a module that is possibly not yet parsed (no AST), or not yet analysed (no importation). If the module was already identified, or loaded, it is returned.

Property definitions

nitc :: loader $ ModelBuilder :: identify_module
	# Identify a source file and load the associated package and groups if required.
	#
	# This method does what the user expects when giving an argument to a Nit tool.
	#
	# * If `path` is an existing Nit source file (with the `.nit` extension),
	#   then the associated MModule is returned
	# * If `path` is a directory (with a `/`),
	#   then the MModule of its default module is returned (if any)
	# * If `path` is a simple identifier (eg. `digraph`),
	#   then the main module of the package `digraph` is searched in `paths` and returned.
	#
	# Silently return `null` if `path` does not exists or cannot be identified.
	# If `null` is returned, `last_loader_error` can be set to a specific error message.
	#
	# On success, it returns a module that is possibly not yet parsed (no AST), or not yet analysed (no importation).
	# If the module was already identified, or loaded, it is returned.
	fun identify_module(path: String): nullable MModule
	do
		last_loader_error = null

		# special case for not a nit file
		if not path.has_suffix(".nit") then do
			# search dirless files in known -I paths
			if not path.chars.has('/') then
				var res = search_module_in_paths(null, path, self.paths)
				if res != null then return res
			end

			# Found nothing? maybe it is a group...
			if path.file_exists then
				var mgroup = identify_group(path)
				if mgroup != null then
					var owner_path = mgroup.filepath.join_path(mgroup.name + ".nit")
					if owner_path.file_exists then
						path = owner_path
						break
					end
				end
			end

			# Found nothing? maybe it is a qualified name
			if path.chars.has(':') then
				var ids = path.split("::")
				var g = identify_group(ids.first)
				if g != null then
					scan_group(g)
					var ms = g.mmodules_by_name(ids.last)

					# Return exact match
					for m in ms do
						if m.full_name == path then
							return m
						end
					end

					# Where there is only one or two names `foo::bar`
					# then accept module that matches `foo::*::bar`
					if ids.length <= 2 then
						if ms.length == 1 then return ms.first
						if ms.length > 1 then
							var l = new Array[String]
							for m in ms do
								var fp = m.filepath
								if fp != null then fp = " ({fp})" else fp = ""
								l.add "`{m.full_name}`{fp}"
							end
							last_loader_error = "Error: conflicting module for `{path}`: {l.join(", ")} "
							return null
						end
					end

					var bests = new BestDistance[String](path.length / 2)
					# We found nothing. But propose something in the package?
					for sg in g.mpackage.mgroups do
						for m in sg.mmodules do
							var d = path.levenshtein_distance(m.full_name)
							bests.update(d, m.full_name)
						end
					end
					var last_loader_error = "Error: cannot find module `{path}`."
					if bests.best_items.not_empty then
						last_loader_error += " Did you mean " + bests.best_items.join(", ", " or ") + "?"
					end
					self.last_loader_error = last_loader_error
					return null
				end
			end

			return null
		end

		# Does the file exists?
		if not path.file_exists then
			return null
		end

		# Fast track, the path is already known
		if identified_modules_by_path.has_key(path) then return identified_modules_by_path[path]
		var rp = module_absolute_path(path)
		if identified_modules_by_path.has_key(rp) then return identified_modules_by_path[rp]

		var pn = path.basename(".nit")

		# Search for a group
		var mgrouppath = path.join_path("..").simplify_path
		var mgroup = identify_group(mgrouppath)

		if mgroup != null then
			var mpackage = mgroup.mpackage
			if not mpackage.accept(path) then
				mgroup = null
				toolcontext.info("module `{path}` excluded from package `{mpackage}`", 2)
			end
		end
		if mgroup == null then
			# singleton package
			var loc = new Location.opaque_file(path)
			var mpackage = new MPackage(pn, model, loc)
			mgroup = new MGroup(pn, loc, mpackage, null) # same name for the root group
			mpackage.root = mgroup
			toolcontext.info("found singleton package `{pn}` at {path}", 2)

			# Attach homonymous `ini` file to the package
			var inipath = path.dirname / "{pn}.ini"
			if inipath.file_exists then
				var ini = new IniFile.from_file(inipath)
				mpackage.ini = ini
			end
		end

		var loc = new Location.opaque_file(path)
		var res = new MModule(model, mgroup, pn, loc)

		identified_modules_by_path[rp] = res
		identified_modules_by_path[path] = res
		identified_modules.add(res)
		return res
	end
src/loader.nit:355,2--492,4