Merge branch 'master' into beacons

This commit is contained in:
Grey-Echo 2017-04-19 15:33:24 +02:00
commit 42ec0d6332
361 changed files with 26099 additions and 43083 deletions

View File

@ -3,7 +3,6 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/lua/5.1/bin/luadocumentor.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose\Core\*.lua&quot; &#13;&#10;&quot;Moose\Wrapper\*.lua&quot; &#13;&#10;&quot;Moose\Actions\*.lua&quot; &#13;&#10;&quot;Moose\Functional\*.lua&quot; &#13;&#10;&quot;Moose\Tasking\*.lua&quot; &#13;&#10;&quot;Moose\Utilities\*.lua&quot; &#13;&#10;&quot;Moose\AI\*.lua&quot; &#13;&#10;--dir &quot;${workspace_loc:/Moose_Framework/docs/Documentation}&#13;&#10;--style ${workspace_loc:/Moose_Framework/docs/Stylesheet/stylesheet.css}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Development}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luadocumentor.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Utils}"/>
</launchConfiguration>

View File

@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/lua/5.1/bin/lua.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;D&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@ -3,7 +3,7 @@
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/lua/5.1/bin/lua.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;S&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@ -4,6 +4,5 @@
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;${folder_prompt}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Missions}"/>
</launchConfiguration>

View File

@ -4,62 +4,22 @@
--
-- ![Banner Image](..\Presentations\AI_Balancer\Dia1.JPG)
--
-- ===
-- ====
--
-- # 1) @{AI_Balancer#AI_BALANCER} class, extends @{Fsm#FSM_SET}
-- # Demo Missions
--
-- The @{AI_Balancer#AI_BALANCER} class monitors and manages as many replacement AI groups as there are
-- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
-- ### [AI_BALANCER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
--
-- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
-- ### [AI_BALANCER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
-- ====
--
-- * **@{#AI_BALANCER.OnAfterSpawned}**( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
-- # YouTube Channel
--
-- ## 1.1) AI_BALANCER construction
-- ### [AI_BALANCER YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
--
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
--
-- ## 1.2) AI_BALANCER is a FSM
--
-- ![Process](..\Presentations\AI_Balancer\Dia13.JPG)
--
-- ### 1.2.1) AI_BALANCER States
--
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
--
-- ### 1.2.2) AI_BALANCER Events
--
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
--
-- ## 1.3) AI_BALANCER spawn interval for replacement AI
--
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
--
-- ## 1.4) AI_BALANCER returns AI to Airbases
--
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
-- When a human player joins a slot, you can configure to let the AI return to:
--
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}.
--
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
--
-- ===
--
-- # **API CHANGE HISTORY**
@ -90,12 +50,68 @@
--
-- @module AI_Balancer
--- AI_BALANCER class
-- @type AI_BALANCER
--- @type AI_BALANCER
-- @field Core.Set#SET_CLIENT SetClient
-- @field Functional.Spawn#SPAWN SpawnAI
-- @field Wrapper.Group#GROUP Test
-- @extends Core.Fsm#FSM_SET
--- # AI_BALANCER class, extends @{Fsm#FSM_SET}
--
-- The AI_BALANCER class monitors and manages as many replacement AI groups as there are
-- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
--
-- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
--
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
--
-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
--
-- ## 1. AI_BALANCER construction
--
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
--
-- ## 2. AI_BALANCER is a FSM
--
-- ![Process](..\Presentations\AI_Balancer\Dia13.JPG)
--
-- ### 2.1. AI_BALANCER States
--
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
--
-- ### 2.2. AI_BALANCER Events
--
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
--
-- ## 3. AI_BALANCER spawn interval for replacement AI
--
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
--
-- ## 4. AI_BALANCER returns AI to Airbases
--
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
-- When a human player joins a slot, you can configure to let the AI return to:
--
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}.
--
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
--
-- @field #AI_BALANCER
AI_BALANCER = {
ClassName = "AI_BALANCER",
PatrolZones = {},

View File

@ -11,6 +11,22 @@
-- * @{#AI_CAP_ZONE}: Perform a CAP in a zone.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_CAP Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol)
--
-- ### [AI_CAP Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAP%20-%20Combat%20Air%20Patrol)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [AI_CAP YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L)
--
-- ====
--
-- # **API CHANGE HISTORY**
--
@ -48,9 +64,9 @@
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- # 1) @{#AI_CAP_ZONE} class, extends @{AI_CAP#AI_PATROL_ZONE}
--- # AI_CAP_ZONE class, extends @{AI_CAP#AI_PATROL_ZONE}
--
-- The @{#AI_CAP_ZONE} class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}
-- The AI_CAP_ZONE class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
@ -81,22 +97,22 @@
--
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
--
-- ## 1.1) AI_CAP_ZONE constructor
-- ## 1. AI_CAP_ZONE constructor
--
-- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object.
--
-- ## 1.2) AI_CAP_ZONE is a FSM
-- ## 2. AI_CAP_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_CAP\Dia2.JPG)
--
-- ### 1.2.1) AI_CAP_ZONE States
-- ### 2.1 AI_CAP_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 1.2.2) AI_CAP_ZONE Events
-- ### 2.2 AI_CAP_ZONE Events
--
-- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
@ -109,7 +125,7 @@
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 1.3) Set the Range of Engagement
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_CAP\Dia11.JPG)
--
@ -119,7 +135,7 @@
-- The range is applied at the position of the AI.
-- Use the method @{AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range.
--
-- ## 1.4) Set the Zone of Engagement
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
@ -129,8 +145,7 @@
--
-- ===
--
-- @field #AI_CAP_ZONE AI_CAP_ZONE
--
-- @field #AI_CAP_ZONE
AI_CAP_ZONE = {
ClassName = "AI_CAP_ZONE",
}

View File

@ -10,6 +10,22 @@
--
-- * @{#AI_CAS_ZONE}: Perform a CAS in a zone.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_CAS Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support)
--
-- ### [AI_CAS Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAS%20-%20Close%20Air%20Support)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [AI_CAS YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
--
-- ===
--
-- # **API CHANGE HISTORY**
@ -46,11 +62,11 @@
-- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed.
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- # 1) @{#AI_CAS_ZONE} class, extends @{AI_Patrol#AI_PATROL_ZONE}
--- # AI_CAS_ZONE class, extends @{AI_Patrol#AI_PATROL_ZONE}
--
-- @{#AI_CAS_ZONE} derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour.
-- AI_CAS_ZONE derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour.
--
-- The @{#AI_CAS_ZONE} class implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Controllable} or @{Group}.
-- The AI_CAS_ZONE class implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Controllable} or @{Group}.
-- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG)
@ -104,22 +120,22 @@
--
-- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG)
--
-- # 1.1) AI_CAS_ZONE constructor
-- # 1. AI_CAS_ZONE constructor
--
-- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object.
--
-- ## 1.2) AI_CAS_ZONE is a FSM
-- ## 2. AI_CAS_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_CAS\Dia2.JPG)
--
-- ### 1.2.1) AI_CAS_ZONE States
-- ### 2.1. AI_CAS_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 1.2.2) AI_CAS_ZONE Events
-- ### 2.2. AI_CAS_ZONE Events
--
-- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
@ -134,8 +150,7 @@
--
-- ===
--
-- @field #AI_CAS_ZONE AI_CAS_ZONE
--
-- @field #AI_CAS_ZONE
AI_CAS_ZONE = {
ClassName = "AI_CAS_ZONE",
}

View File

@ -12,6 +12,22 @@
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_PATROL Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
--
-- ### [AI_PATROL Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [AI_PATROL YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
--
-- ====
--
-- # **OPEN ISSUES**
--
-- 2017-01-17: When Spawned AI is located at an airbase, it will be routed first back to the airbase after take-off.
@ -64,9 +80,9 @@
-- @field Functional.Spawn#SPAWN CoordTest
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # 1) @{#AI_PATROL_ZONE} class, extends @{Fsm#FSM_CONTROLLABLE}
--- # AI_PATROL_ZONE class, extends @{Fsm#FSM_CONTROLLABLE}
--
-- The @{#AI_PATROL_ZONE} class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}.
-- The AI_PATROL_ZONE class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}.
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
@ -97,15 +113,15 @@
--
-- ![Process](..\Presentations\AI_PATROL\Dia11.JPG)
--
-- ## 1.1) AI_PATROL_ZONE constructor
-- ## 1. AI_PATROL_ZONE constructor
--
-- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object.
--
-- ## 1.2) AI_PATROL_ZONE is a FSM
-- ## 2. AI_PATROL_ZONE is a FSM
--
-- ![Process](..\Presentations\AI_PATROL\Dia2.JPG)
--
-- ### 1.2.1) AI_PATROL_ZONE States
-- ### 2.1. AI_PATROL_ZONE States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
@ -113,7 +129,7 @@
-- * **Stopped** ( Group ): The process is stopped.
-- * **Crashed** ( Group ): The AI has crashed or is dead.
--
-- ### 1.2.2) AI_PATROL_ZONE Events
-- ### 2.2. AI_PATROL_ZONE Events
--
-- * **Start** ( Group ): Start the process.
-- * **Stop** ( Group ): Stop the process.
@ -123,17 +139,17 @@
-- * **Detected** ( Group ): The AI has detected new targets.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 1.3) Set or Get the AI controllable
-- ## 3. Set or Get the AI controllable
--
-- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable.
-- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable.
--
-- ## 1.4) Set the Speed and Altitude boundaries of the AI controllable
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
--
-- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
-- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
--
-- ## 1.5) Manage the detection process of the AI controllable
-- ## 5. Manage the detection process of the AI controllable
--
-- The detection process of the AI controllable can be manipulated.
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
@ -150,7 +166,7 @@
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
-- according the weather conditions.
--
-- ## 1.6) Manage the "out of fuel" in the AI_PATROL_ZONE
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
--
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
@ -159,7 +175,7 @@
-- Once the time is finished, the old AI will return to the base.
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place.
--
-- ## 1.7) Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
--
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
@ -167,8 +183,7 @@
--
-- ===
--
-- @field #AI_PATROL_ZONE AI_PATROL_ZONE
--
-- @field #AI_PATROL_ZONE
AI_PATROL_ZONE = {
ClassName = "AI_PATROL_ZONE",
}

View File

@ -219,16 +219,17 @@ local _ClassID = 0
BASE = {
ClassName = "BASE",
ClassID = 0,
_Private = {},
Events = {},
States = {}
States = {},
_ = {},
}
--- The Formation Class
-- @type FORMATION
-- @field Cone A cone formation.
FORMATION = {
Cone = "Cone"
Cone = "Cone",
Vee = "Vee"
}
@ -360,7 +361,7 @@ do -- Event Handling
-- @param #BASE self
-- @return #number The @{Event} processing Priority.
function BASE:GetEventPriority()
return self._Private.EventPriority or 5
return self._.EventPriority or 5
end
--- Set the Class @{Event} processing Priority.
@ -370,7 +371,7 @@ do -- Event Handling
-- @param #number EventPriority The @{Event} processing Priority.
-- @return self
function BASE:SetEventPriority( EventPriority )
self._Private.EventPriority = EventPriority
self._.EventPriority = EventPriority
end
--- Remove all subscribed events

View File

@ -6,12 +6,14 @@
-- ===================================================
-- Mission designers can use the DATABASE class to refer to:
--
-- * STATICS
-- * UNITS
-- * GROUPS
-- * CLIENTS
-- * AIRPORTS
-- * AIRBASES
-- * PLAYERSJOINED
-- * PLAYERS
-- * CARGOS
--
-- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.
--
@ -44,6 +46,7 @@ DATABASE = {
Templates = {
Units = {},
Groups = {},
Statics = {},
ClientsByName = {},
ClientsByID = {},
},
@ -53,6 +56,7 @@ DATABASE = {
PLAYERS = {},
PLAYERSJOINED = {},
CLIENTS = {},
CARGOS = {},
AIRBASES = {},
COUNTRY_ID = {},
COUNTRY_NAME = {},
@ -84,13 +88,15 @@ local _DATABASECategory =
function DATABASE:New()
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE
self:SetEventPriority( 1 )
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
-- Follow alive players and clients
self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit )
@ -166,22 +172,24 @@ end
--- Adds a Airbase based on the Airbase Name in the DATABASE.
-- @param #DATABASE self
function DATABASE:AddAirbase( DCSAirbaseName )
-- @param #string AirbaseName The name of the airbase
function DATABASE:AddAirbase( AirbaseName )
if not self.AIRBASES[DCSAirbaseName] then
self.AIRBASES[DCSAirbaseName] = AIRBASE:Register( DCSAirbaseName )
if not self.AIRBASES[AirbaseName] then
self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName )
end
end
--- Deletes a Airbase from the DATABASE based on the Airbase Name.
-- @param #DATABASE self
function DATABASE:DeleteAirbase( DCSAirbaseName )
-- @param #string AirbaseName The name of the airbase
function DATABASE:DeleteAirbase( AirbaseName )
--self.AIRBASES[DCSAirbaseName] = nil
self.AIRBASES[AirbaseName] = nil
end
--- Finds a AIRBASE based on the AirbaseName.
--- Finds an AIRBASE based on the AirbaseName.
-- @param #DATABASE self
-- @param #string AirbaseName
-- @return Wrapper.Airbase#AIRBASE The found AIRBASE.
@ -191,6 +199,35 @@ function DATABASE:FindAirbase( AirbaseName )
return AirbaseFound
end
--- Adds a Cargo based on the Cargo Name in the DATABASE.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:AddCargo( Cargo )
if not self.CARGOS[Cargo.Name] then
self.CARGOS[Cargo.Name] = Cargo
end
end
--- Deletes a Cargo from the DATABASE based on the Cargo Name.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:DeleteCargo( CargoName )
self.CARGOS[CargoName] = nil
end
--- Finds an CARGO based on the CargoName.
-- @param #DATABASE self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found CARGO.
function DATABASE:FindCargo( CargoName )
local CargoFound = self.CARGOS[CargoName]
return CargoFound
end
--- Finds a CLIENT based on the ClientName.
-- @param #DATABASE self
@ -282,7 +319,7 @@ function DATABASE:Spawn( SpawnTemplate )
SpawnTemplate.CountryID = nil
SpawnTemplate.CategoryID = nil
self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID )
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID )
self:T3( SpawnTemplate )
coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
@ -318,7 +355,7 @@ end
-- @param #DATABASE self
-- @param #table GroupTemplate
-- @return #DATABASE self
function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID )
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID )
local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name)
@ -396,6 +433,54 @@ function DATABASE:GetGroupTemplate( GroupName )
return GroupTemplate
end
--- Private method that registers new Static Templates within the DATABASE Object.
-- @param #DATABASE self
-- @param #table GroupTemplate
-- @return #DATABASE self
function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
local TraceTable = {}
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name)
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
StaticTemplate.CategoryID = CategoryID
StaticTemplate.CoalitionID = CoalitionID
StaticTemplate.CountryID = CountryID
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName
self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate
self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1]
self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID
self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID
self.Templates.Statics[StaticTemplateName].CountryID = CountryID
TraceTable[#TraceTable+1] = "Static"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].GroupName
TraceTable[#TraceTable+1] = "Coalition"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CoalitionID
TraceTable[#TraceTable+1] = "Category"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CategoryID
TraceTable[#TraceTable+1] = "Country"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CountryID
self:E( TraceTable )
end
--- @param #DATABASE self
function DATABASE:GetStaticUnitTemplate( StaticName )
local StaticTemplate = self.Templates.Statics[StaticName].UnitTemplate
StaticTemplate.SpawnCoalitionID = self.Templates.Statics[StaticName].CoalitionID
StaticTemplate.SpawnCategoryID = self.Templates.Statics[StaticName].CategoryID
StaticTemplate.SpawnCountryID = self.Templates.Statics[StaticName].CountryID
return StaticTemplate
end
function DATABASE:GetGroupNameFromUnitName( UnitName )
return self.Templates.Units[UnitName].GroupName
end
@ -665,7 +750,7 @@ end
--- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters.
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the database. The function needs to accept a UNIT parameter.
-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter.
-- @return #DATABASE self
function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
@ -677,7 +762,7 @@ end
--- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters.
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the database. The function needs to accept a GROUP parameter.
-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a GROUP parameter.
-- @return #DATABASE self
function DATABASE:ForEachGroup( IteratorFunction, ... )
self:F2( arg )
@ -690,7 +775,7 @@ end
--- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters.
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called when there is an player in the database. The function needs to accept the player name.
-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept the player name.
-- @return #DATABASE self
function DATABASE:ForEachPlayer( IteratorFunction, ... )
self:F2( arg )
@ -703,7 +788,7 @@ end
--- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters.
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called when there is was a player in the database. The function needs to accept a UNIT parameter.
-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter.
-- @return #DATABASE self
function DATABASE:ForEachPlayerJoined( IteratorFunction, ... )
self:F2( arg )
@ -715,7 +800,7 @@ end
--- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters.
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter.
-- @param #function IteratorFunction The function that will be called object in the database. The function needs to accept a CLIENT parameter.
-- @return #DATABASE self
function DATABASE:ForEachClient( IteratorFunction, ... )
self:F2( arg )
@ -725,7 +810,44 @@ function DATABASE:ForEachClient( IteratorFunction, ... )
return self
end
--- Iterate the DATABASE and call an iterator function for each CARGO, providing the CARGO object to the function and optional parameters.
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a CLIENT parameter.
-- @return #DATABASE self
function DATABASE:ForEachCargo( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self.CARGOS )
return self
end
--- Handles the OnEventNewCargo event.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA EventData
function DATABASE:OnEventNewCargo( EventData )
self:F2( { EventData } )
if EventData.Cargo then
self:AddCargo( EventData.Cargo )
end
end
--- Handles the OnEventDeleteCargo.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA EventData
function DATABASE:OnEventDeleteCargo( EventData )
self:F2( { EventData } )
if EventData.Cargo then
self:DeleteCargo( EventData.Cargo.Name )
end
end
--- @param #DATABASE self
function DATABASE:_RegisterTemplates()
self:F2()
@ -781,11 +903,18 @@ function DATABASE:_RegisterTemplates()
--self.Units[coa_name][countryName][category] = {}
for group_num, GroupTemplate in pairs(obj_type_data.group) do
for group_num, Template in pairs(obj_type_data.group) do
if GroupTemplate and GroupTemplate.units and type(GroupTemplate.units) == 'table' then --making sure again- this is a valid group
self:_RegisterTemplate(
GroupTemplate,
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
self:_RegisterGroupTemplate(
Template,
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID
)
else
self:_RegisterStaticTemplate(
Template,
CoalitionSide,
_DATABASECategory[string.lower(CategoryName)],
CountryID

View File

@ -197,6 +197,9 @@ EVENT = {
ClassID = 0,
}
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001
--- The different types of events supported by MOOSE.
-- Use this structure to subscribe to events using the @{Base#BASE.HandleEvent}() method.
-- @type EVENTS
@ -224,13 +227,15 @@ EVENTS = {
PlayerComment = world.event.S_EVENT_PLAYER_COMMENT,
ShootingStart = world.event.S_EVENT_SHOOTING_START,
ShootingEnd = world.event.S_EVENT_SHOOTING_END,
NewCargo = world.event.S_EVENT_NEW_CARGO,
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
}
--- The Event structure
-- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event:
--
-- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event.
-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ
-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ
--
-- @type EVENTDATA
-- @field #number id The identifier of the event.
@ -271,122 +276,156 @@ EVENTS = {
-- @field WeaponTgtDCSUnit
local _EVENTMETA = {
[world.event.S_EVENT_SHOT] = {
Order = 1,
Side = "I",
Event = "OnEventShot",
Text = "S_EVENT_SHOT"
},
[world.event.S_EVENT_HIT] = {
Order = 1,
Side = "T",
Event = "OnEventHit",
Text = "S_EVENT_HIT"
},
[world.event.S_EVENT_TAKEOFF] = {
Order = 1,
Side = "I",
Event = "OnEventTakeoff",
Text = "S_EVENT_TAKEOFF"
},
[world.event.S_EVENT_LAND] = {
Order = 1,
Side = "I",
Event = "OnEventLand",
Text = "S_EVENT_LAND"
},
[world.event.S_EVENT_CRASH] = {
Order = -1,
Side = "I",
Event = "OnEventCrash",
Text = "S_EVENT_CRASH"
},
[world.event.S_EVENT_EJECTION] = {
Order = 1,
Side = "I",
Event = "OnEventEjection",
Text = "S_EVENT_EJECTION"
},
[world.event.S_EVENT_REFUELING] = {
Order = 1,
Side = "I",
Event = "OnEventRefueling",
Text = "S_EVENT_REFUELING"
},
[world.event.S_EVENT_DEAD] = {
Order = -1,
Side = "I",
Event = "OnEventDead",
Text = "S_EVENT_DEAD"
},
[world.event.S_EVENT_PILOT_DEAD] = {
Order = 1,
Side = "I",
Event = "OnEventPilotDead",
Text = "S_EVENT_PILOT_DEAD"
},
[world.event.S_EVENT_BASE_CAPTURED] = {
Order = 1,
Side = "I",
Event = "OnEventBaseCaptured",
Text = "S_EVENT_BASE_CAPTURED"
},
[world.event.S_EVENT_MISSION_START] = {
Order = 1,
Side = "N",
Event = "OnEventMissionStart",
Text = "S_EVENT_MISSION_START"
},
[world.event.S_EVENT_MISSION_END] = {
Order = 1,
Side = "N",
Event = "OnEventMissionEnd",
Text = "S_EVENT_MISSION_END"
},
[world.event.S_EVENT_TOOK_CONTROL] = {
Order = 1,
Side = "N",
Event = "OnEventTookControl",
Text = "S_EVENT_TOOK_CONTROL"
},
[world.event.S_EVENT_REFUELING_STOP] = {
Order = 1,
Side = "I",
Event = "OnEventRefuelingStop",
Text = "S_EVENT_REFUELING_STOP"
},
[world.event.S_EVENT_BIRTH] = {
Order = 1,
Side = "I",
Event = "OnEventBirth",
Text = "S_EVENT_BIRTH"
},
[world.event.S_EVENT_HUMAN_FAILURE] = {
Order = 1,
Side = "I",
Event = "OnEventHumanFailure",
Text = "S_EVENT_HUMAN_FAILURE"
},
[world.event.S_EVENT_ENGINE_STARTUP] = {
Order = 1,
Side = "I",
Event = "OnEventEngineStartup",
Text = "S_EVENT_ENGINE_STARTUP"
},
[world.event.S_EVENT_ENGINE_SHUTDOWN] = {
Order = 1,
Side = "I",
Event = "OnEventEngineShutdown",
Text = "S_EVENT_ENGINE_SHUTDOWN"
},
[world.event.S_EVENT_PLAYER_ENTER_UNIT] = {
Order = 1,
Side = "I",
Event = "OnEventPlayerEnterUnit",
Text = "S_EVENT_PLAYER_ENTER_UNIT"
},
[world.event.S_EVENT_PLAYER_LEAVE_UNIT] = {
Order = -1,
Side = "I",
Event = "OnEventPlayerLeaveUnit",
Text = "S_EVENT_PLAYER_LEAVE_UNIT"
},
[world.event.S_EVENT_PLAYER_COMMENT] = {
Order = 1,
Side = "I",
Event = "OnEventPlayerComment",
Text = "S_EVENT_PLAYER_COMMENT"
},
[world.event.S_EVENT_SHOOTING_START] = {
Order = 1,
Side = "I",
Event = "OnEventShootingStart",
Text = "S_EVENT_SHOOTING_START"
},
[world.event.S_EVENT_SHOOTING_END] = {
Order = 1,
Side = "I",
Event = "OnEventShootingEnd",
Text = "S_EVENT_SHOOTING_END"
},
[EVENTS.NewCargo] = {
Order = 1,
Event = "OnEventNewCargo",
Text = "S_EVENT_NEW_CARGO"
},
[EVENTS.DeleteCargo] = {
Order = 1,
Event = "OnEventDeleteCargo",
Text = "S_EVENT_DELETE_CARGO"
},
}
@ -401,13 +440,6 @@ function EVENT:New()
return self
end
function EVENT:EventText( EventID )
local EventText = _EVENTMETA[EventID].Text
return EventText
end
--- Initializes the Events structure for the event
-- @param #EVENT self
@ -419,7 +451,7 @@ function EVENT:Init( EventID, EventClass )
if not self.Events[EventID] then
-- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned.
self.Events[EventID] = setmetatable( {}, { __mode = "k" } )
self.Events[EventID] = {}
end
-- Each event has a subtable of EventClasses, ordered by EventPriority.
@ -429,53 +461,56 @@ function EVENT:Init( EventID, EventClass )
end
if not self.Events[EventID][EventPriority][EventClass] then
self.Events[EventID][EventPriority][EventClass] = setmetatable( {}, { __mode = "v" } )
self.Events[EventID][EventPriority][EventClass] = {}
end
return self.Events[EventID][EventPriority][EventClass]
end
--- Removes an Events entry
--- Removes a subscription
-- @param #EVENT self
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param Dcs.DCSWorld#world.event EventID
-- @return #EVENT.Events
function EVENT:Remove( EventClass, EventID )
self:F3( { EventClass, _EVENTMETA[EventID].Text } )
local EventClass = EventClass
self:E( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } )
local EventPriority = EventClass:GetEventPriority()
self.EventsDead = self.EventsDead or {}
self.EventsDead[EventID] = self.EventsDead[EventID] or {}
self.EventsDead[EventID][EventPriority] = self.EventsDead[EventID][EventPriority] or {}
self.EventsDead[EventID][EventPriority][EventClass] = self.Events[EventID][EventPriority][EventClass]
self.Events[EventID][EventPriority][EventClass] = nil
end
--- Removes an Events entry for a UNIT.
--- Resets subscriptions
-- @param #EVENT self
-- @param #string UnitName The name of the UNIT.
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param Dcs.DCSWorld#world.event EventID
-- @return #EVENT.Events
function EVENT:RemoveForUnit( UnitName, EventClass, EventID )
self:F3( { EventClass, _EVENTMETA[EventID].Text } )
function EVENT:Reset( EventObject )
local EventClass = EventClass
local EventPriority = EventClass:GetEventPriority()
local Event = self.Events[EventID][EventPriority][EventClass]
Event.EventUnit[UnitName] = nil
self:E( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } )
local EventPriority = EventObject:GetEventPriority()
for EventID, EventData in pairs( self.Events ) do
if self.EventsDead then
if self.EventsDead[EventID] then
if self.EventsDead[EventID][EventPriority] then
if self.EventsDead[EventID][EventPriority][EventObject] then
self.Events[EventID][EventPriority][EventObject] = self.EventsDead[EventID][EventPriority][EventObject]
end
end
end
end
end
end
--- Removes an Events entry for a GROUP.
-- @param #EVENT self
-- @param #string GroupName The name of the GROUP.
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param Dcs.DCSWorld#world.event EventID
-- @return #EVENT.Events
function EVENT:RemoveForGroup( GroupName, EventClass, EventID )
self:F3( { EventClass, _EVENTMETA[EventID].Text } )
local EventClass = EventClass
local EventPriority = EventClass:GetEventPriority()
local Event = self.Events[EventID][EventPriority][EventClass]
Event.EventGroup[GroupName] = nil
end
--- Clears all event subscriptions for a @{Base#BASE} derived object.
-- @param #EVENT self
@ -519,7 +554,6 @@ function EVENT:OnEventGeneric( EventFunction, EventClass, EventID )
local EventData = self:Init( EventID, EventClass )
EventData.EventFunction = EventFunction
EventData.EventClass = EventClass
return self
end
@ -536,12 +570,8 @@ function EVENT:OnEventForUnit( UnitName, EventFunction, EventClass, EventID )
self:F2( UnitName )
local EventData = self:Init( EventID, EventClass )
if not EventData.EventUnit then
EventData.EventUnit = {}
end
EventData.EventUnit[UnitName] = {}
EventData.EventUnit[UnitName].EventFunction = EventFunction
EventData.EventUnit[UnitName].EventClass = EventClass
EventData.EventUnit = true
EventData.EventFunction = EventFunction
return self
end
@ -556,12 +586,8 @@ function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID )
self:F2( GroupName )
local Event = self:Init( EventID, EventClass )
if not Event.EventGroup then
Event.EventGroup = {}
end
Event.EventGroup[GroupName] = {}
Event.EventGroup[GroupName].EventFunction = EventFunction
Event.EventGroup[GroupName].EventClass = EventClass
Event.EventGroup = true
Event.EventFunction = EventFunction
return self
end
@ -672,6 +698,39 @@ do -- OnEngineShutDown
end
do -- Event Creation
--- Creation of a New Cargo Event.
-- @param #EVENT self
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
function EVENT:CreateEventNewCargo( Cargo )
self:F( { Cargo } )
local Event = {
id = EVENTS.NewCargo,
time = timer.getTime(),
cargo = Cargo,
}
world.onEvent( Event )
end
--- Creation of a Cargo Deletion Event.
-- @param #EVENT self
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
function EVENT:CreateEventDeleteCargo( Cargo )
self:F( { Cargo } )
local Event = {
id = EVENTS.DeleteCargo,
time = timer.getTime(),
cargo = Cargo,
}
world.onEvent( Event )
end
end
--- @param #EVENT self
-- @param #EVENTDATA Event
@ -687,10 +746,10 @@ function EVENT:onEvent( Event )
return errmsg
end
self:E( _EVENTMETA[Event.id].Text, Event )
local EventMeta = _EVENTMETA[Event.id]
if self and self.Events and self.Events[Event.id] then
if Event.initiator then
@ -795,12 +854,17 @@ function EVENT:onEvent( Event )
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
end
local PriorityOrder = _EVENTMETA[Event.id].Order
if Event.cargo then
Event.Cargo = Event.cargo
Event.CargoName = Event.cargo.Name
end
local PriorityOrder = EventMeta.Order
local PriorityBegin = PriorityOrder == -1 and 5 or 1
local PriorityEnd = PriorityOrder == -1 and 1 or 5
if Event.IniObjectCategory ~= 3 then
self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } )
self:E( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } )
end
for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do
@ -810,186 +874,144 @@ function EVENT:onEvent( Event )
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
-- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT.
if ( Event.IniDCSUnitName and EventData.EventUnit and EventData.EventUnit[Event.IniDCSUnitName] ) or
( Event.TgtDCSUnitName and EventData.EventUnit and EventData.EventUnit[Event.TgtDCSUnitName] ) then
if EventData.EventUnit then
if EventData.EventUnit[Event.IniDCSUnitName] then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventUnit[Event.IniDCSUnitName].EventFunction then
-- So now the EventClass must be a UNIT class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead then
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventUnit[Event.IniDCSUnitName].EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
local UnitName = EventClass:GetName()
if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or
( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
if EventData.EventUnit[Event.TgtDCSUnitName] then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventUnit[Event.TgtDCSUnitName].EventFunction then
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventUnit[Event.TgtDCSUnitName].EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
self:Remove( EventClass, Event.id )
end
else
-- If the EventData is for a GROUP, the call directly the EventClass EventFunction for the UNIT in that GROUP.
if ( Event.IniDCSUnitName and Event.IniDCSGroupName and Event.IniGroupName and EventData.EventGroup and EventData.EventGroup[Event.IniGroupName] ) or
( Event.TgtDCSUnitName and Event.TgtDCSGroupName and Event.TgtGroupName and EventData.EventGroup and EventData.EventGroup[Event.TgtGroupName] ) then
if EventData.EventGroup then
if EventData.EventGroup[Event.IniGroupName] then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventGroup[Event.IniGroupName].EventFunction then
-- So now the EventClass must be a GROUP class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead then
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventGroup[Event.IniGroupName].EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
-- We can get the name of the EventClass, which is now always a GROUP object.
local GroupName = EventClass:GetName()
if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or
( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then
if EventData.EventGroup[Event.TgtGroupName] then
if EventData.EventGroup[Event.TgtGroupName].EventFunction then
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventGroup[Event.TgtGroupName].EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
-- If the EventData is not bound to a specific unit, then call the EventClass EventFunction.
-- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon.
if (Event.IniDCSUnit or Event.WeaponUNIT) and not EventData.EventUnit then
if EventClass == EventData.EventClass then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
-- There is an EventFunction defined, so call the EventFunction.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ]
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
self:E( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
local Result, Value = EventFunction( EventClass, Event )
return Result, Value
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
self:Remove( EventClass, Event.id )
end
else
-- If the EventData is not bound to a specific unit, then call the EventClass EventFunction.
-- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon.
if not EventData.EventUnit then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
-- There is an EventFunction defined, so call the EventFunction.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
local EventFunction = EventClass[ EventMeta.Event ]
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:E( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
local Result, Value = EventFunction( EventClass, Event )
return Result, Value
end, ErrorHandler )
end
end
end
end
end
@ -997,7 +1019,7 @@ function EVENT:onEvent( Event )
end
end
else
self:E( { _EVENTMETA[Event.id].Text, Event } )
self:E( { EventMeta.Text, Event } )
end
Event = nil

View File

@ -5,6 +5,8 @@
--
-- ===
--
-- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**.
--
-- A FSM can only be in one of a finite number of states.
-- The machine is in only one state at a time; the state it is in at any given time is called the **current state**.
-- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**.
@ -90,8 +92,43 @@ do -- FSM
-- @extends Core.Base#BASE
--- # 1) FSM class, extends @{Base#BASE}
--- # FSM class, extends @{Base#BASE}
--
-- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**.
--
-- A FSM can only be in one of a finite number of states.
-- The machine is in only one state at a time; the state it is in at any given time is called the **current state**.
-- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**.
-- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**.
-- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions.
--
-- The FSM class supports a **hierarchical implementation of a Finite State Machine**,
-- that is, it allows to **embed existing FSM implementations in a master FSM**.
-- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes.
--
-- ![Workflow Example](..\Presentations\FSM\Dia2.JPG)
--
-- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone,
-- orders him to destroy x targets and account the results.
-- Other examples of ready made FSM could be:
--
-- * route a plane to a zone flown by a human
-- * detect targets by an AI and report to humans
-- * account for destroyed targets by human players
-- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle
-- * let an AI patrol a zone
--
-- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes,
-- because **the goal of MOOSE is to simplify mission design complexity for mission building**.
-- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes.
-- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used,
-- and tailored** by mission designers through **the implementation of Transition Handlers**.
-- Each of these FSM implementation classes start either with:
--
-- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class.
-- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class.
-- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class.
--
-- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG)
--
-- The FSM class is the base class of all FSM\_ derived classes. It implements the main functionality to define and execute Finite State Machines.
@ -114,13 +151,13 @@ do -- FSM
-- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation.
-- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**.
--
-- ## 1.1) FSM Linear Transitions
-- ## FSM Linear Transitions
--
-- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**.
-- The Lineair transition rule evaluation will always be done from the **current state** of the FSM.
-- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop.
--
-- ### 1.1.1) FSM Transition Rules
-- ### FSM Transition Rules
--
-- The FSM has transition rules that it follows and validates, as it walks the process.
-- These rules define when an FSM can transition from a specific state towards an other specific state upon a triggered event.
@ -145,7 +182,7 @@ do -- FSM
-- * It can be switched **Off** by triggering event **SwitchOff**.
-- * Note that once the Switch is **On** or **Middle**, it can only be switched **Off**.
--
-- ### Some additional comments:
-- #### Some additional comments:
--
-- Note that Linear Transition Rules **can be declared in a few variations**:
--
@ -156,7 +193,7 @@ do -- FSM
--
-- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" )
--
-- ### 1.1.2) Transition Handling
-- ### Transition Handling
--
-- ![Transition Handlers](..\Presentations\FSM\Dia4.JPG)
--
@ -178,7 +215,7 @@ do -- FSM
--
-- On top, each of these methods can have a variable amount of parameters passed. See the example in section [1.1.3](#1.1.3\)-event-triggers).
--
-- ### 1.1.3) Event Triggers
-- ### Event Triggers
--
-- ![Event Triggers](..\Presentations\FSM\Dia5.JPG)
--
@ -216,7 +253,7 @@ do -- FSM
--
-- Because ... When Event was asynchronously processed after 5 seconds, Amount was set to 2. So be careful when processing and passing values and objects in asynchronous processing!
--
-- ### 1.1.4) Linear Transition Example
-- ### Linear Transition Example
--
-- This example is fully implemented in the MOOSE test mission on GITHUB: [FSM-100 - Transition Explanation](https://github.com/FlightControl-Master/MOOSE/blob/master/Moose%20Test%20Missions/FSM%20-%20Finite%20State%20Machine/FSM-100%20-%20Transition%20Explanation/FSM-100%20-%20Transition%20Explanation.lua)
--
@ -298,7 +335,7 @@ do -- FSM
-- So... When FsmDemo:Stop() is being triggered, the state of FsmDemo will transition from Red or Green to Stopped.
-- And there is no transition handling method defined for that transition, thus, no new event is being triggered causing the FsmDemo process flow to halt.
--
-- ## 1.5) FSM Hierarchical Transitions
-- ## FSM Hierarchical Transitions
--
-- Hierarchical Transitions allow to re-use readily available and implemented FSMs.
-- This becomes in very useful for mission building, where mission designers build complex processes and workflows,

View File

@ -27,109 +27,26 @@
--
-- ===
--
-- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these):
-- # **AUTHORS and CONTRIBUTIONS**
--
-- 1) MENU_ BASE abstract base classes (don't use them)
-- ====================================================
-- The underlying base menu classes are **NOT** to be used within your missions.
-- These are simply abstract base classes defining a couple of fields that are used by the
-- derived MENU_ classes to manage menus.
-- ### Contributions:
--
-- 1.1) @{#MENU_BASE} class, extends @{Base#BASE}
-- --------------------------------------------------
-- The @{#MENU_BASE} class defines the main MENU class where other MENU classes are derived from.
--
-- 1.2) @{#MENU_COMMAND_BASE} class, extends @{Base#BASE}
-- ----------------------------------------------------------
-- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands.
--
-- ===
--
-- **The next menus define the MENU classes that you can use within your missions.**
--
-- 2) MENU MISSION classes
-- ======================
-- The underlying classes manage the menus for a complete mission file.
--
-- 2.1) @{#MENU_MISSION} class, extends @{Menu#MENU_BASE}
-- ---------------------------------------------------------
-- The @{Menu#MENU_MISSION} class manages the main menus for a complete mission.
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
--
-- 2.2) @{#MENU_MISSION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE}
-- -------------------------------------------------------------------------
-- The @{Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}.
--
-- ===
--
-- 3) MENU COALITION classes
-- =========================
-- The underlying classes manage the menus for whole coalitions.
--
-- 3.1) @{#MENU_COALITION} class, extends @{Menu#MENU_BASE}
-- ------------------------------------------------------------
-- The @{Menu#MENU_COALITION} class manages the main menus for coalitions.
-- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}.
--
-- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE}
-- ----------------------------------------------------------------------------
-- The @{Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}.
--
-- ===
--
-- 4) MENU GROUP classes
-- =====================
-- The underlying classes manage the menus for groups. Note that groups can be inactive, alive or can be destroyed.
--
-- 4.1) @{Menu#MENU_GROUP} class, extends @{Menu#MENU_BASE}
-- --------------------------------------------------------
-- The @{Menu#MENU_GROUP} class manages the main menus for coalitions.
-- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}.
--
-- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE}
-- ------------------------------------------------------------------------
-- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}.
--
-- ===
--
-- 5) MENU CLIENT classes
-- ======================
-- The underlying classes manage the menus for units with skill level client or player.
--
-- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE}
-- ---------------------------------------------------------
-- The @{Menu#MENU_CLIENT} class manages the main menus for coalitions.
-- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}.
--
-- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE}
-- -------------------------------------------------------------------------
-- The @{Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}.
--
-- ===
--
-- ### Contributions: -
-- ### Authors: FlightControl : Design & Programming
-- ### Authors:
--
-- * **FlightControl**: Design & Programming
--
-- @module Menu
do -- MENU_BASE
--- The MENU_BASE class
-- @type MENU_BASE
--- @type MENU_BASE
-- @extends Base#BASE
--- # MENU_BASE class, extends @{Base#BASE}
-- The MENU_BASE class defines the main MENU class where other MENU classes are derived from.
-- This is an abstract class, so don't use it.
-- @field #MENU_BASE
MENU_BASE = {
ClassName = "MENU_BASE",
MenuPath = nil,
@ -193,10 +110,15 @@ end
do -- MENU_COMMAND_BASE
--- The MENU_COMMAND_BASE class
-- @type MENU_COMMAND_BASE
--- @type MENU_COMMAND_BASE
-- @field #function MenuCallHandler
-- @extends Core.Menu#MENU_BASE
--- # MENU_COMMAND_BASE class, extends @{Base#BASE}
-- ----------------------------------------------------------
-- The MENU_COMMAND_BASE class defines the main MENU class where other MENU COMMAND_
-- classes are derived from, in order to set commands.
-- @field #MENU_COMMAND_BASE
MENU_COMMAND_BASE = {
ClassName = "MENU_COMMAND_BASE",
CommandMenuFunction = nil,
@ -224,9 +146,15 @@ end
do -- MENU_MISSION
--- The MENU_MISSION class
-- @type MENU_MISSION
--- @type MENU_MISSION
-- @extends Core.Menu#MENU_BASE
--- # MENU_MISSION class, extends @{Menu#MENU_BASE}
--
-- The MENU_MISSION class manages the main menus for a complete mission.
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
-- @field #MENU_MISSION
MENU_MISSION = {
ClassName = "MENU_MISSION"
}
@ -291,9 +219,16 @@ end
do -- MENU_MISSION_COMMAND
--- The MENU_MISSION_COMMAND class
-- @type MENU_MISSION_COMMAND
--- @type MENU_MISSION_COMMAND
-- @extends Core.Menu#MENU_COMMAND_BASE
--- # MENU_MISSION_COMMAND class, extends @{Menu#MENU_COMMAND_BASE}
--
-- The MENU_MISSION_COMMAND class manages the command menus for a complete mission, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}.
--
-- @field #MENU_MISSION_COMMAND
MENU_MISSION_COMMAND = {
ClassName = "MENU_MISSION_COMMAND"
}
@ -341,9 +276,16 @@ end
do -- MENU_COALITION
--- The MENU_COALITION class
-- @type MENU_COALITION
--- @type MENU_COALITION
-- @extends Core.Menu#MENU_BASE
--- # MENU_COALITION class, extends @{Menu#MENU_BASE}
--
-- The @{Menu#MENU_COALITION} class manages the main menus for coalitions.
-- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}.
--
--
-- @usage
-- -- This demo creates a menu structure for the planes within the red coalition.
-- -- To test, join the planes, then look at the other radio menus (Option F10).
@ -380,6 +322,8 @@ do -- MENU_COALITION
--
-- local MenuAdd = MENU_COALITION_COMMAND:New( coalition.side.RED, "Add Status Menu", MenuCoalitionRed, AddStatusMenu )
-- local MenuRemove = MENU_COALITION_COMMAND:New( coalition.side.RED, "Remove Status Menu", MenuCoalitionRed, RemoveStatusMenu )
--
-- @field #MENU_COALITION
MENU_COALITION = {
ClassName = "MENU_COALITION"
}
@ -446,9 +390,16 @@ end
do -- MENU_COALITION_COMMAND
--- The MENU_COALITION_COMMAND class
-- @type MENU_COALITION_COMMAND
--- @type MENU_COALITION_COMMAND
-- @extends Core.Menu#MENU_COMMAND_BASE
--- # MENU_COALITION_COMMAND class, extends @{Menu#MENU_COMMAND_BASE}
--
-- The MENU_COALITION_COMMAND class manages the command menus for coalitions, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}.
--
-- @field #MENU_COALITION_COMMAND
MENU_COALITION_COMMAND = {
ClassName = "MENU_COALITION_COMMAND"
}
@ -506,6 +457,14 @@ do -- MENU_CLIENT
--- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters.
-- @type MENU_CLIENT
-- @extends Core.Menu#MENU_BASE
--- # MENU_CLIENT class, extends @{Menu#MENU_BASE}
--
-- The MENU_CLIENT class manages the main menus for coalitions.
-- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}.
--
-- @usage
-- -- This demo creates a menu structure for the two clients of planes.
-- -- Each client will receive a different menu structure.
@ -555,6 +514,8 @@ do -- MENU_CLIENT
-- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneClient )
-- end
-- end, {}, 10, 10 )
--
-- @field #MENU_CLIENT
MENU_CLIENT = {
ClassName = "MENU_CLIENT"
}
@ -644,9 +605,16 @@ do -- MENU_CLIENT
end
--- The MENU_CLIENT_COMMAND class
-- @type MENU_CLIENT_COMMAND
--- @type MENU_CLIENT_COMMAND
-- @extends Core.Menu#MENU_COMMAND
--- # MENU_CLIENT_COMMAND class, extends @{Menu#MENU_COMMAND_BASE}
--
-- The MENU_CLIENT_COMMAND class manages the command menus for coalitions, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}.
--
-- @field #MENU_CLIENT_COMMAND
MENU_CLIENT_COMMAND = {
ClassName = "MENU_CLIENT_COMMAND"
}
@ -730,9 +698,16 @@ do
-- These menu classes are handling this logic with this variable.
local _MENUGROUPS = {}
--- The MENU_GROUP class
-- @type MENU_GROUP
--- @type MENU_GROUP
-- @extends Core.Menu#MENU_BASE
--- #MENU_GROUP class, extends @{Menu#MENU_BASE}
--
-- The MENU_GROUP class manages the main menus for coalitions.
-- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}.
--
-- @usage
-- -- This demo creates a menu structure for the two groups of planes.
-- -- Each group will receive a different menu structure.
@ -783,6 +758,7 @@ do
-- end
-- end, {}, 10, 10 )
--
-- @field #MENU_GROUP
MENU_GROUP = {
ClassName = "MENU_GROUP"
}
@ -876,9 +852,16 @@ do
end
--- The MENU_GROUP_COMMAND class
-- @type MENU_GROUP_COMMAND
--- @type MENU_GROUP_COMMAND
-- @extends Core.Menu#MENU_BASE
--- # MENU_GROUP_COMMAND class, extends @{Menu#MENU_COMMAND_BASE}
--
-- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution.
-- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}.
--
-- @field #MENU_GROUP_COMMAND
MENU_GROUP_COMMAND = {
ClassName = "MENU_GROUP_COMMAND"
}

View File

@ -4,18 +4,24 @@
--
-- ===
--
-- # 1) @{Message#MESSAGE} class, extends @{Base#BASE}
-- @module Message
--- The MESSAGE class
-- @type MESSAGE
-- @extends Core.Base#BASE
--- # MESSAGE class, extends @{Base#BASE}
--
-- Message System to display Messages to Clients, Coalitions or All.
-- Messages are shown on the display panel for an amount of seconds, and will then disappear.
-- Messages can contain a category which is indicating the category of the message.
--
-- ## 1.1) MESSAGE construction
-- ## MESSAGE construction
--
-- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
-- To send messages, you need to use the To functions.
--
-- ## 1.2) Send messages to an audience
-- ## Send messages to an audience
--
-- Messages are sent:
--
@ -26,19 +32,14 @@
-- * To the blue coalition using @{Message#MESSAGE.ToBlue}().
-- * To all Players using @{Message#MESSAGE.ToAll}().
--
-- ## 1.3) Send conditionally to an audience
-- ## Send conditionally to an audience
--
-- Messages can be sent conditionally to an audience (when a condition is true):
--
-- * To all players using @{Message#MESSAGE.ToAllIf}().
-- * To a coalition using @{Message#MESSAGE.ToCoalitionIf}().
--
--
-- @module Message
--- The MESSAGE class
-- @type MESSAGE
-- @extends Core.Base#BASE
-- @field #MESSAGE
MESSAGE = {
ClassName = "MESSAGE",
MessageCategory = 0,

View File

@ -1,91 +1,22 @@
--- **Core** - **POINT\_VEC** classes define an **extensive API** to **manage 3D points** in the simulation space.
--
-- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE}
-- ==================================================
-- The @{Point#POINT_VEC3} class defines a 3D point in the simulator.
--
-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts.
-- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums.
--
-- ## 1.1) POINT_VEC3 constructor
--
-- A new POINT_VEC3 instance can be created with:
--
-- * @{Point#POINT_VEC3.New}(): a 3D point.
-- * @{Point#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCSTypes#Vec3}.
--
-- ## 1.2) Manupulate the X, Y, Z coordinates of the point
--
-- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate.
-- Methods exist to manupulate these coordinates.
--
-- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively.
-- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value.
-- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}()
-- to add or substract a value from the current respective axis value.
-- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example:
--
-- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3()
--
-- ## 1.3) Create waypoints for routes
--
-- A POINT_VEC3 can prepare waypoints for Ground, Air and Naval groups to be embedded into a Route.
--
--
-- ## 1.5) Smoke, flare, explode, illuminate
--
-- At the point a smoke, flare, explosion and illumination bomb can be triggered. Use the following methods:
--
-- ### 1.5.1) Smoke
--
-- * @{#POINT_VEC3.Smoke}(): To smoke the point in a certain color.
-- * @{#POINT_VEC3.SmokeBlue}(): To smoke the point in blue.
-- * @{#POINT_VEC3.SmokeRed}(): To smoke the point in red.
-- * @{#POINT_VEC3.SmokeOrange}(): To smoke the point in orange.
-- * @{#POINT_VEC3.SmokeWhite}(): To smoke the point in white.
-- * @{#POINT_VEC3.SmokeGreen}(): To smoke the point in green.
--
-- ### 1.5.2) Flare
--
-- * @{#POINT_VEC3.Flare}(): To flare the point in a certain color.
-- * @{#POINT_VEC3.FlareRed}(): To flare the point in red.
-- * @{#POINT_VEC3.FlareYellow}(): To flare the point in yellow.
-- * @{#POINT_VEC3.FlareWhite}(): To flare the point in white.
-- * @{#POINT_VEC3.FlareGreen}(): To flare the point in green.
--
-- ### 1.5.3) Explode
--
-- * @{#POINT_VEC3.Explosion}(): To explode the point with a certain intensity.
--
-- ### 1.5.4) Illuminate
--
-- * @{#POINT_VEC3.IlluminationBomb}(): To illuminate the point.
--
--
-- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3}
-- =========================================================
-- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified.
-- ![Banner Image](..\Presentations\POINT\Dia1.JPG)
--
-- ====
--
-- 2.1) POINT_VEC2 constructor
-- ---------------------------
-- A new POINT_VEC2 instance can be created with:
-- # Demo Missions
--
-- * @{Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter.
-- * @{Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCSTypes#Vec2}.
-- ### [POINT_VEC Demo Missions source code]()
--
-- ## 1.2) Manupulate the X, Altitude, Y coordinates of the 2D point
-- ### [POINT_VEC Demo Missions, only for beta testers]()
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate.
-- Methods exist to manupulate these coordinates.
-- ====
--
-- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively.
-- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value.
-- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively.
-- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}()
-- to add or substract a value from the current respective axis value.
-- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example:
-- # YouTube Channel
--
-- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2()
-- ### [POINT_VEC YouTube Channel]()
--
-- ===
--
@ -133,6 +64,126 @@
-- @field #POINT_VEC3.RoutePointType RoutePointType
-- @field #POINT_VEC3.RoutePointAction RoutePointAction
-- @extends Core.Base#BASE
--- # POINT_VEC3 class, extends @{Base#BASE}
--
-- POINT_VEC3 defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space.
--
-- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts.
-- In order to keep the credibility of the the author,
-- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors,
-- who you can find on the Eagle Dynamics Forums.
--
--
-- ## POINT_VEC3 constructor
--
-- A new POINT_VEC3 object can be created with:
--
-- * @{#POINT_VEC3.New}(): a 3D point.
-- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCSTypes#Vec3}.
--
--
-- ## Manupulate the X, Y, Z coordinates of the POINT_VEC3
--
-- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate.
-- Methods exist to manupulate these coordinates.
--
-- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively.
-- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value.
-- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}()
-- to add or substract a value from the current respective axis value.
-- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example:
--
-- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3()
--
--
-- ## Create waypoints for routes
--
-- A POINT_VEC3 can prepare waypoints for Ground and Air groups to be embedded into a Route.
--
-- * @{#POINT_VEC3.RoutePointAir}(): Build an air route point.
-- * @{#POINT_VEC3.RoutePointGround}(): Build a ground route point.
--
-- Route points can be used in the Route methods of the @{Group#GROUP} class.
--
--
-- ## Smoke, flare, explode, illuminate
--
-- At the point a smoke, flare, explosion and illumination bomb can be triggered. Use the following methods:
--
-- ### Smoke
--
-- * @{#POINT_VEC3.Smoke}(): To smoke the point in a certain color.
-- * @{#POINT_VEC3.SmokeBlue}(): To smoke the point in blue.
-- * @{#POINT_VEC3.SmokeRed}(): To smoke the point in red.
-- * @{#POINT_VEC3.SmokeOrange}(): To smoke the point in orange.
-- * @{#POINT_VEC3.SmokeWhite}(): To smoke the point in white.
-- * @{#POINT_VEC3.SmokeGreen}(): To smoke the point in green.
--
-- ### Flare
--
-- * @{#POINT_VEC3.Flare}(): To flare the point in a certain color.
-- * @{#POINT_VEC3.FlareRed}(): To flare the point in red.
-- * @{#POINT_VEC3.FlareYellow}(): To flare the point in yellow.
-- * @{#POINT_VEC3.FlareWhite}(): To flare the point in white.
-- * @{#POINT_VEC3.FlareGreen}(): To flare the point in green.
--
-- ### Explode
--
-- * @{#POINT_VEC3.Explosion}(): To explode the point with a certain intensity.
--
-- ### Illuminate
--
-- * @{#POINT_VEC3.IlluminationBomb}(): To illuminate the point.
--
--
-- ## 3D calculation methods
--
-- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method:
--
-- ### Distance
--
-- * @{#POINT_VEC3.Get3DDistance}(): Obtain the distance from the current 3D point to the provided 3D point in 3D space.
-- * @{#POINT_VEC3.Get2DDistance}(): Obtain the distance from the current 3D point to the provided 3D point in 2D space.
--
-- ### Angle
--
-- * @{#POINT_VEC3.GetAngleDegrees}(): Obtain the angle in degrees from the current 3D point with the provided 3D direction vector.
-- * @{#POINT_VEC3.GetAngleRadians}(): Obtain the angle in radians from the current 3D point with the provided 3D direction vector.
-- * @{#POINT_VEC3.GetDirectionVec3}(): Obtain the 3D direction vector from the current 3D point to the provided 3D point.
--
-- ### Translation
--
-- * @{#POINT_VEC3.Translate}(): Translate the current 3D point towards an other 3D point using the given Distance and Angle.
--
-- ### Get the North correction of the current location
--
-- * @{#POINT_VEC3.GetNorthCorrection}(): Obtains the north correction at the current 3D point.
--
--
-- ## Point Randomization
--
-- Various methods exist to calculate random locations around a given 3D point.
--
-- * @{#POINT_VEC3.GetRandomPointVec2InRadius}(): Provides a random 2D point around the current 3D point, in the given inner to outer band.
-- * @{#POINT_VEC3.GetRandomPointVec3InRadius}(): Provides a random 3D point around the current 3D point, in the given inner to outer band.
-- * @{#POINT_VEC3.GetRandomVec2InRadius}(): Provides a random 2D vector around the current 3D point, in the given inner to outer band.
-- * @{#POINT_VEC3.GetRandomVec3InRadius}(): Provides a random 3D vector around the current 3D point, in the given inner to outer band.
--
--
-- ## Metric system
--
-- * @{#POINT_VEC3.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles.
-- * @{#POINT_VEC3.SetMetric}(): Sets the 3D point to Metric or Nautical Miles.
--
--
-- ## Coorinate text generation
--
-- * @{#POINT_VEC3.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance.
-- * @{#POINT_VEC3.ToStringLL}(): Generates a Latutude & Longutude text.
--
-- @field #POINT_VEC3 POINT_VEC3
--
POINT_VEC3 = {
ClassName = "POINT_VEC3",
Metric = true,
@ -149,11 +200,38 @@ POINT_VEC3 = {
},
}
--- The POINT_VEC2 class
-- @type POINT_VEC2
--- @type POINT_VEC2
-- @field Dcs.DCSTypes#Distance x The x coordinate in meters.
-- @field Dcs.DCSTypes#Distance y the y coordinate in meters.
-- @extends Core.Point#POINT_VEC3
--- # POINT_VEC2 class, extends @{Point#POINT_VEC3}
--
-- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified.
--
-- ## POINT_VEC2 constructor
--
-- A new POINT_VEC2 instance can be created with:
--
-- * @{Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter.
-- * @{Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCSTypes#Vec2}.
--
-- ## Manupulate the X, Altitude, Y coordinates of the 2D point
--
-- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate.
-- Methods exist to manupulate these coordinates.
--
-- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively.
-- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value.
-- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively.
-- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}()
-- to add or substract a value from the current respective axis value.
-- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example:
--
-- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2()
--
-- @field #POINT_VEC2 POINT_VEC2
--
POINT_VEC2 = {
ClassName = "POINT_VEC2",
}
@ -399,11 +477,11 @@ function POINT_VEC3:GetNorthCorrectionRadians()
end
--- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format.
--- Return an angle in radians from the POINT_VEC3 using a direction vector in Vec3 format.
-- @param #POINT_VEC3 self
-- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format.
-- @return #number DirectionRadians The direction in radians.
function POINT_VEC3:GetDirectionRadians( DirectionVec3 )
-- @return #number DirectionRadians The angle in radians.
function POINT_VEC3:GetAngleRadians( DirectionVec3 )
local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x )
--DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians()
if DirectionRadians < 0 then
@ -412,6 +490,17 @@ function POINT_VEC3:GetDirectionRadians( DirectionVec3 )
return DirectionRadians
end
--- Return an angle in degrees from the POINT_VEC3 using a direction vector in Vec3 format.
-- @param #POINT_VEC3 self
-- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format.
-- @return #number DirectionRadians The angle in degrees.
function POINT_VEC3:GetAngleDegrees( DirectionVec3 )
local AngleRadians = self:GetAngleRadians(DirectionVec3)
local Angle = UTILS.ToDegree( AngleRadians )
return Angle
end
--- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3.
-- @param #POINT_VEC3 self
-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3.
@ -482,7 +571,7 @@ end
-- @return #string The BR text.
function POINT_VEC3:GetBRText( TargetPointVec3 )
local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 )
local AngleRadians = self:GetDirectionRadians( DirectionVec3 )
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
local Distance = self:Get2DDistance( TargetPointVec3 )
return self:ToStringBR( AngleRadians, Distance )
end

View File

@ -1,6 +1,6 @@
--- **Core** - The RADIO class is responsible for **transmitting radio communications**.
--
-- --- bitmap
-- ![Banner Image](..\Presentations\RADIO\Dia1.JPG)
--
-- ===
--
@ -27,19 +27,20 @@
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet
-- ### Author: Hugues "Grey_Echo" Bousquet
--
-- @module Radio
--- # 1) RADIO class, extends @{Base#BASE}
--
-- ## 1.1) RADIO usage
--
-- There are 3 steps to a successful radio transmission.
--
-- * First, you need to **"add" a @{#RADIO} object** to your @{Positionable#POSITIONABLE}. This is done using the @{Positionable#POSITIONABLE.GetRadio}() function,
-- * First, you need to **"add a @{#RADIO} object** to your @{Positionable#POSITIONABLE}. This is done using the @{Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{Positionable#POSITIONABLE.Broadcast}() function.
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
-- Methods to set relevant parameters for both a @{Unit#UNIT} or a @{Group#GROUP} or any other @{Positionable#POSITIONABLE}
--
@ -53,7 +54,7 @@
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
--
-- Additional Methods to set relevant parameters if the transmiter is any other @{Wrapper.Positionable#POSITIONABLE}
-- Additional Methods to set relevant parameters if the transmiter is any other @{Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
@ -68,7 +69,7 @@
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- @type RADIO
-- @field Wrapper.Positionable#POSITIONABLE Positionable The transmiter
-- @field Positionable#POSITIONABLE Positionable The transmiter
-- @field #string FileName Name of the sound file
-- @field #number Frequency Frequency of the transmission in Hz
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM)

View File

@ -4,31 +4,30 @@
--
-- ===
--
-- # 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE}
-- SCHEDULER manages the **scheduling of functions**:
--
-- The @{Scheduler#SCHEDULER} class creates schedule.
--
-- ## 1.1) SCHEDULER constructor
--
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
--
-- * @{Scheduler#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
-- * @{Scheduler#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
-- * @{Scheduler#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
-- * @{Scheduler#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
-- ## 1.2) SCHEDULER timer stopping and (re-)starting.
--
-- The SCHEDULER can be stopped and restarted with the following methods:
--
-- * @{Scheduler#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started.
-- * @{Scheduler#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped.
--
-- ## 1.3) Create a new schedule
--
-- With @{Scheduler#SCHEDULER.Schedule}() a new time event can be scheduled. This function is used by the :New() constructor when a new schedule is planned.
-- * optionally in an optional specified time interval,
-- * optionally **repeating** with a specified time repeat interval,
-- * optionally **randomizing** with a specified time interval randomization factor,
-- * optionally **stop** the repeating after a specified time interval.
--
-- ===
--
-- # Demo Missions
--
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler)
--
-- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [SCHEDULER YouTube Channel (none)]()
--
-- ====
--
-- ### Contributions:
--
@ -38,10 +37,6 @@
--
-- * FlightControl : Design & Programming
--
-- ### Test Missions:
--
-- * SCH - Scheduler
--
-- ===
--
-- @module Scheduler
@ -51,6 +46,153 @@
-- @type SCHEDULER
-- @field #number ScheduleID the ID of the scheduler.
-- @extends Core.Base#BASE
--- # SCHEDULER class, extends @{Base#BASE}
--
-- The SCHEDULER class creates schedule.
--
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
-- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called.
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(),
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
--
-- ## SCHEDULER constructor
--
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
--
-- The @{#SCHEDULER.New}() method returns 2 variables:
--
-- 1. The SCHEDULER object reference.
-- 2. The first schedule planned in the SCHEDULER object.
--
-- To clarify the different appliances, lets have a look at the following examples:
--
-- ### Construct a SCHEDULER object without a persistent schedule.
--
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
--
-- SchedulerObject = SCHEDULER:New()
-- SchedulerID = SchedulerObject:Schedule( nil, ScheduleFunction, {} )
--
-- The above example creates a new SchedulerObject, but does not schedule anything.
-- A separate schedule is created by using the SchedulerObject using the method :Schedule..., which returns a ScheduleID
--
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
--
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject = SCHEDULER:New( ZoneObject )
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- ...
-- ZoneObject = nil
-- garbagecollect()
--
-- The above example creates a new SchedulerObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
-- A separate schedule is created by using the SchedulerObject using the method :Schedule()..., which returns a ScheduleID
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the ScheduleObject will cancel any planned schedule.
--
-- ### Construct a SCHEDULER object with a persistent schedule.
--
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
-- SchedulerObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
--
-- The above example creates a new SchedulerObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: SchedulerObject, ScheduleID...
--
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
--
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
-- ...
-- ZoneObject = nil
-- garbagecollect()
--
-- The above example creates a new SchedulerObject, and schedules a method call (ScheduleFunction),
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
-- Both a ScheduleObject and a SchedulerID variable are returned.
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
-- As a result, the ScheduleObject will cancel the planned schedule.
--
-- ## SCHEDULER timer stopping and (re-)starting.
--
-- The SCHEDULER can be stopped and restarted with the following methods:
--
-- * @{#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started.
-- * @{#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 10 )
-- ...
-- SchedulerObject:Stop( SchedulerID )
-- ...
-- SchedulerObject:Start( SchedulerID )
--
-- The above example creates a new SchedulerObject, and does schedule the first schedule as part of the call.
-- Note that 2 variables are returned here: SchedulerObject, ScheduleID...
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
--
-- ## Create a new schedule
--
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
-- This method is used by the :New() constructor when a new schedule is planned.
--
-- Consider the following code fragment of the SCHEDULER object creation.
--
-- ZoneObject = ZONE:New( "ZoneName" )
-- SchedulerObject = SCHEDULER:New( ZoneObject )
--
-- Several parameters can be specified that influence the behaviour of a Schedule.
--
-- ### A single schedule, immediately executed
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
--
-- ### A single schedule, planned over time
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
--
-- ### A schedule with a repeating time interval, planned over time
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- and repeating 60 every seconds ...
--
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- and repeating 60 seconds, with a 50% time interval randomization ...
-- So the repeating time interval will be randomized using the **0.5**,
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
-- which is in this example between **30** and **90** seconds.
--
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
--
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
--
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
-- The schedule will repeat every 60 seconds.
-- So the repeating time interval will be randomized using the **0.5**,
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
-- which is in this example between **30** and **90** seconds.
-- The schedule will stop after **300** seconds.
--
-- @field #SCHEDULER
SCHEDULER = {
ClassName = "SCHEDULER",
Schedules = {},

View File

@ -124,7 +124,7 @@ end
-- @param Core.Base#BASE Object
-- @return Core.Base#BASE The added BASE Object.
function SET_BASE:Add( ObjectName, Object )
self:F2( ObjectName )
self:F( ObjectName )
local t = { _ = Object }
@ -1374,7 +1374,7 @@ function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ...
--- @param Core.Zone#ZONE_BASE ZoneObject
-- @param Wrapper.Unit#UNIT UnitObject
function( ZoneObject, UnitObject )
if UnitObject:IsCompletelyInZone( ZoneObject ) then
if UnitObject:IsInZone( ZoneObject ) then
return true
else
return false
@ -2391,3 +2391,346 @@ function SET_AIRBASE:IsIncludeObject( MAirbase )
self:T2( MAirbaseInclude )
return MAirbaseInclude
end
--- @type SET_CARGO
-- @extends Core.Set#SET_BASE
--- # SET_CARGO class, extends @{Set#SET_BASE}
--
-- Mission designers can use the @{Set#SET_CARGO} class to build sets of cargos optionally belonging to certain:
--
-- * Coalitions
-- * Types
-- * Name or Prefix
--
-- ## SET_CARGO constructor
--
-- Create a new SET_CARGO object with the @{#SET_CARGO.New} method:
--
-- * @{#SET_CARGO.New}: Creates a new SET_CARGO object.
--
-- ## Add or Remove CARGOs from SET_CARGO
--
-- CARGOs can be added and removed using the @{Set#SET_CARGO.AddCargosByName} and @{Set#SET_CARGO.RemoveCargosByName} respectively.
-- These methods take a single CARGO name or an array of CARGO names to be added or removed from SET_CARGO.
--
-- ## SET_CARGO filter criteria
--
-- You can set filter criteria to automatically maintain the SET_CARGO contents.
-- Filter criteria are defined by:
--
-- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s).
-- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the prefix string(s).
-- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s).
-- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies).
--
-- Once the filter criteria have been set for the SET_CARGO, you can start filtering using:
--
-- * @{#SET_CARGO.FilterStart}: Starts the filtering of the cargos within the SET_CARGO.
--
-- ## SET_CARGO iterators
--
-- Once the filters have been defined and the SET_CARGO has been built, you can iterate the SET_CARGO with the available iterator methods.
-- The iterator methods will walk the SET_CARGO set, and call for each cargo within the set a function that you provide.
-- The following iterator methods are currently available within the SET_CARGO:
--
-- * @{#SET_CARGO.ForEachCargo}: Calls a function for each cargo it finds within the SET_CARGO.
--
-- @field #SET_CARGO SET_CARGO
--
SET_CARGO = {
ClassName = "SET_CARGO",
Cargos = {},
Filter = {
Coalitions = nil,
Types = nil,
Countries = nil,
ClientPrefixes = nil,
},
FilterMeta = {
Coalitions = {
red = coalition.side.RED,
blue = coalition.side.BLUE,
neutral = coalition.side.NEUTRAL,
},
},
}
--- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories.
-- @param #SET_CARGO self
-- @return #SET_CARGO self
-- @usage
-- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos.
-- DatabaseSet = SET_CARGO:New()
function SET_CARGO:New()
-- Inherits from BASE
local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) )
return self
end
--- Add CARGOs to SET_CARGO.
-- @param Core.Set#SET_CARGO self
-- @param #string AddCargoNames A single name or an array of CARGO names.
-- @return self
function SET_CARGO:AddCargosByName( AddCargoNames )
local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames }
for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do
self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) )
end
return self
end
--- Remove CARGOs from SET_CARGO.
-- @param Core.Set#SET_CARGO self
-- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names.
-- @return self
function SET_CARGO:RemoveCargosByName( RemoveCargoNames )
local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames }
for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do
self:Remove( RemoveCargoName.CargoName )
end
return self
end
--- Finds a Cargo based on the Cargo Name.
-- @param #SET_CARGO self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found Cargo.
function SET_CARGO:FindCargo( CargoName )
local CargoFound = self.Set[CargoName]
return CargoFound
end
--- Builds a set of cargos of coalitions.
-- Possible current coalitions are red, blue and neutral.
-- @param #SET_CARGO self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
-- @return #SET_CARGO self
function SET_CARGO:FilterCoalitions( Coalitions )
if not self.Filter.Coalitions then
self.Filter.Coalitions = {}
end
if type( Coalitions ) ~= "table" then
Coalitions = { Coalitions }
end
for CoalitionID, Coalition in pairs( Coalitions ) do
self.Filter.Coalitions[Coalition] = Coalition
end
return self
end
--- Builds a set of cargos of defined cargo types.
-- Possible current types are those types known within DCS world.
-- @param #SET_CARGO self
-- @param #string Types Can take those type strings known within DCS world.
-- @return #SET_CARGO self
function SET_CARGO:FilterTypes( Types )
if not self.Filter.Types then
self.Filter.Types = {}
end
if type( Types ) ~= "table" then
Types = { Types }
end
for TypeID, Type in pairs( Types ) do
self.Filter.Types[Type] = Type
end
return self
end
--- Builds a set of cargos of defined countries.
-- Possible current countries are those known within DCS world.
-- @param #SET_CARGO self
-- @param #string Countries Can take those country strings known within DCS world.
-- @return #SET_CARGO self
function SET_CARGO:FilterCountries( Countries )
if not self.Filter.Countries then
self.Filter.Countries = {}
end
if type( Countries ) ~= "table" then
Countries = { Countries }
end
for CountryID, Country in pairs( Countries ) do
self.Filter.Countries[Country] = Country
end
return self
end
--- Builds a set of cargos of defined cargo prefixes.
-- All the cargos starting with the given prefixes will be included within the set.
-- @param #SET_CARGO self
-- @param #string Prefixes The prefix of which the cargo name starts with.
-- @return #SET_CARGO self
function SET_CARGO:FilterPrefixes( Prefixes )
if not self.Filter.CargoPrefixes then
self.Filter.CargoPrefixes = {}
end
if type( Prefixes ) ~= "table" then
Prefixes = { Prefixes }
end
for PrefixID, Prefix in pairs( Prefixes ) do
self.Filter.CargoPrefixes[Prefix] = Prefix
end
return self
end
--- Starts the filtering.
-- @param #SET_CARGO self
-- @return #SET_CARGO self
function SET_CARGO:FilterStart()
if _DATABASE then
self:_FilterStart()
end
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
return self
end
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
-- @param #SET_CARGO self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the CARGO
-- @return #table The CARGO
function SET_CARGO:AddInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Handles the Database to check on any event that Object exists in the Database.
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
-- @param #SET_CARGO self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the CARGO
-- @return #table The CARGO
function SET_CARGO:FindInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Iterate the SET_CARGO and call an interator function for each CARGO, providing the CARGO and optional parameters.
-- @param #SET_CARGO self
-- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter.
-- @return #SET_CARGO self
function SET_CARGO:ForEachCargo( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self.Set )
return self
end
--- Iterate the SET_CARGO while identifying the nearest @{Cargo#CARGO} from a @{Point#POINT_VEC2}.
-- @param #SET_CARGO self
-- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Cargo#CARGO}.
-- @return Wrapper.Cargo#CARGO The closest @{Cargo#CARGO}.
function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 )
self:F2( PointVec2 )
local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 )
return NearestCargo
end
---
-- @param #SET_CARGO self
-- @param AI.AI_Cargo#AI_CARGO MCargo
-- @return #SET_CARGO self
function SET_CARGO:IsIncludeObject( MCargo )
self:F2( MCargo )
local MCargoInclude = true
if MCargo then
local MCargoName = MCargo:GetName()
if self.Filter.Coalitions then
local MCargoCoalition = false
for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
local CargoCoalitionID = MCargo:GetCoalition()
self:T3( { "Coalition:", CargoCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } )
if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == CargoCoalitionID then
MCargoCoalition = true
end
end
self:T( { "Evaluated Coalition", MCargoCoalition } )
MCargoInclude = MCargoInclude and MCargoCoalition
end
if self.Filter.Types then
local MCargoType = false
for TypeID, TypeName in pairs( self.Filter.Types ) do
self:T3( { "Type:", MCargo:GetType(), TypeName } )
if TypeName == MCargo:GetType() then
MCargoType = true
end
end
self:T( { "Evaluated Type", MCargoType } )
MCargoInclude = MCargoInclude and MCargoType
end
if self.Filter.CargoPrefixes then
local MCargoPrefix = false
for CargoPrefixId, CargoPrefix in pairs( self.Filter.CargoPrefixes ) do
self:T3( { "Prefix:", string.find( MCargo.Name, CargoPrefix, 1 ), CargoPrefix } )
if string.find( MCargo.Name, CargoPrefix, 1 ) then
MCargoPrefix = true
end
end
self:T( { "Evaluated Prefix", MCargoPrefix } )
MCargoInclude = MCargoInclude and MCargoPrefix
end
end
self:T2( MCargoInclude )
return MCargoInclude
end
--- Handles the OnEventNewCargo event for the Set.
-- @param #SET_CARGO self
-- @param Core.Event#EVENTDATA EventData
function SET_CARGO:OnEventNewCargo( EventData )
if EventData.Cargo then
if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then
self:Add( EventData.Cargo.Name , EventData.Cargo )
end
end
end
--- Handles the OnDead or OnCrash event for alive units set.
-- @param #SET_CARGO self
-- @param Core.Event#EVENTDATA EventData
function SET_CARGO:OnEventDeleteCargo( EventData )
self:F3( { EventData } )
if EventData.Cargo then
local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name )
if Cargo and Cargo.Name then
self:Remove( Cargo.Name )
end
end
end

View File

@ -0,0 +1,177 @@
--- **Core** -- Spawn dynamically new STATICs in your missions.
--
-- ![Banner Image](..\Presentations\SPAWNSTATIC\Dia1.JPG)
--
-- ====
--
-- SPAWNSTATIC spawns static structures in your missions dynamically. See below the SPAWNSTATIC class documentation.
--
-- ====
--
-- # Demo Missions
--
-- ### [SPAWNSTATIC Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPS - Spawning Statics)
--
-- ### [SPAWNSTATIC Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPS%20-%20Spawning%20Statics)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [SPAWNSTATIC YouTube Channel]()
--
-- ====
--
-- # **API CHANGE HISTORY**
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- ===
--
-- # **AUTHORS and CONTRIBUTIONS**
--
-- ### Contributions:
--
-- ### Authors:
--
-- * **FlightControl**: Design & Programming
--
-- @module SpawnStatic
--- @type SPAWNSTATIC
-- @extends Core.Base#BASE
--- # SPAWNSTATIC class, extends @{Base#BASE}
--
-- The SPAWNSTATIC class allows to spawn dynamically new @{Static}s.
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME),
-- SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy"
-- these properties to create a new static object and place it at the desired coordinate.
--
-- New spawned @{Static}s get **the same name** as the name of the template Static,
-- or gets the given name when a new name is provided at the Spawn method.
-- By default, spawned @{Static}s will follow a naming convention at run-time:
--
-- * Spawned @{Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**,
-- and _nnn_ is a **counter from 0 to 99999**.
--
--
-- ## SPAWNSTATIC construction methods
--
-- Create a new SPAWNSTATIC object with the @{#SPAWNSTATIC.NewFromStatic}():
--
-- * @{#SPAWNSTATIC.NewFromStatic}(): Creates a new SPAWNSTATIC object given a name that is used as the base of the naming of each spawned Static.
--
-- ## **Spawn** methods
--
-- Groups can be spawned at different times and methods:
--
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(): Spawn a new group from a POINT_VEC2 coordinate.
-- (The group will be spawned at land height ).
-- * @{#SPAWNSTATIC.SpawnFromZone}(): Spawn a new group in a @{Zone}.
--
-- @field #SPAWNSTATIC SPAWNSTATIC
--
SPAWNSTATIC = {
ClassName = "SPAWNSTATIC",
}
--- @type SPAWNSTATIC.SpawnZoneTable
-- @list <Core.Zone#ZONE_BASE> SpawnZone
--- Creates the main object to spawn a @{Static} defined in the ME.
-- @param #SPAWNSTATIC self
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID )
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self:F( { SpawnTemplatePrefix } )
local TemplateStatic = StaticObject.getByName( SpawnTemplatePrefix )
if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.CountryID = CountryID
self.SpawnIndex = 0
else
error( "SPAWNSTATIC:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
end
self:SetEventPriority( 5 )
return self
end
--- Creates the main object to spawn a @{Static} based on a type name.
-- @param #SPAWNSTATIC self
-- @param #string SpawnTypeName is the name of the type.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, CountryID )
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self:F( { SpawnTypeName } )
self.SpawnTypeName = SpawnTypeName
self.CountryID = CountryID
self.SpawnIndex = 0
self:SetEventPriority( 5 )
return self
end
--- Creates a new @{Static} from a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName )
self:F( { PointVec2, Heading, NewName } )
local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID]
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
StaticTemplate.x = PointVec2:GetLat()
StaticTemplate.y = PointVec2:GetLon()
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( self.CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
--- Creates a new @{Static} from a @{Zone}.
-- @param #SPAWNSTATIC self
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName )
self:F( { Zone, Heading, NewName } )
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
return Static
end

View File

@ -66,48 +66,53 @@
-- @module Zone
--- The ZONE_BASE class
-- @type ZONE_BASE
--- @type ZONE_BASE
-- @field #string ZoneName Name of the zone.
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @extends Core.Base#BASE
--- # 1) ZONE_BASE class, extends @{Base#BASE}
--- # ZONE_BASE class, extends @{Base#BASE}
--
-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated.
--
-- ## 1.1) Each zone has a name:
-- ## Each zone has a name:
--
-- * @{#ZONE_BASE.GetName}(): Returns the name of the zone.
--
-- ## 1.2) Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}:
-- ## Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}:
--
-- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a Vec2 is within the zone.
-- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a Vec3 is within the zone.
-- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a 2D vector is within the zone.
-- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a 3D vector is within the zone.
-- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a 2D point vector is within the zone.
-- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a 3D point vector is within the zone.
--
-- ## 1.3) A zone has a probability factor that can be set to randomize a selection between zones:
-- ## A zone has a probability factor that can be set to randomize a selection between zones:
--
-- * @{#ZONE_BASE.SetRandomizeProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% )
-- * @{#ZONE_BASE.GetRandomizeProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% )
-- * @{#ZONE_BASE.SetZoneProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% )
-- * @{#ZONE_BASE.GetZoneProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% )
-- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate.
--
-- ## 1.4) A zone manages Vectors:
-- ## A zone manages vectors:
--
-- * @{#ZONE_BASE.GetVec2}(): Returns the @{DCSTypes#Vec2} coordinate of the zone.
-- * @{#ZONE_BASE.GetRandomVec2}(): Define a random @{DCSTypes#Vec2} within the zone.
-- * @{#ZONE_BASE.GetVec2}(): Returns the 2D vector coordinate of the zone.
-- * @{#ZONE_BASE.GetVec3}(): Returns the 3D vector coordinate of the zone.
-- * @{#ZONE_BASE.GetPointVec2}(): Returns the 2D point vector coordinate of the zone.
-- * @{#ZONE_BASE.GetPointVec3}(): Returns the 3D point vector coordinate of the zone.
-- * @{#ZONE_BASE.GetRandomVec2}(): Define a random 2D vector within the zone.
-- * @{#ZONE_BASE.GetRandomPointVec2}(): Define a random 2D point vector within the zone.
-- * @{#ZONE_BASE.GetRandomPointVec3}(): Define a random 3D point vector within the zone.
--
-- ## 1.5) A zone has a bounding square:
-- ## A zone has a bounding square:
--
-- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone.
--
-- ## 1.6) A zone can be marked:
-- ## A zone can be marked:
--
-- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color.
-- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color.
--
-- ===
-- @field #ZONE_BASE ZONE_BASE
-- @field #ZONE_BASE
ZONE_BASE = {
ClassName = "ZONE_BASE",
ZoneName = "",
@ -144,20 +149,21 @@ function ZONE_BASE:GetName()
return self.ZoneName
end
--- Returns if a location is within the zone.
--- Returns if a Vec2 is within the zone.
-- @param #ZONE_BASE self
-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test.
-- @return #boolean true if the location is within the zone.
-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 to test.
-- @return #boolean true if the Vec2 is within the zone.
function ZONE_BASE:IsVec2InZone( Vec2 )
self:F2( Vec2 )
return false
end
--- Returns if a point is within the zone.
--- Returns if a Vec3 is within the zone.
-- @param #ZONE_BASE self
-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test.
-- @return #boolean true if the point is within the zone.
-- @return #boolean true if the Vec3 is within the zone.
function ZONE_BASE:IsVec3InZone( Vec3 )
self:F2( Vec3 )
@ -166,6 +172,31 @@ function ZONE_BASE:IsVec3InZone( Vec3 )
return InZone
end
--- Returns if a PointVec2 is within the zone.
-- @param #ZONE_BASE self
-- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to test.
-- @return #boolean true if the PointVec2 is within the zone.
function ZONE_BASE:IsPointVec2InZone( PointVec2 )
self:F2( PointVec2 )
local InZone = self:IsVec2InZone( PointVec2:GetVec2() )
return InZone
end
--- Returns if a PointVec3 is within the zone.
-- @param #ZONE_BASE self
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 to test.
-- @return #boolean true if the PointVec3 is within the zone.
function ZONE_BASE:IsPointVec3InZone( PointVec3 )
self:F2( PointVec3 )
local InZone = self:IsPointVec2InZone( PointVec3 )
return InZone
end
--- Returns the @{DCSTypes#Vec2} coordinate of the zone.
-- @param #ZONE_BASE self
-- @return #nil.
@ -310,29 +341,29 @@ end
-- @type ZONE_RADIUS
-- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone.
-- @field Dcs.DCSTypes#Distance Radius The radius of the zone.
-- @extends Core.Zone#ZONE_BASE
-- @extends #ZONE_BASE
--- # 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE}
--- # ZONE_RADIUS class, extends @{Zone#ZONE_BASE}
--
-- The ZONE_RADIUS class defined by a zone name, a location and a radius.
-- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties.
--
-- ## 2.1) @{Zone#ZONE_RADIUS} constructor
-- ## ZONE_RADIUS constructor
--
-- * @{#ZONE_RADIUS.New}(): Constructor.
--
-- ## 2.2) Manage the radius of the zone
-- ## Manage the radius of the zone
--
-- * @{#ZONE_RADIUS.SetRadius}(): Sets the radius of the zone.
-- * @{#ZONE_RADIUS.GetRadius}(): Returns the radius of the zone.
--
-- ## 2.3) Manage the location of the zone
-- ## Manage the location of the zone
--
-- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCSTypes#Vec2} of the zone.
-- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCSTypes#Vec2} of the zone.
-- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCSTypes#Vec3} of the zone, taking an additional height parameter.
--
-- ## 2.4) Zone point randomization
-- ## Zone point randomization
--
-- Various functions exist to find random points within the zone.
--
@ -340,10 +371,7 @@ end
-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Point#POINT_VEC2} object representing a random 2D point in the zone.
-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight.
--
-- ===
--
-- @field #ZONE_RADIUS ZONE_RADIUS
--
-- @field #ZONE_RADIUS
ZONE_RADIUS = {
ClassName="ZONE_RADIUS",
}
@ -615,19 +643,16 @@ end
-- @type ZONE
-- @extends Core.Zone#ZONE_RADIUS
--- @type ZONE
-- @extends #ZONE_RADIUS
--- # 3) ZONE class, extends @{Zone#ZONE_RADIUS}
--- # ZONE class, extends @{Zone#ZONE_RADIUS}
--
-- The ZONE class, defined by the zone name as defined within the Mission Editor.
-- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties.
--
-- ===
--
-- @field #ZONE ZONE
--
-- @field #ZONE
ZONE = {
ClassName="ZONE",
}
@ -655,20 +680,16 @@ function ZONE:New( ZoneName )
end
--- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius.
-- @type ZONE_UNIT
--- @type ZONE_UNIT
-- @field Wrapper.Unit#UNIT ZoneUNIT
-- @extends Core.Zone#ZONE_RADIUS
--- # 4) #ZONE_UNIT class, extends @{Zone#ZONE_RADIUS}
--- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS}
--
-- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius.
-- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties.
--
-- ===
--
-- @field #ZONE_UNIT ZONE_UNIT
--
-- @field #ZONE_UNIT
ZONE_UNIT = {
ClassName="ZONE_UNIT",
}
@ -750,19 +771,15 @@ function ZONE_UNIT:GetVec3( Height )
end
--- @type ZONE_GROUP
-- @field Wrapper.Group#GROUP ZoneGROUP
-- @extends Core.Zone#ZONE_RADIUS
-- @extends #ZONE_RADIUS
--- # 5) #ZONE_GROUP class, extends @{Zone#ZONE_RADIUS}
--- # ZONE_GROUP class, extends @{Zone#ZONE_RADIUS}
--
-- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone.
-- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties.
--
-- ===
--
-- @field #ZONE_GROUP ZONE_GROUP
--
-- @field #ZONE_GROUP
ZONE_GROUP = {
ClassName="ZONE_GROUP",
}
@ -777,7 +794,7 @@ function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius )
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) )
self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } )
self.ZoneGROUP = ZoneGROUP
self._.ZoneGROUP = ZoneGROUP
return self
end
@ -789,7 +806,7 @@ end
function ZONE_GROUP:GetVec2()
self:F( self.ZoneName )
local ZoneVec2 = self.ZoneGROUP:GetVec2()
local ZoneVec2 = self._.ZoneGROUP:GetVec2()
self:T( { ZoneVec2 } )
@ -803,7 +820,7 @@ function ZONE_GROUP:GetRandomVec2()
self:F( self.ZoneName )
local Point = {}
local Vec2 = self.ZoneGROUP:GetVec2()
local Vec2 = self._.ZoneGROUP:GetVec2()
local angle = math.random() * math.pi*2;
Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius();
@ -817,17 +834,17 @@ end
--- @type ZONE_POLYGON_BASE
-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}.
-- @extends Core.Zone#ZONE_BASE
-- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}.
-- @extends #ZONE_BASE
--- # 6) ZONE_POLYGON_BASE class, extends @{Zone#ZONE_BASE}
--- # ZONE_POLYGON_BASE class, extends @{Zone#ZONE_BASE}
--
-- The ZONE_POLYGON_BASE class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon.
-- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties.
-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated.
--
-- ## 6.1) Zone point randomization
-- ## Zone point randomization
--
-- Various functions exist to find random points within the zone.
--
@ -835,10 +852,7 @@ end
-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Point#POINT_VEC2} object representing a random 2D point within the zone.
-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone.
--
-- ===
--
-- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE
--
-- @field #ZONE_POLYGON_BASE
ZONE_POLYGON_BASE = {
ClassName="ZONE_POLYGON_BASE",
}
@ -859,24 +873,35 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray )
local i = 0
self.Polygon = {}
self._.Polygon = {}
for i = 1, #PointsArray do
self.Polygon[i] = {}
self.Polygon[i].x = PointsArray[i].x
self.Polygon[i].y = PointsArray[i].y
self._.Polygon[i] = {}
self._.Polygon[i].x = PointsArray[i].x
self._.Polygon[i].y = PointsArray[i].y
end
return self
end
--- Returns the center location of the polygon.
-- @param #ZONE_GROUP self
-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location.
function ZONE_POLYGON_BASE:GetVec2()
self:F( self.ZoneName )
local Bounds = self:GetBoundingSquare()
return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 }
end
--- Flush polygon coordinates as a table in DCS.log.
-- @param #ZONE_POLYGON_BASE self
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:Flush()
self:F2()
self:E( { Polygon = self.ZoneName, Coordinates = self.Polygon } )
self:E( { Polygon = self.ZoneName, Coordinates = self._.Polygon } )
return self
end
@ -892,17 +917,17 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound )
local Segments = 10
i = 1
j = #self.Polygon
j = #self._.Polygon
while i <= #self.Polygon do
self:T( { i, j, self.Polygon[i], self.Polygon[j] } )
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
local DeltaX = self.Polygon[j].x - self.Polygon[i].x
local DeltaY = self.Polygon[j].y - self.Polygon[i].y
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line.
local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments )
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
local Tire = {
["country"] = "USA",
["category"] = "Fortifications",
@ -942,17 +967,17 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor )
local Segments = 10
i = 1
j = #self.Polygon
j = #self._.Polygon
while i <= #self.Polygon do
self:T( { i, j, self.Polygon[i], self.Polygon[j] } )
while i <= #self._.Polygon do
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
local DeltaX = self.Polygon[j].x - self.Polygon[i].x
local DeltaY = self.Polygon[j].y - self.Polygon[i].y
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line.
local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments )
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor )
end
j = i
@ -978,12 +1003,12 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
local InPolygon = false
Next = 1
Prev = #self.Polygon
Prev = #self._.Polygon
while Next <= #self.Polygon do
self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } )
if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and
( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x )
while Next <= #self._.Polygon do
self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } )
if ( ( ( self._.Polygon[Next].y > Vec2.y ) ~= ( self._.Polygon[Prev].y > Vec2.y ) ) and
( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x )
) then
InPolygon = not InPolygon
end
@ -1054,17 +1079,17 @@ end
-- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square.
function ZONE_POLYGON_BASE:GetBoundingSquare()
local x1 = self.Polygon[1].x
local y1 = self.Polygon[1].y
local x2 = self.Polygon[1].x
local y2 = self.Polygon[1].y
local x1 = self._.Polygon[1].x
local y1 = self._.Polygon[1].y
local x2 = self._.Polygon[1].x
local y2 = self._.Polygon[1].y
for i = 2, #self.Polygon do
self:T2( { self.Polygon[i], x1, y1, x2, y2 } )
x1 = ( x1 > self.Polygon[i].x ) and self.Polygon[i].x or x1
x2 = ( x2 < self.Polygon[i].x ) and self.Polygon[i].x or x2
y1 = ( y1 > self.Polygon[i].y ) and self.Polygon[i].y or y1
y2 = ( y2 < self.Polygon[i].y ) and self.Polygon[i].y or y2
for i = 2, #self._.Polygon do
self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1
x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2
y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1
y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2
end
@ -1073,18 +1098,15 @@ end
--- @type ZONE_POLYGON
-- @extends Core.Zone#ZONE_POLYGON_BASE
-- @extends #ZONE_POLYGON_BASE
--- # 7) ZONE_POLYGON class, extends @{Zone#ZONE_POLYGON_BASE}
--- # ZONE_POLYGON class, extends @{Zone#ZONE_POLYGON_BASE}
--
-- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon.
-- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties.
--
-- ===
--
-- @field #ZONE_POLYGON ZONE_POLYGON
--
-- @field #ZONE_POLYGON
ZONE_POLYGON = {
ClassName="ZONE_POLYGON",
}
@ -1100,7 +1122,7 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup )
local GroupPoints = ZoneGroup:GetTaskRoute()
local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) )
self:F( { ZoneName, ZoneGroup, self.Polygon } )
self:F( { ZoneName, ZoneGroup, self._.Polygon } )
return self
end

View File

@ -7,8 +7,25 @@
-- DETECTION classes facilitate the detection of enemy units within the battle zone executed by FACs (Forward Air Controllers) or RECCEs (Reconnassance Units).
-- DETECTION uses the in-built detection capabilities of DCS World, but adds new functionalities.
--
-- Please watch this [youtube video](https://youtu.be/C7p81dUwP-E) that explains the detection concepts.
-- Find the DETECTION classes documentation further in this document in the globals section.
--
-- ====
--
-- # Demo Missions
--
-- ### [DETECTION Demo Missions and Source Code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/DET%20-%20Detection)
--
-- ### [DETECTION Demo Missions, only for Beta Testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/DET%20-%20Detection)
--
-- ### [ALL Demo Missions pack of the Latest Release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [DETECTION YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3Cf5jpI6BS0sBOVWK__tji)
--
-- ====
--
-- ### Contributions:
--
@ -23,16 +40,24 @@
do -- DETECTION_BASE
--- # 1) DETECTION_BASE class, extends @{Fsm#FSM}
--- @type DETECTION_BASE
-- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role.
-- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected.
-- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects.
-- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified.
-- @field #number DetectionRun
-- @extends Core.Fsm#FSM
--- DETECTION_BASE class, extends @{Fsm#FSM}
--
-- The DETECTION_BASE class defines the core functions to administer detected objects.
-- The DETECTION_BASE class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s).
--
-- ## 1.1) DETECTION_BASE constructor
-- ## DETECTION_BASE constructor
--
-- Construct a new DETECTION_BASE instance using the @{#DETECTION_BASE.New}() method.
--
-- ## 1.2) DETECTION_BASE initialization
-- ## Initialization
--
-- By default, detection will return detected objects with all the detection sensors available.
-- However, you can ask how the objects were found with specific detection methods.
@ -48,7 +73,29 @@ do -- DETECTION_BASE
-- * @{#DETECTION_BASE.InitDetectRWR}(): Detected using RWR.
-- * @{#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK.
--
-- ## 1.3) DETECTION_BASE derived classes group the detected units into a DetectedItems[] list
-- ## **Filter** detected units based on **category of the unit**
--
-- Filter the detected units based on Unit.Category using the method @{#DETECTION_BASE.FilterCategories}().
-- The different values of Unit.Category can be:
--
-- * Unit.Category.AIRPLANE
-- * Unit.Category.GROUND_UNIT
-- * Unit.Category.HELICOPTER
-- * Unit.Category.SHIP
-- * Unit.Category.STRUCTURE
--
-- Multiple Unit.Category entries can be given as a table and then these will be evaluated as an OR expression.
--
-- Example to filter a single category (Unit.Category.AIRPLANE).
--
-- DetectionObject:FilterCategories( Unit.Category.AIRPLANE )
--
-- Example to filter multiple categories (Unit.Category.AIRPLANE, Unit.Category.HELICOPTER). Note the {}.
--
-- DetectionObject:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
--
--
-- ## **DETECTION_ derived classes** group the detected units into a **DetectedItems[]** list
--
-- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later
-- of grouping of detected units. Each DetectedItem within the DetectedItems[] list contains
@ -67,7 +114,7 @@ do -- DETECTION_BASE
-- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ).
-- This method retrieves the Set from a DetectedItem element from the DetectedItem list (DetectedItems[ DetectedItemIndex ].Set ).
--
-- ## 1.4) Apply additional Filters to fine-tune the detected objects
-- ## **Visual filters** to fine-tune the probability of the detected objects
--
-- By default, DCS World will return any object that is in LOS and within "visual reach", or detectable through one of the electronic detection means.
-- That being said, the DCS World detection algorithm can sometimes be unrealistic.
@ -88,7 +135,8 @@ do -- DETECTION_BASE
-- I advise however, that, when you first use the DETECTION derived classes, that you don't use these filters.
-- Only when you experience unrealistic behaviour in your missions, these filters could be applied.
--
-- ### 1.4.1 ) Distance visual detection probability
--
-- ### Distance visual detection probability
--
-- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly.
-- Also, the speed of accurate detection plays a role.
@ -102,7 +150,7 @@ do -- DETECTION_BASE
--
-- Use the method @{Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance.
--
-- ### 1.4.2 ) Alpha Angle visual detection probability
-- ### Alpha Angle visual detection probability
--
-- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly.
-- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct.
@ -114,7 +162,7 @@ do -- DETECTION_BASE
--
-- Use the method @{Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°.
--
-- ### 1.4.3 ) Cloudy Zones detection probability
-- ### Cloudy Zones detection probability
--
-- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully.
-- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission
@ -129,12 +177,12 @@ do -- DETECTION_BASE
-- Typically, this kind of filter would be applied for very specific areas were a detection needs to be very realisting for
-- AI not to detect so easily targets within a forrest or village rich area.
--
-- ## 1.5 ) Accept / Reject detected units
-- ## Accept / Reject detected units
--
-- DETECTION_BASE can accept or reject successful detections based on the location of the detected object,
-- if it is located in range or located inside or outside of specific zones.
--
-- ### 1.5.1 ) Detection acceptance of within range limit
-- ### Detection acceptance of within range limit
--
-- A range can be set that will limit a successful detection for a unit.
-- Use the method @{Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted.
@ -151,7 +199,7 @@ do -- DETECTION_BASE
-- Detection:Start()
--
--
-- ### 1.5.2 ) Detection acceptance if within zone(s).
-- ### Detection acceptance if within zone(s).
--
-- Specific ZONE_BASE object(s) can be given as a parameter, which will only accept a detection if the unit is within the specified ZONE_BASE object(s).
-- Use the method @{Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones.
@ -171,7 +219,7 @@ do -- DETECTION_BASE
-- -- Start the Detection.
-- Detection:Start()
--
-- ### 1.5.3 ) Detection rejectance if within zone(s).
-- ### Detection rejectance if within zone(s).
--
-- Specific ZONE_BASE object(s) can be given as a parameter, which will reject detection if the unit is within the specified ZONE_BASE object(s).
-- Use the method @{Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones.
@ -192,29 +240,24 @@ do -- DETECTION_BASE
-- -- Start the Detection.
-- Detection:Start()
--
-- ## 1.6) DETECTION_BASE is a Finite State Machine
-- ## DETECTION_BASE is a Finite State Machine
--
-- Various Events and State Transitions can be tailored using DETECTION_BASE.
--
-- ### 1.6.1) DETECTION_BASE States
-- ### DETECTION_BASE States
--
-- * **Detecting**: The detection is running.
-- * **Stopped**: The detection is stopped.
--
-- ### 1.6.2) DETECTION_BASE Events
-- ### DETECTION_BASE Events
--
-- * **Start**: Start the detection process.
-- * **Detect**: Detect new units.
-- * **Detected**: New units have been detected.
-- * **Stop**: Stop the detection process.
--
-- @field #DETECTION_BASE DETECTION_BASE
--
-- @type DETECTION_BASE
-- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role.
-- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected.
-- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects.
-- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified.
-- @field #number DetectionRun
-- @extends Core.Fsm#FSM
DETECTION_BASE = {
ClassName = "DETECTION_BASE",
DetectionSetGroup = nil,
@ -267,11 +310,19 @@ do -- DETECTION_BASE
self.DetectionInterval = 30
self:InitDetectVisual( true )
self:InitDetectOptical( false )
self:InitDetectRadar( false )
self:InitDetectRWR( false )
self:InitDetectIRST( false )
self:InitDetectDLINK( false )
self:InitDetectOptical( true )
self:InitDetectRadar( true )
self:InitDetectRWR( true )
self:InitDetectIRST( true )
self:InitDetectDLINK( true )
self:FilterCategories( {
Unit.Category.AIRPLANE,
Unit.Category.GROUND_UNIT,
Unit.Category.HELICOPTER,
Unit.Category.SHIP,
Unit.Category.STRUCTURE
} )
-- Create FSM transitions.
@ -498,9 +549,8 @@ do -- DETECTION_BASE
for DetectionObjectID, Detection in pairs( DetectedTargets ) do
local DetectedObject = Detection.object -- Dcs.DCSWrapper.Object#Object
self:T2( DetectedObject )
if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then
if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then
local DetectionAccepted = true
@ -515,10 +565,14 @@ do -- DETECTION_BASE
( DetectedObjectVec3.y - DetectionGroupVec3.y )^2 +
( DetectedObjectVec3.z - DetectionGroupVec3.z )^2
) ^ 0.5 / 1000
local DetectedUnitCategory = DetectedObject:getDesc().category
self:T( { "Detected Target", DetectionGroupName, DetectedObjectName, Distance } )
self:T( { "Detected Target:", DetectionGroupName, DetectedObjectName, Distance, DetectedUnitCategory, DetectedCategory } )
-- Calculate Acceptance
DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false
if self.AcceptRange and Distance > self.AcceptRange then
DetectionAccepted = false
@ -625,10 +679,10 @@ do -- DETECTION_BASE
end
if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then
self:__Detect( self.DetectionInterval )
self:T( "--> Create Detection Sets" )
self:CreateDetectionSets()
self:__Detect( self.DetectionInterval )
end
end
@ -645,6 +699,8 @@ do -- DETECTION_BASE
function DETECTION_BASE:InitDetectVisual( DetectVisual )
self.DetectVisual = DetectVisual
return self
end
--- Detect Optical.
@ -655,6 +711,8 @@ do -- DETECTION_BASE
self:F2()
self.DetectOptical = DetectOptical
return self
end
--- Detect Radar.
@ -665,6 +723,8 @@ do -- DETECTION_BASE
self:F2()
self.DetectRadar = DetectRadar
return self
end
--- Detect IRST.
@ -675,6 +735,8 @@ do -- DETECTION_BASE
self:F2()
self.DetectIRST = DetectIRST
return self
end
--- Detect RWR.
@ -685,6 +747,8 @@ do -- DETECTION_BASE
self:F2()
self.DetectRWR = DetectRWR
return self
end
--- Detect DLINK.
@ -695,9 +759,52 @@ do -- DETECTION_BASE
self:F2()
self.DetectDLINK = DetectDLINK
return self
end
end
do -- Filter methods
--- Filter the detected units based on Unit.Category
-- The different values of Unit.Category can be:
--
-- * Unit.Category.AIRPLANE
-- * Unit.Category.GROUND_UNIT
-- * Unit.Category.HELICOPTER
-- * Unit.Category.SHIP
-- * Unit.Category.STRUCTURE
--
-- Multiple Unit.Category entries can be given as a table and then these will be evaluated as an OR expression.
--
-- Example to filter a single category (Unit.Category.AIRPLANE).
--
-- DetectionObject:FilterCategories( Unit.Category.AIRPLANE )
--
-- Example to filter multiple categories (Unit.Category.AIRPLANE, Unit.Category.HELICOPTER). Note the {}.
--
-- DetectionObject:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
--
-- @param #DETECTION_BASE self
-- @param #list<Dcs.DCSUnit#Unit> FilterCategories The Categories entries
-- @return #DETECTION_BASE self
function DETECTION_BASE:FilterCategories( FilterCategories )
self:F2()
self._.FilterCategories = {}
if type( FilterCategories ) == "table" then
for CategoryID, Category in pairs( FilterCategories ) do
self._.FilterCategories[Category] = Category
end
else
self._.FilterCategories[FilterCategories] = FilterCategories
end
return self
end
end
do

View File

@ -1,112 +1,26 @@
--- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**All** --
-- **Spawn groups of units dynamically in your missions.**
--- **Functional** -- Spawn dynamically new GROUPs in your missions.
--
-- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG)
--
-- ===
-- ====
--
-- # 1) @{#SPAWN} class, extends @{Base#BASE}
-- The documentation of the SPAWN class can be found further in this document.
--
-- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
-- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object.
-- A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods.
-- ====
--
-- Within the SPAWN object, there is an internal index that keeps track of which group from the internal group list was spawned.
-- When new groups get spawned by using the SPAWN methods (see below), it will be validated whether the Limits (@{#SPAWN.Limit}) of the SPAWN object are not reached.
-- When all is valid, a new group will be created by the spawning methods, and the internal index will be increased with 1.
-- # Demo Missions
--
-- Regarding the name of new spawned groups, a _SpawnPrefix_ will be assigned for each new group created.
-- If you want to have the Spawn Template name to be used as the _SpawnPrefix_ name, use the @{#SPAWN.New} constructor.
-- However, when the @{#SPAWN.NewWithAlias} constructor was used, the Alias name will define the _SpawnPrefix_ name.
-- Groups will follow the following naming structure when spawned at run-time:
-- ### [SPAWN Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning)
--
-- 1. Spawned groups will have the name _SpawnPrefix_#ggg, where ggg is a counter from 0 to 999.
-- 2. Spawned units will have the name _SpawnPrefix_#ggg-uu, where uu is a counter from 0 to 99 for each new spawned unit belonging to the group.
--
-- Some additional notes that need to be remembered:
--
-- * Templates are actually groups defined within the mission editor, with the flag "Late Activation" set. As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
-- * It is important to defined BEFORE you spawn new groups, a proper initialization of the SPAWN instance is done with the options you want to use.
-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn Template(s), or the SPAWN module logic won't work anymore.
--
-- ## 1.1) SPAWN construction methods
--
-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
--
-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition).
-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition), and gives each spawned @{Group} an different name.
-- ### [SPAWN Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning)
--
-- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
-- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
--
-- ## 1.2) SPAWN initialization methods
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
-- ====
--
-- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
-- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
-- * @{#SPAWN.InitRepeat}(): Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}.
-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
-- # YouTube Channel
--
-- ## 1.3) SPAWN spawning methods
--
-- Groups can be spawned at different times and methods:
--
-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart}() and @{#SPAWN.SpawnScheduleStop}() to start and stop the schedule respectively.
-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
--
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
--
-- ## 1.4) Retrieve alive GROUPs spawned by the SPAWN object
--
-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
-- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
-- SPAWN provides methods to iterate through that internal GROUP object reference table:
--
-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
--
-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
--
-- ## 1.5) SPAWN object cleaning
--
-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
-- and it may occur that no new groups are or can be spawned as limits are reached.
-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
-- Check the @{#SPAWN.InitCleanUp}() for further info.
--
-- ## 1.6) Catch the @{Group} spawn event in a callback function!
--
-- When using the SpawnScheduled method, new @{Group}s are created following the schedule timing parameters.
-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
-- To SPAWN class supports this functionality through the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method, which takes a function as a parameter that you can define locally.
-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
-- ### [SPAWN YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL)
--
-- ====
--
@ -119,44 +33,34 @@
--
-- Hereby the change log:
--
-- 2017-04-08: SPAWN:**InitDelayOnOff( DelayOnOff )** added.
-- 2017-04-08: SPAWN:**InitDelayOn()** added.
-- 2017-04-08: SPAWN:**InitDelayOff()** added.
--
-- 2017-03-14: SPAWN:**InitKeepUnitNames()** added.
-- 2017-03-14: SPAWN:**InitRandomizePosition( RandomizePosition, OuterRadious, InnerRadius )** added.
-- 2017-03-14: SPAWN:**InitRandomizePosition( RandomizePosition, OuterRadious, InnerRadius )** added.
--
-- 2017-02-04: SPAWN:InitUnControlled( **UnControlled** ) replaces SPAWN:InitUnControlled().
-- 2017-02-04: SPAWN:InitUnControlled( **UnControlled** ) replaces SPAWN:InitUnControlled().
--
-- 2017-01-24: SPAWN:**InitAIOnOff( AIOnOff )** added.
--
-- 2017-01-24: SPAWN:**InitAIOn()** added.
--
-- 2017-01-24: SPAWN:**InitAIOff()** added.
--
-- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ).
-- 2017-01-24: SPAWN:**InitAIOnOff( AIOnOff )** added.
-- 2017-01-24: SPAWN:**InitAIOn()** added.
-- 2017-01-24: SPAWN:**InitAIOff()** added.
--
-- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ).
-- 2016-08-15: SPAWN:**InitRandomizeZones( SpawnZones )** added.
--
-- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ).
--
-- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ).
--
-- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ):
--
-- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ):
--
-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ):
--
-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ):
--
-- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added:
--
-- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ):
--
-- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ).
--
-- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ).
--
-- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ).
--
-- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_().
-- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ).
-- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ).
-- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ).
-- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ).
-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ).
-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ).
-- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added.
-- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ).
-- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ).
-- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ).
-- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ).
-- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_().
--
-- ===
--
@ -177,7 +81,6 @@
--- SPAWN Class
-- @type SPAWN
-- @extends Core.Base#BASE
-- @field ClassName
-- @field #string SpawnTemplatePrefix
-- @field #string SpawnAliasPrefix
@ -186,6 +89,214 @@
-- @field #number SpawnIndex
-- @field #number MaxAliveGroups
-- @field #SPAWN.SpawnZoneTable SpawnZoneTable
-- @extends Core.Base#BASE
--- # SPAWN class, extends @{Base#BASE}
--
-- The SPAWN class allows to spawn dynamically new groups.
-- Each SPAWN object needs to be have a related **template group** setup in the Mission Editor (ME),
-- which is a normal group with the **Late Activation** flag set.
-- This template group will never be activated in your mission.
-- SPAWN uses that **template group** to reference to all the characteristics
-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned.
--
-- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require
-- **the name of the template group** to be given as a string to those constructor methods.
--
-- Initialization settings can be applied on the SPAWN object,
-- which modify the behaviour or the way groups are spawned.
-- These initialization methods have the prefix **Init**.
-- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways.
--
-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!!
--
-- Because SPAWN can spawn multiple groups of a template group,
-- SPAWN has an **internal index** that keeps track
-- which was the latest group that was spawned.
--
-- **Limits** can be set on how many groups can be spawn in each SPAWN object,
-- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits:
--
-- * The maximum amount of @{Unit}s that can be **alive** at the same time...
-- * The maximum amount of @{Group}s that can be **spawned**... This is more of a **resource**-type of limit.
--
-- When new groups get spawned using the **Spawn** methods,
-- it will be evaluated whether any limits have been reached.
-- When no spawn limit is reached, a new group will be created by the spawning methods,
-- and the internal index will be increased with 1.
--
-- These limits ensure that your mission does not accidentally get flooded with spawned groups.
-- Additionally, it also guarantees that independent of the group composition,
-- at any time, the most optimal amount of groups are alive in your mission.
-- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time,
-- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group,
-- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!!
--
-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Group} had been spawned!!!
--
-- Spawned groups get **the same name** as the name of the template group.
-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group.
-- However, because multiple groups and units are created from the template group,
-- a suffix is added to each spawned group and unit.
--
-- Newly spawned groups will get the following naming structure at run-time:
--
-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**,
-- and _nnn_ is a **counter from 0 to 999**.
-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_,
-- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
--
-- That being said, there is a way to keep the same unit names!
-- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus:
--
-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_,
-- where _UnitName_ is the **unit name as defined in the template group*,
-- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group.
--
-- Some **additional notes that need to be considered!!**:
--
-- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set.
-- As such, these groups are never used within the mission, but are used by the @{#SPAWN} module.
-- * It is important to defined BEFORE you spawn new groups,
-- a proper initialization of the SPAWN instance is done with the options you want to use.
-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn template(s),
-- or the SPAWN module logic won't work anymore.
--
-- ## SPAWN construction methods
--
-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods:
--
-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition).
-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Group} an different name.
--
-- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned.
-- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
--
-- ## SPAWN **Init**ialization methods
--
-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix:
--
-- ### Unit Names
--
-- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!!
--
-- ### Route randomization
--
-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height.
--
-- ### Group composition randomization
--
-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
--
-- ### Uncontrolled
--
-- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled.
--
-- ### Array formation
--
-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array.
--
-- ### Position randomization
--
-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius.
-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor.
--
-- ### Enable / Disable AI when spawning a new @{Group}
--
-- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object.
-- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object.
-- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object.
--
-- ### Limit scheduled spawning
--
-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
--
-- ### Delay initial scheduled spawn
--
-- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Group} object.
-- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Group} object.
-- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Group} object.
--
-- ### Repeat spawned @{Group}s upon landing
--
-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
--
--
-- ## SPAWN **Spawn** methods
--
-- Groups can be spawned at different times and methods:
--
-- ### **Single** spawning methods
--
-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index.
-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index.
-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air).
-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ).
-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}.
-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}.
-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}.
--
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
--
-- ### **Scheduled** spawning methods
--
-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals.
-- * @{#SPAWN.SpawnScheduledStart}(): Start or continue to spawn groups at scheduled time intervals.
-- * @{#SPAWN.SpawnScheduledStop}(): Stop the spawning of groups at scheduled time intervals.
--
--
--
-- ## Retrieve alive GROUPs spawned by the SPAWN object
--
-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution.
-- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS.
-- SPAWN provides methods to iterate through that internal GROUP object reference table:
--
-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found.
-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found.
-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found.
--
-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example.
-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive...
--
-- ## Spawned cleaning of inactive groups
--
-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
-- and it may occur that no new groups are or can be spawned as limits are reached.
-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group.
-- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time.
-- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"...
-- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically.
-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
-- Check the @{#SPAWN.InitCleanUp}() for further info.
--
-- ## Catch the @{Group} Spawn Event in a callback function!
--
-- When using the @{#SPAWN.SpawnScheduled)() method, new @{Group}s are created following the spawn time interval parameters.
-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event.
-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ),
-- which takes a function as a parameter that you can define locally.
-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter.
-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object.
-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method.
--
-- ## Delay the initial spawning
--
-- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Group}
-- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to
-- activate a delay before the first @{Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that
-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a
-- @{#SPAWN.SpawnScheduledStop}() ; @{#SPAWN.SpawnScheduledStart}() sequence would have been used.
--
--
-- @field #SPAWN SPAWN
--
SPAWN = {
ClassName = "SPAWN",
SpawnTemplatePrefix = nil,
@ -227,6 +338,7 @@ function SPAWN:New( SpawnTemplatePrefix )
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else
@ -270,6 +382,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else
@ -626,6 +739,36 @@ do -- AI methods
end -- AI methods
do -- Delay methods
--- Turns the Delay On or Off for the first @{Group} scheduled spawning.
-- The default value is that for scheduled spawning, there is an initial delay when spawning the first @{Group}.
-- @param #SPAWN self
-- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off.
-- @return #SPAWN The SPAWN object
function SPAWN:InitDelayOnOff( DelayOnOff )
self.DelayOnOff = DelayOnOff
return self
end
--- Turns the Delay On for the @{Group} when spawning.
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWN:InitDelayOn()
return self:InitDelayOnOff( true )
end
--- Turns the Delay Off for the @{Group} when spawning.
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWN:InitDelayOff()
return self:InitDelayOnOff( false )
end
end -- Delay methods
--- Will spawn a group based on the internal index.
-- Note: Uses @{DATABASE} module defined in MOOSE.
-- @param #SPAWN self
@ -669,6 +812,8 @@ function SPAWN:ReSpawn( SpawnIndex )
SpawnGroup:ReSpawnFunction()
end
SpawnGroup:ResetEvents()
return SpawnGroup
end
@ -787,7 +932,11 @@ function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
self:F( { SpawnTime, SpawnTimeVariation } )
if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, 1, SpawnTime, SpawnTimeVariation )
local InitialDelay = 0
if self.DelayOnOff == true then
InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
end
self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
end
return self
@ -795,17 +944,23 @@ end
--- Will re-start the spawning scheduler.
-- Note: This method is only required to be called when the schedule was stopped.
-- @param #SPAWN self
-- @return #SPAWN
function SPAWN:SpawnScheduleStart()
self:F( { self.SpawnTemplatePrefix } )
self.SpawnScheduler:Start()
return self
end
--- Will stop the scheduled spawning scheduler.
-- @param #SPAWN self
-- @return #SPAWN
function SPAWN:SpawnScheduleStop()
self:F( { self.SpawnTemplatePrefix } )
self.SpawnScheduler:Stop()
return self
end
@ -926,7 +1081,7 @@ end
function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } )
if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then
if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then
return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex )
end

View File

@ -117,6 +117,21 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
end
)
-- Handle when a player leaves a slot and goes back to spectators ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
self:HandleEvent( EVENTS.MissionEnd,
--- @param #TASK self
-- @param Core.Event#EVENTDATA EventData
function( self, EventData )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
Mission:Stop()
end
end
)
-- Handle when a player leaves a slot and goes back to spectators ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
@ -127,7 +142,9 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
Mission:AbortUnit( PlayerUnit )
if Mission:IsOngoing() then
Mission:AbortUnit( PlayerUnit )
end
end
end
)
@ -257,8 +274,7 @@ end
-- @param #sring Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown.
function COMMANDCENTER:MessageToGroup( Message, TaskGroup, Name )
local Prefix = "@ Group"
Prefix = Prefix .. ( Name and " (" .. Name .. "): " or '' )
local Prefix = Name and "@ " .. Name .. ": " or "@ " .. TaskGroup:GetCallsign() .. ": "
Message = Prefix .. Message
self:GetPositionable():MessageToGroup( Message , 20, TaskGroup, self:GetName() )

View File

@ -216,7 +216,7 @@ function TASK:SetUnitProcess( FsmTemplate )
end
--- Add a PlayerUnit to join the Task.
-- For each Group within the Task, the Unit is check if it can join the Task.
-- For each Group within the Task, the Unit is checked if it can join the Task.
-- If the Unit was not part of the Task, false is returned.
-- If the Unit is part of the Task, true is returned.
-- @param #TASK self
@ -275,8 +275,9 @@ function TASK:AbortUnit( PlayerUnit )
local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup )
self:E( { IsAssignedToGroup = IsAssignedToGroup } )
if IsAssignedToGroup then
local PlayerName = PlayerUnit:GetPlayerName()
self:UnAssignFromUnit( PlayerUnit )
self:MessageToGroups( PlayerUnit:GetPlayerName() .. " aborted Task " .. self:GetName() )
self:MessageToGroups( PlayerName .. " aborted Task " .. self:GetName() )
self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } )
if #PlayerGroup:GetUnits() == 1 then
self:UnAssignFromGroup( PlayerGroup )
@ -284,6 +285,7 @@ function TASK:AbortUnit( PlayerUnit )
self:RemoveMenuForGroup( PlayerGroup )
end
self:Abort()
self:PlayerAborted( PlayerUnit )
end
end
end

View File

@ -72,6 +72,8 @@ do -- TASK_A2G_DISPATCHER
self.Detection = Detection
self.Mission = Mission
self.Detection:FilterCategories( Unit.Category.GROUND_UNIT, Unit.Category.SHIP )
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.

View File

@ -0,0 +1,813 @@
--- **Tasking (Release 2.1)** -- The TASK_CARGO models tasks for players to transport @{Cargo}.
--
-- ![Banner Image](..\Presentations\TASK_CARGO\Dia1.JPG)
--
-- ====
--
-- The Moose framework provides various CARGO classes that allow DCS phisical or logical objects to be transported or sling loaded by Carriers.
-- The CARGO_ classes, as part of the moose core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units.
--
-- This collection of classes in this module define tasks for human players to handle these cargo objects.
-- Cargo can be transported, picked-up, deployed and sling-loaded from and to other places.
--
-- The following classes are important to consider:
--
-- * @{#TASK_CARGO_TRANSPORT}: Defines a task for a human player to transport a set of cargo between various zones.
--
-- ==
--
-- # **API CHANGE HISTORY**
--
-- The underlying change log documents the API changes. Please read this carefully. The following notation is used:
--
-- * **Added** parts are expressed in bold type face.
-- * _Removed_ parts are expressed in italic type face.
--
-- Hereby the change log:
--
-- 2017-03-09: Revised version.
--
-- ===
--
-- # **AUTHORS and CONTRIBUTIONS**
--
-- ### Contributions:
--
-- ### Authors:
--
-- * **FlightControl**: Concept, Design & Programming.
--
-- @module Task_Cargo
do -- TASK_CARGO
--- @type TASK_CARGO
-- @extends Tasking.Task#TASK
---
-- # TASK_CARGO class, extends @{Task#TASK}
--
-- ## A flexible tasking system
--
-- The TASK_CARGO classes provide you with a flexible tasking sytem,
-- that allows you to transport cargo of various types between various locations
-- and various dedicated deployment zones.
--
-- The cargo in scope of the TASK_CARGO classes must be explicitly given, and is of type SET_CARGO.
-- The SET_CARGO contains a collection of CARGO objects that must be handled by the players in the mission.
--
--
-- ## Task execution experience from the player perspective
--
-- A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J).
-- The player needs to accept the task from the task overview list within the mission, using the radio menus.
--
-- Once the TASK_CARGO is assigned to the player and accepted by the player, the player will obtain
-- an extra **Cargo Handling Radio Menu** that contains the CARGO objects that need to be transported.
--
-- Each CARGO object has a certain state:
--
-- * **UnLoaded**: The CARGO is located within the battlefield. It may still need to be transported.
-- * **Loaded**: The CARGO is loaded within a Carrier. This can be your air unit, or another air unit, or even a vehicle.
-- * **Boarding**: The CARGO is running or moving towards your Carrier for loading.
-- * **UnBoarding**: The CARGO is driving or jumping out of your Carrier and moves to a location in the Deployment Zone.
--
-- Cargo must be transported towards different **Deployment @{Zone}s**.
--
-- The Cargo Handling Radio Menu system allows to execute **various actions** to handle the cargo.
-- In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed.
-- Depending on the location of your Carrier unit, the menu options will vary.
--
--
-- ## Cargo Pickup and Boarding
--
-- For cargo boarding, a cargo can only execute the boarding actions if it is within the foreseen **Reporting Range**.
-- Therefore, it is important that you steer your Carrier within the Reporting Range,
-- so that boarding actions can be executed on the cargo.
-- To Pickup and Board cargo, the following menu items will be shown in your carrier radio menu:
--
-- ### Board Cargo
--
-- If your Carrier is within the Reporting Range of the cargo, it will allow to pickup the cargo by selecting this menu option.
-- Depending on the Cargo type, the cargo will either move to your Carrier or you will receive instructions how to handle the cargo
-- pickup. If the cargo moves to your carrier, it will indicate the boarding status.
-- Note that multiple units need to board your Carrier, so it is required to await the full boarding process.
-- Once the cargo is fully boarded within your Carrier, you will be notified of this.
--
-- Note that for airborne Carriers, it is required to land first before the Boarding process can be initiated.
-- If during boarding the Carrier gets airborne, the boarding process will be cancelled.
--
-- ## Pickup Cargo
--
-- If your Carrier is not within the Reporting Range of the cargo, the HQ will guide you to its location.
-- Routing information is shown in flight that directs you to the cargo within Reporting Range.
-- Upon arrival, the Cargo will contact you and further instructions will be given.
-- When your Carrier is airborne, you will receive instructions to land your Carrier.
-- The action will not be completed until you've landed your Carrier.
--
--
-- ## Cargo Deploy and UnBoarding
--
-- Various Deployment Zones can be foreseen in the scope of the Cargo transportation. Each deployment zone can be of varying @{Zone} type.
-- The Cargo Handling Radio Menu provides with menu options to execute an action to steer your Carrier to a specific Zone.
--
-- ### UnBoard Cargo
--
-- If your Carrier is already within a Deployment Zone,
-- then the Cargo Handling Radio Menu allows to **UnBoard** a specific cargo that is
-- loaded within your Carrier group into the Deployment Zone.
-- Note that the Unboarding process takes a while, as the cargo units (infantry or vehicles) must unload from your Carrier.
-- Ensure that you stay at the position or stay on the ground while Unboarding.
-- If any unforeseen manoeuvre is done by the Carrier, then the Unboarding will be cancelled.
--
-- ### Deploy Cargo
--
-- If your Carrier is not within a Deployment Zone, you'll need to fly towards one.
-- Fortunately, the Cargo Handling Radio Menu provides you with menu options to select a specific Deployment Zone to fly towards.
-- Once a Deployment Zone has been selected, your Carrier will receive routing information from HQ towards the Deployment Zone center.
-- Upon arrival, the HQ will provide you with further instructions.
-- When your Carrier is airborne, you will receive instructions to land your Carrier.
-- The action will not be completed until you've landed your Carrier!
--
-- ## Handle TASK_CARGO Events ...
--
-- The TASK_CARGO classes define @{Cargo} transport tasks,
-- based on the tasking capabilities defined in @{Task#TASK}.
--
-- ### Specific TASK_CARGO Events
--
-- Specific Cargo Handling event can be captured, that allow to trigger specific actions!
--
-- * **Boarded**: Triggered when the Cargo has been Boarded into your Carrier.
-- * **UnBoarded**: Triggered when the cargo has been Unboarded from your Carrier and has arrived at the Deployment Zone.
--
-- ### Standard TASK_CARGO Events
--
-- The TASK_CARGO is implemented using a @{Statemachine#FSM_TASK}, and has the following standard statuses:
--
-- * **None**: Start of the process.
-- * **Planned**: The cargo task is planned.
-- * **Assigned**: The cargo task is assigned to a @{Group#GROUP}.
-- * **Success**: The cargo task is successfully completed.
-- * **Failed**: The cargo task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
--
-- ===
--
-- @field #TASK_CARGO TASK_CARGO
--
TASK_CARGO = {
ClassName = "TASK_CARGO",
}
--- Instantiates a new TASK_CARGO.
-- @param #TASK_CARGO self
-- @param Tasking.Mission#MISSION Mission
-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported.
-- @param #string TaskType The type of Cargo task.
-- @return #TASK_CARGO self
function TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, TaskType )
local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) -- #TASK_CARGO
self:F( {Mission, SetGroup, TaskName, SetCargo, TaskType})
self.SetCargo = SetCargo
self.TaskType = TaskType
self.DeployZones = {} -- setmetatable( {}, { __mode = "v" } ) -- weak table on value
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } )
Fsm:AddTransition( { "Assigned", "WaitingForCommand", "ArrivedAtPickup", "ArrivedAtDeploy", "Boarded", "UnBoarded" }, "SelectAction", "WaitingForCommand" )
Fsm:AddTransition( "WaitingForCommand", "RouteToPickup", "RoutingToPickup" )
Fsm:AddProcess ( "RoutingToPickup", "RouteToPickupPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtPickup" } )
Fsm:AddTransition( "Arrived", "ArriveAtPickup", "ArrivedAtPickup" )
Fsm:AddTransition( "WaitingForCommand", "RouteToDeploy", "RoutingToDeploy" )
Fsm:AddProcess ( "RoutingToDeploy", "RouteToDeployZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtDeploy" } )
Fsm:AddTransition( "Arrived", "ArriveAtDeploy", "ArrivedAtDeploy" )
Fsm:AddTransition( { "ArrivedAtPickup", "ArrivedAtDeploy", "Landing" }, "Land", "Landing" )
Fsm:AddTransition( "Landing", "Landed", "Landed" )
Fsm:AddTransition( "WaitingForCommand", "PrepareBoarding", "AwaitBoarding" )
Fsm:AddTransition( "AwaitBoarding", "Board", "Boarding" )
Fsm:AddTransition( "Boarding", "Boarded", "Boarded" )
Fsm:AddTransition( "WaitingForCommand", "PrepareUnBoarding", "AwaitUnBoarding" )
Fsm:AddTransition( "AwaitUnBoarding", "UnBoard", "UnBoarding" )
Fsm:AddTransition( "UnBoarding", "UnBoarded", "UnBoarded" )
Fsm:AddTransition( "Deployed", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onenterWaitingForCommand( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if TaskUnit.Menu then
TaskUnit.Menu:Remove()
end
TaskUnit.Menu = MENU_GROUP:New( TaskUnit:GetGroup(), Task:GetName() .. " @ " .. TaskUnit:GetName() )
Task.SetCargo:ForEachCargo(
--- @param Core.Cargo#CARGO Cargo
function( Cargo )
if Cargo:IsUnLoaded() then
if Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Board cargo " .. Cargo.Name,
TaskUnit.Menu,
self.MenuBoardCargo,
self,
Cargo
)
else
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Route to Pickup cargo " .. Cargo.Name,
TaskUnit.Menu,
self.MenuRouteToPickup,
self,
Cargo
)
end
end
if Cargo:IsLoaded() then
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if Cargo:IsInZone( DeployZone ) then
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Unboard cargo " .. Cargo.Name,
TaskUnit.Menu,
self.MenuUnBoardCargo,
self,
Cargo,
DeployZone
)
else
MENU_GROUP_COMMAND:New(
TaskUnit:GetGroup(),
"Route to Deploy cargo at " .. DeployZoneName,
TaskUnit.Menu,
self.MenuRouteToDeploy,
self,
DeployZone
)
end
end
end
end
)
self:__SelectAction( -15 )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:OnLeaveWaitingForCommand( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
TaskUnit.Menu:Remove()
end
function Fsm:MenuBoardCargo( Cargo )
self:__PrepareBoarding( 1.0, Cargo )
end
function Fsm:MenuUnBoardCargo( Cargo, DeployZone )
self:__PrepareUnBoarding( 1.0, Cargo, DeployZone )
end
function Fsm:MenuRouteToPickup( Cargo )
self:__RouteToPickup( 1.0, Cargo )
end
function Fsm:MenuRouteToDeploy( DeployZone )
self:__RouteToDeploy( 1.0, DeployZone )
end
--- Route to Cargo
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterRouteToPickup( TaskUnit, Task, From, Event, To, Cargo )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self.Cargo = Cargo
Task:SetCargoPickup( self.Cargo, TaskUnit )
self:__RouteToPickupPoint( -0.1 )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterArriveAtPickup( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if TaskUnit:IsAir() then
self:__Land( -0.1, "Pickup" )
else
self:__SelectAction( -0.1 )
end
end
--- Route to DeployZone
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
function Fsm:onafterRouteToDeploy( TaskUnit, Task, From, Event, To, DeployZone )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self.DeployZone = DeployZone
Task:SetDeployZone( self.DeployZone, TaskUnit )
self:__RouteToDeployZone( -0.1 )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterArriveAtDeploy( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if TaskUnit:IsAir() then
self:__Land( -0.1, "Deploy" )
else
self:__SelectAction( -0.1 )
end
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() )
self:__Land( -10, Action )
else
Task:GetMission():GetCommandCenter():MessageToGroup( "Landed ...", TaskUnit:GetGroup() )
self:__Landed( -0.1, Action )
end
else
if Action == "Pickup" then
self:__RouteToPickupZone( -0.1 )
else
self:__RouteToDeployZone( -0.1 )
end
end
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
self:__Land( -0.1, Action )
else
self:__SelectAction( -0.1 )
end
else
if Action == "Pickup" then
self:__RouteToPickupZone( -0.1 )
else
self:__RouteToDeployZone( -0.1 )
end
end
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterPrepareBoarding( TaskUnit, Task, From, Event, To, Cargo )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self.Cargo = Cargo -- Core.Cargo#CARGO_GROUP
self:__Board( -0.1 )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterBoard( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
function self.Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess )
self:E({From, Event, To, TaskUnit, TaskProcess })
TaskProcess:__Boarded( 0.1 )
end
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
--- ABORT the boarding. Split group if any and go back to select action.
else
self.Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() )
self.Cargo:Board( TaskUnit, 20, self )
end
else
--self:__ArriveAtCargo( -0.1 )
end
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterBoarded( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self.Cargo:MessageToGroup( "Boarded ...", TaskUnit:GetGroup() )
self:__SelectAction( 1 )
-- TODO:I need to find a more decent solution for this.
Task:E( { CargoPickedUp = Task.CargoPickedUp } )
if Task.CargoPickedUp then
Task:CargoPickedUp( TaskUnit, self.Cargo )
end
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterPrepareUnBoarding( TaskUnit, Task, From, Event, To, Cargo, DeployZone )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self.Cargo = Cargo
self.DeployZone = DeployZone
self:__UnBoard( -0.1 )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterUnBoard( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
function self.Cargo:OnEnterUnLoaded( From, Event, To, DeployZone, TaskProcess )
self:E({From, Event, To, TaskUnit, TaskProcess })
TaskProcess:__UnBoarded( -0.1 )
end
self.Cargo:MessageToGroup( "UnBoarding ...", TaskUnit:GetGroup() )
self.Cargo:UnBoard( self.DeployZone:GetPointVec2(), 20, self )
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterUnBoarded( TaskUnit, Task )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self.Cargo:MessageToGroup( "UnBoarded ...", TaskUnit:GetGroup() )
-- TODO:I need to find a more decent solution for this.
Task:E( { CargoDeployed = Task.CargoDeployed } )
if Task.CargoDeployed then
Task:CargoDeployed( TaskUnit, self.Cargo, self.DeployZone )
end
self:__SelectAction( 1 )
end
return self
end
--- @param #TASK_CARGO self
function TASK_CARGO:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
end
--- @param #TASK_CARGO self
-- @return Core.Set#SET_CARGO The Cargo Set.
function TASK_CARGO:GetCargoSet()
return self.SetCargo
end
--- @param #TASK_CARGO self
-- @return #list<Core.Zone#ZONE_BASE> The Deployment Zones.
function TASK_CARGO:GetDeployZones()
return self.DeployZones
end
--- @param #TASK_CARGO self
-- @param AI.AI_Cargo#AI_CARGO Cargo The cargo.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetCargoPickup( Cargo, TaskUnit )
self:F({Cargo, TaskUnit})
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteCargo = ProcessUnit:GetProcess( "RoutingToPickup", "RouteToPickupPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteCargo:SetPointVec2( Cargo:GetPointVec2() )
ActRouteCargo:SetRange( Cargo:GetBoardingRange() )
return self
end
--- @param #TASK_CARGO self
-- @param Core.Zone#ZONE DeployZone
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetDeployZone( DeployZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteDeployZone = ProcessUnit:GetProcess( "RoutingToDeploy", "RouteToDeployZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteDeployZone:SetZone( DeployZone )
return self
end
--- @param #TASK_CARGO self
-- @param Core.Zone#ZONE DeployZone
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:AddDeployZone( DeployZone, TaskUnit )
self.DeployZones[DeployZone:GetName()] = DeployZone
return self
end
--- @param #TASK_CARGO self
-- @param Core.Zone#ZONE DeployZone
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:RemoveDeployZone( DeployZone, TaskUnit )
self.DeployZones[DeployZone:GetName()] = nil
return self
end
--- @param #TASK_CARGO self
-- @param @list<Core.Zone#ZONE> DeployZones
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetDeployZones( DeployZones, TaskUnit )
for DeployZoneID, DeployZone in pairs( DeployZones ) do
self.DeployZones[DeployZone:GetName()] = DeployZone
end
return self
end
--- @param #TASK_CARGO self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
function TASK_CARGO:GetTargetZone( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteTarget:GetZone()
end
--- Set a score when a target in scope of the A2G attack, has been destroyed .
-- @param #TASK_CARGO self
-- @param #string Text The text to display to the player, when the target has been destroyed.
-- @param #number Score The score in points.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetScoreOnDestroy( Text, Score, TaskUnit )
self:F( { Text, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScoreProcess( "Engaging", "Account", "Account", Text, Score )
return self
end
--- Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- @param #TASK_CARGO self
-- @param #string Text The text to display to the player, when all targets hav been destroyed.
-- @param #number Score The score in points.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetScoreOnSuccess( Text, Score, TaskUnit )
self:F( { Text, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Success", Text, Score )
return self
end
--- Set a penalty when the A2G attack has failed.
-- @param #TASK_CARGO self
-- @param #string Text The text to display to the player, when the A2G attack has failed.
-- @param #number Penalty The penalty in points.
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetPenaltyOnFailed( Text, Penalty, TaskUnit )
self:F( { Text, Score, TaskUnit } )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
ProcessUnit:AddScore( "Failed", Text, Penalty )
return self
end
end
do -- TASK_CARGO_TRANSPORT
--- The TASK_CARGO_TRANSPORT class
-- @type TASK_CARGO_TRANSPORT
-- @extends #TASK_CARGO
TASK_CARGO_TRANSPORT = {
ClassName = "TASK_CARGO_TRANSPORT",
}
--- Instantiates a new TASK_CARGO_TRANSPORT.
-- @param #TASK_CARGO_TRANSPORT self
-- @param Tasking.Mission#MISSION Mission
-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported.
-- @return #TASK_CARGO_TRANSPORT self
function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, SetCargo )
local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "Transport" ) ) -- #TASK_CARGO_TRANSPORT
self:F()
Mission:AddTask( self )
-- Events
self:AddTransition( "*", "CargoPickedUp", "*" )
self:AddTransition( "*", "CargoDeployed", "*" )
do
--- OnBefore Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnAfterCargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- Synchronous Event Trigger for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] CargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- Asynchronous Event Trigger for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] __CargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param #number Delay The delay in seconds.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
end
do
--- OnBefore Transition Handler for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnAfterCargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
--- Synchronous Event Trigger for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] CargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
--- Asynchronous Event Trigger for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] __CargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param #number Delay The delay in seconds.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
end
local Fsm = self:GetUnitProcess()
return self
end
---
-- @param #TASK_CARGO_TRANSPORT self
-- @return #boolean
function TASK_CARGO_TRANSPORT:IsAllCargoTransported()
local CargoSet = self:GetCargoSet()
local Set = CargoSet:GetSet()
local DeployZones = self:GetDeployZones()
local CargoDeployed = true
-- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ).
for CargoID, CargoData in pairs( Set ) do
local Cargo = CargoData -- Core.Cargo#CARGO
-- Loop the DeployZones set for the TASK_CARGO_TRANSPORT.
for DeployZoneID, DeployZone in pairs( DeployZones ) do
-- If there is a Cargo not in one of DeployZones, then not all Cargo is deployed.
self:T( { Cargo.CargoObject } )
if Cargo:IsInZone( DeployZone ) then
else
CargoDeployed = false
end
end
end
return CargoDeployed
end
end

View File

@ -156,6 +156,7 @@ end
-- @param #GROUP self
-- @return Dcs.DCSWrapper.Group#Group The DCS Group.
function GROUP:GetDCSObject()
self:F(self.GroupName)
local DCSGroup = Group.getByName( self.GroupName )
if DCSGroup then
@ -319,6 +320,7 @@ function GROUP:GetUnit( UnitNumber )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnit = DCSGroup:getUnit( UnitNumber )
local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) )
self:T2( UnitFound )
return UnitFound
@ -834,6 +836,9 @@ function GROUP:Respawn( Template )
self:Destroy()
_DATABASE:Spawn( Template )
self:ResetEvents()
end
--- Returns the group template from the @{DATABASE} (_DATABASE object).
@ -1077,7 +1082,21 @@ do -- Event Handling
-- @return #GROUP
function GROUP:UnHandleEvent( Event )
self:EventDispatcher():RemoveForGroup( self:GetName(), self, Event )
self:EventDispatcher():Remove( self, Event )
return self
end
--- Reset the subscriptions.
-- @param #GROUP self
-- @return #GROUP
function GROUP:ResetEvents()
self:EventDispatcher():Reset( self )
for UnitID, UnitData in pairs( self:GetUnits() ) do
UnitData:ResetEvents()
end
return self
end

View File

@ -640,7 +640,7 @@ function UNIT:GetThreatLevel()
"Bomber",
"Strategic Bomber",
"Attack Helicopter",
"Interceptor",
"Battleplane",
"Multirole Fighter",
"Fighter"
}
@ -990,5 +990,16 @@ do -- Event Handling
return self
end
--- Reset the subscriptions.
-- @param #UNIT self
-- @return #UNIT
function UNIT:ResetEvents()
self:EventDispatcher():Reset( self )
return self
end
end

View File

@ -0,0 +1,31 @@
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
env.info( 'Moose Generation Timestamp: 20170328_0728' )
local base = _G
Include = {}
Include.File = function( IncludeFile )
if not Include.Files[ IncludeFile ] then
Include.Files[IncludeFile] = IncludeFile
env.info( "Include:" .. IncludeFile .. " from " .. Include.ProgramPath )
local f = assert( base.loadfile( Include.ProgramPath .. IncludeFile .. ".lua" ) )
if f == nil then
error ("Could not load MOOSE file " .. IncludeFile .. ".lua" )
else
env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath )
return f()
end
end
end
Include.ProgramPath = "Scripts/Moose/"
env.info( "Include.ProgramPath = " .. Include.ProgramPath)
Include.Files = {}
Include.File( "Moose" )
BASE:TraceOnOff( true )
env.info( '*** MOOSE INCLUDE END *** ' )

View File

@ -13,6 +13,8 @@ Core/Point.lua
Core/Message.lua
Core/Fsm.lua
Core/Radio.lua
Core/SpawnStatic.lua
Core/Cargo.lua
Wrapper/Object.lua
Wrapper/Identifiable.lua
@ -39,7 +41,6 @@ AI/AI_Balancer.lua
AI/AI_Patrol.lua
AI/AI_Cap.lua
AI/AI_Cas.lua
AI/AI_Cargo.lua
Actions/Act_Assign.lua
Actions/Act_Route.lua
@ -52,5 +53,6 @@ Tasking/Task.lua
Tasking/DetectionManager.lua
Tasking/Task_A2G_Dispatcher.lua
Tasking/Task_A2G.lua
Tasking/Task_Cargo.lua
Moose.lua

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +0,0 @@
@echo off
"C:\Program Files\lua\5.1\bin\lua.exe" -e "package.path=\"C:\\Users\\svenv\\AppData\\Roaming/luarocks/share/lua/5.1/?.lua;C:\\Users\\svenv\\AppData\\Roaming/luarocks/share/lua/5.1/?/init.lua;c:\\program files\\lua\\5.1\\/share/lua/5.1/?.lua;c:\\program files\\lua\\5.1\\/share/lua/5.1/?/init.lua;C:\\Program Files (x86)\\LuaRocks\\lua\\?.lua;\"..package.path; package.cpath=\"C:\\Users\\svenv\\AppData\\Roaming/luarocks/lib/lua/5.1/?.dll;c:\\program files\\lua\\5.1\\/lib/lua/5.1/?.dll;\"..package.cpath" -e "local k,l,_=pcall(require,\"luarocks.loader\") _=k and l.add_context(\"luadocumentor\",\"0.1.5-1\")" "c:\program files\lua\5.1\\lib\luarocks\rocks\luadocumentor\0.1.5-1\bin\luadocumentor" %*
exit /b %ERRORLEVEL%

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,163 +0,0 @@
.\" $Id: lua.man,v 1.11 2006/01/06 16:03:34 lhf Exp $
.TH LUA 1 "$Date: 2006/01/06 16:03:34 $"
.SH NAME
lua \- Lua interpreter
.SH SYNOPSIS
.B lua
[
.I options
]
[
.I script
[
.I args
]
]
.SH DESCRIPTION
.B lua
is the stand-alone Lua interpreter.
It loads and executes Lua programs,
either in textual source form or
in precompiled binary form.
(Precompiled binaries are output by
.BR luac ,
the Lua compiler.)
.B lua
can be used as a batch interpreter and also interactively.
.LP
The given
.I options
(see below)
are executed and then
the Lua program in file
.I script
is loaded and executed.
The given
.I args
are available to
.I script
as strings in a global table named
.BR arg .
If these arguments contain spaces or other characters special to the shell,
then they should be quoted
(but note that the quotes will be removed by the shell).
The arguments in
.B arg
start at 0,
which contains the string
.RI ' script '.
The index of the last argument is stored in
.BR arg.n .
The arguments given in the command line before
.IR script ,
including the name of the interpreter,
are available in negative indices in
.BR arg .
.LP
At the very start,
before even handling the command line,
.B lua
executes the contents of the environment variable
.BR LUA_INIT ,
if it is defined.
If the value of
.B LUA_INIT
is of the form
.RI '@ filename ',
then
.I filename
is executed.
Otherwise, the string is assumed to be a Lua statement and is executed.
.LP
Options start with
.B '\-'
and are described below.
You can use
.B "'\--'"
to signal the end of options.
.LP
If no arguments are given,
then
.B "\-v \-i"
is assumed when the standard input is a terminal;
otherwise,
.B "\-"
is assumed.
.LP
In interactive mode,
.B lua
prompts the user,
reads lines from the standard input,
and executes them as they are read.
If a line does not contain a complete statement,
then a secondary prompt is displayed and
lines are read until a complete statement is formed or
a syntax error is found.
So, one way to interrupt the reading of an incomplete statement is
to force a syntax error:
adding a
.B ';'
in the middle of a statement is a sure way of forcing a syntax error
(except inside multiline strings and comments; these must be closed explicitly).
If a line starts with
.BR '=' ,
then
.B lua
displays the values of all the expressions in the remainder of the
line. The expressions must be separated by commas.
The primary prompt is the value of the global variable
.BR _PROMPT ,
if this value is a string;
otherwise, the default prompt is used.
Similarly, the secondary prompt is the value of the global variable
.BR _PROMPT2 .
So,
to change the prompts,
set the corresponding variable to a string of your choice.
You can do that after calling the interpreter
or on the command line
(but in this case you have to be careful with quotes
if the prompt string contains a space; otherwise you may confuse the shell.)
The default prompts are "> " and ">> ".
.SH OPTIONS
.TP
.B \-
load and execute the standard input as a file,
that is,
not interactively,
even when the standard input is a terminal.
.TP
.BI \-e " stat"
execute statement
.IR stat .
You need to quote
.I stat
if it contains spaces, quotes,
or other characters special to the shell.
.TP
.B \-i
enter interactive mode after
.I script
is executed.
.TP
.BI \-l " name"
call
.BI require(' name ')
before executing
.IR script .
Typically used to load libraries.
.TP
.B \-v
show version information.
.SH "SEE ALSO"
.BR luac (1)
.br
http://www.lua.org/
.SH DIAGNOSTICS
Error messages should be self explanatory.
.SH AUTHORS
R. Ierusalimschy,
L. H. de Figueiredo,
and
W. Celes
.\" EOF

View File

@ -1,136 +0,0 @@
.\" $Id: luac.man,v 1.28 2006/01/06 16:03:34 lhf Exp $
.TH LUAC 1 "$Date: 2006/01/06 16:03:34 $"
.SH NAME
luac \- Lua compiler
.SH SYNOPSIS
.B luac
[
.I options
] [
.I filenames
]
.SH DESCRIPTION
.B luac
is the Lua compiler.
It translates programs written in the Lua programming language
into binary files that can be later loaded and executed.
.LP
The main advantages of precompiling chunks are:
faster loading,
protecting source code from accidental user changes,
and
off-line syntax checking.
.LP
Pre-compiling does not imply faster execution
because in Lua chunks are always compiled into bytecodes before being executed.
.B luac
simply allows those bytecodes to be saved in a file for later execution.
.LP
Pre-compiled chunks are not necessarily smaller than the corresponding source.
The main goal in pre-compiling is faster loading.
.LP
The binary files created by
.B luac
are portable only among architectures with the same word size and byte order.
.LP
.B luac
produces a single output file containing the bytecodes
for all source files given.
By default,
the output file is named
.BR luac.out ,
but you can change this with the
.B \-o
option.
.LP
In the command line,
you can mix
text files containing Lua source and
binary files containing precompiled chunks.
This is useful to combine several precompiled chunks,
even from different (but compatible) platforms,
into a single precompiled chunk.
.LP
You can use
.B "'\-'"
to indicate the standard input as a source file
and
.B "'\--'"
to signal the end of options
(that is,
all remaining arguments will be treated as files even if they start with
.BR "'\-'" ).
.LP
The internal format of the binary files produced by
.B luac
is likely to change when a new version of Lua is released.
So,
save the source files of all Lua programs that you precompile.
.LP
.SH OPTIONS
Options must be separate.
.TP
.B \-l
produce a listing of the compiled bytecode for Lua's virtual machine.
Listing bytecodes is useful to learn about Lua's virtual machine.
If no files are given, then
.B luac
loads
.B luac.out
and lists its contents.
.TP
.BI \-o " file"
output to
.IR file ,
instead of the default
.BR luac.out .
(You can use
.B "'\-'"
for standard output,
but not on platforms that open standard output in text mode.)
The output file may be a source file because
all files are loaded before the output file is written.
Be careful not to overwrite precious files.
.TP
.B \-p
load files but do not generate any output file.
Used mainly for syntax checking and for testing precompiled chunks:
corrupted files will probably generate errors when loaded.
Lua always performs a thorough integrity test on precompiled chunks.
Bytecode that passes this test is completely safe,
in the sense that it will not break the interpreter.
However,
there is no guarantee that such code does anything sensible.
(None can be given, because the halting problem is unsolvable.)
If no files are given, then
.B luac
loads
.B luac.out
and tests its contents.
No messages are displayed if the file passes the integrity test.
.TP
.B \-s
strip debug information before writing the output file.
This saves some space in very large chunks,
but if errors occur when running a stripped chunk,
then the error messages may not contain the full information they usually do.
For instance,
line numbers and names of local variables are lost.
.TP
.B \-v
show version information.
.SH FILES
.TP 15
.B luac.out
default output file
.SH "SEE ALSO"
.BR lua (1)
.br
http://www.lua.org/
.SH DIAGNOSTICS
Error messages should be self explanatory.
.SH AUTHORS
L. H. de Figueiredo,
R. Ierusalimschy and
W. Celes
.\" EOF

3
Utils/luadocumentor.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
"./luarocks/lua5.1" -e "package.path=\"./luarocks/systree/share/lua/5.1/?.lua;./luarocks/systree/share/lua/5.1/?/init.lua;./luarocks/systree/share/lua/5.1/?.lua;./luarocks/systree/share/lua/5.1/?/init.lua;./luarocks/lua/?.lua;\"..package.path; package.cpath=\"./luarocks/lib/lua/5.1/?.dll;./luarocks/systree/lib/lua/5.1/?.dll;\"..package.cpath" -e "local k,l,_=pcall(require,\"luarocks.loader\") _=k and l.add_context(\"luadocumentor\",\"0.1.5-1\")" "./luarocks/systree/lib/luarocks/rocks/luadocumentor/0.1.5-1/bin/luadocumentor" -f doc -d "../docs/Documentation" -s "../docs/Stylesheet/stylesheet.css" "../Moose Development/Moose" %*
exit /b %ERRORLEVEL%

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInheritable></noInheritable>
<assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
<file name="msvcr80.dll" hash="10f4cb2831f1e9288a73387a8734a8b604e5beaa" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>n9On8FItNsK/DmT8UQxu6jYDtWQ=</dsig:DigestValue></asmv2:hash></file>
<file name="msvcp80.dll" hash="b2082dfd3009365c5b287448dcb3b4e2158a6d26" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>0KJ/VTwP4OUHx98HlIW2AdW1kuY=</dsig:DigestValue></asmv2:hash></file>
<file name="msvcm80.dll" hash="542490d0fcf8615c46d0ca487033ccaeb3941f0b" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>YJuB+9Os2oxW4mY+2oC/r8lICZE=</dsig:DigestValue></asmv2:hash></file>
</assembly>

BIN
Utils/luarocks/bin2c5.1.exe Normal file

Binary file not shown.

View File

@ -0,0 +1,14 @@
rocks_trees = {
home..[[/luarocks]],
{ name = [[user]],
root = home..[[/luarocks]],
},
{ name = [[system]],
root = [[C:/Users/Hugues/Documents/GitHub/MOOSE/Utils/luarocks\systree]],
},
}
variables = {
MSVCRT = 'MSVCR80',
LUALIB = 'lua5.1.lib'
}
verbose = false -- set to 'true' to enable verbose output

View File

@ -1,5 +1,5 @@
/*
** $Id: lua.h,v 1.218.1.7 2012/01/13 20:36:20 roberto Exp $
** $Id: lua.h,v 1.218.1.5 2008/08/06 13:30:12 roberto Exp $
** Lua - An Extensible Extension Language
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
** See Copyright Notice at the end of this file
@ -17,9 +17,9 @@
#define LUA_VERSION "Lua 5.1"
#define LUA_RELEASE "Lua 5.1.5"
#define LUA_RELEASE "Lua 5.1.4"
#define LUA_VERSION_NUM 501
#define LUA_COPYRIGHT "Copyright (C) 1994-2012 Lua.org, PUC-Rio"
#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes"
@ -362,7 +362,7 @@ struct lua_Debug {
/******************************************************************************
* Copyright (C) 1994-2012 Lua.org, PUC-Rio. All rights reserved.
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the

View File

@ -91,7 +91,7 @@
".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua"
#define LUA_CPATH_DEFAULT \
".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll"
".\\?.dll;" ".\\?51.dll;" LUA_CDIR"?.dll;" LUA_CDIR"?51.dll;" LUA_CDIR"clibs\\?.dll;" LUA_CDIR"clibs\\?51.dll;" LUA_CDIR"loadall.dll;" LUA_CDIR"clibs\\loadall.dll"
#else
#define LUA_ROOT "/usr/local/"
@ -101,7 +101,7 @@
"./?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \
LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua"
#define LUA_CPATH_DEFAULT \
"./?.so;" LUA_CDIR"?.so;" LUA_CDIR"loadall.so"
"./?.so;" "./lib?51.so;" LUA_CDIR"?.so;" LUA_CDIR"lib?51.so;" LUA_CDIR"loadall.so"
#endif

BIN
Utils/luarocks/lua.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,122 @@
--- Module implementing the luarocks-admin "add" command.
-- Adds a rock or rockspec to a rocks server.
local add = {}
package.loaded["luarocks.add"] = add
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local index = require("luarocks.index")
local fs = require("luarocks.fs")
local cache = require("luarocks.cache")
util.add_run_function(add)
add.help_summary = "Add a rock or rockspec to a rocks server."
add.help_arguments = "[--server=<server>] [--no-refresh] {<rockspec>|<rock>...}"
add.help = [[
Arguments are local files, which may be rockspecs or rocks.
The flag --server indicates which server to use.
If not given, the default server set in the upload_server variable
from the configuration file is used instead.
The flag --no-refresh indicates the local cache should not be refreshed
prior to generation of the updated manifest.
]]
local function add_files_to_server(refresh, rockfiles, server, upload_server)
assert(type(refresh) == "boolean" or not refresh)
assert(type(rockfiles) == "table")
assert(type(server) == "string")
assert(type(upload_server) == "table" or not upload_server)
local download_url, login_url = cache.get_server_urls(server, upload_server)
local at = fs.current_dir()
local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url
local local_cache, protocol, server_path, user, password = refresh_fn(server, download_url, cfg.upload_user, cfg.upload_password)
if not local_cache then
return nil, protocol
end
if protocol == "file" then
return nil, "Server "..server.." is not recognized, check your configuration."
end
if not login_url then
login_url = protocol.."://"..server_path
end
local ok, err = fs.change_dir(at)
if not ok then return nil, err end
local files = {}
for _, rockfile in ipairs(rockfiles) do
if fs.exists(rockfile) then
util.printout("Copying file "..rockfile.." to "..local_cache.."...")
local absolute = fs.absolute_name(rockfile)
fs.copy(absolute, local_cache, cfg.perm_read)
table.insert(files, dir.base_name(absolute))
else
util.printerr("File "..rockfile.." not found")
end
end
if #files == 0 then
return nil, "No files found"
end
local ok, err = fs.change_dir(local_cache)
if not ok then return nil, err end
util.printout("Updating manifest...")
manif.make_manifest(local_cache, "one", true)
manif.zip_manifests()
util.printout("Updating index.html...")
index.make_index(local_cache)
local login_info = ""
if user then login_info = " -u "..user end
if password then login_info = login_info..":"..password end
if not login_url:match("/$") then
login_url = login_url .. "/"
end
table.insert(files, "index.html")
table.insert(files, "manifest")
for ver in util.lua_versions() do
table.insert(files, "manifest-"..ver)
table.insert(files, "manifest-"..ver..".zip")
end
-- TODO abstract away explicit 'curl' call
local cmd
if protocol == "rsync" then
local srv, path = server_path:match("([^/]+)(/.+)")
cmd = cfg.variables.RSYNC.." "..cfg.variables.RSYNCFLAGS.." -e ssh "..local_cache.."/ "..user.."@"..srv..":"..path.."/"
elseif upload_server and upload_server.sftp then
local part1, part2 = upload_server.sftp:match("^([^/]*)/(.*)$")
cmd = cfg.variables.SCP.." "..table.concat(files, " ").." "..user.."@"..part1..":/"..part2
else
cmd = cfg.variables.CURL.." "..login_info.." -T '{"..table.concat(files, ",").."}' "..login_url
end
util.printout(cmd)
fs.execute(cmd)
return true
end
function add.command(flags, ...)
local files = {...}
if #files < 1 then
return nil, "Argument missing. "..util.see_help("add", "luarocks-admin")
end
local server, server_table = cache.get_upload_server(flags["server"])
if not server then return nil, server_table end
return add_files_to_server(not flags["no-refresh"], files, server, server_table)
end
return add

View File

@ -0,0 +1,92 @@
--- Module implementing the luarocks-admin "remove" command.
-- Removes a rock or rockspec from a rocks server.
local admin_remove = {}
package.loaded["luarocks.admin_remove"] = admin_remove
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local index = require("luarocks.index")
local fs = require("luarocks.fs")
local cache = require("luarocks.cache")
util.add_run_function(admin_remove)
admin_remove.help_summary = "Remove a rock or rockspec from a rocks server."
admin_remove.help_arguments = "[--server=<server>] [--no-refresh] {<rockspec>|<rock>...}"
admin_remove.help = [[
Arguments are local files, which may be rockspecs or rocks.
The flag --server indicates which server to use.
If not given, the default server set in the upload_server variable
from the configuration file is used instead.
The flag --no-refresh indicates the local cache should not be refreshed
prior to generation of the updated manifest.
]]
local function remove_files_from_server(refresh, rockfiles, server, upload_server)
assert(type(refresh) == "boolean" or not refresh)
assert(type(rockfiles) == "table")
assert(type(server) == "string")
assert(type(upload_server) == "table" or not upload_server)
local download_url, login_url = cache.get_server_urls(server, upload_server)
local at = fs.current_dir()
local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url
local local_cache, protocol, server_path, user, password = refresh_fn(server, download_url, cfg.upload_user, cfg.upload_password)
if not local_cache then
return nil, protocol
end
if protocol ~= "rsync" then
return nil, "This command requires 'rsync', check your configuration."
end
local ok, err = fs.change_dir(at)
if not ok then return nil, err end
local nr_files = 0
for _, rockfile in ipairs(rockfiles) do
local basename = dir.base_name(rockfile)
local file = dir.path(local_cache, basename)
util.printout("Removing file "..file.."...")
fs.delete(file)
if not fs.exists(file) then
nr_files = nr_files + 1
else
util.printerr("Failed removing "..file)
end
end
if nr_files == 0 then
return nil, "No files removed."
end
local ok, err = fs.change_dir(local_cache)
if not ok then return nil, err end
util.printout("Updating manifest...")
manif.make_manifest(local_cache, "one", true)
util.printout("Updating index.html...")
index.make_index(local_cache)
local srv, path = server_path:match("([^/]+)(/.+)")
local cmd = cfg.variables.RSYNC.." "..cfg.variables.RSYNCFLAGS.." --delete -e ssh "..local_cache.."/ "..user.."@"..srv..":"..path.."/"
util.printout(cmd)
fs.execute(cmd)
return true
end
function admin_remove.command(flags, ...)
local files = {...}
if #files < 1 then
return nil, "Argument missing. "..util.see_help("remove", "luarocks-admin")
end
local server, server_table = cache.get_upload_server(flags["server"])
if not server then return nil, server_table end
return remove_files_from_server(not flags["no-refresh"], files, server, server_table)
end
return admin_remove

View File

@ -0,0 +1,415 @@
--- Module implementing the LuaRocks "build" command.
-- Builds a rock, compiling its C parts if any.
local build = {}
package.loaded["luarocks.build"] = build
local pack = require("luarocks.pack")
local path = require("luarocks.path")
local util = require("luarocks.util")
local repos = require("luarocks.repos")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local deps = require("luarocks.deps")
local manif = require("luarocks.manif")
local remove = require("luarocks.remove")
local cfg = require("luarocks.cfg")
util.add_run_function(build)
build.help_summary = "Build/compile a rock."
build.help_arguments = "[--pack-binary-rock] [--keep] {<rockspec>|<rock>|<name> [<version>]}"
build.help = [[
Build and install a rock, compiling its C parts if any.
Argument may be a rockspec file, a source rock file
or the name of a rock to be fetched from a repository.
--pack-binary-rock Do not install rock. Instead, produce a .rock file
with the contents of compilation in the current
directory.
--keep Do not remove previously installed versions of the
rock after building a new one. This behavior can
be made permanent by setting keep_other_versions=true
in the configuration file.
--branch=<name> Override the `source.branch` field in the loaded
rockspec. Allows to specify a different branch to
fetch. Particularly for SCM rocks.
--only-deps Installs only the dependencies of the rock.
]]..util.deps_mode_help()
--- Install files to a given location.
-- Takes a table where the array part is a list of filenames to be copied.
-- In the hash part, other keys, if is_module_path is set, are identifiers
-- in Lua module format, to indicate which subdirectory the file should be
-- copied to. For example, install_files({["foo.bar"] = "src/bar.lua"}, "boo")
-- will copy src/bar.lua to boo/foo.
-- @param files table or nil: A table containing a list of files to copy in
-- the format described above. If nil is passed, this function is a no-op.
-- Directories should be delimited by forward slashes as in internet URLs.
-- @param location string: The base directory files should be copied to.
-- @param is_module_path boolean: True if string keys in files should be
-- interpreted as dotted module paths.
-- @param perms string: Permissions of the newly created files installed.
-- Directories are always created with the default permissions.
-- @return boolean or (nil, string): True if succeeded or
-- nil and an error message.
local function install_files(files, location, is_module_path, perms)
assert(type(files) == "table" or not files)
assert(type(location) == "string")
if files then
for k, file in pairs(files) do
local dest = location
local filename = dir.base_name(file)
if type(k) == "string" then
local modname = k
if is_module_path then
dest = dir.path(location, path.module_to_path(modname))
local ok, err = fs.make_dir(dest)
if not ok then return nil, err end
if filename:match("%.lua$") then
local basename = modname:match("([^.]+)$")
filename = basename..".lua"
end
else
dest = dir.path(location, dir.dir_name(modname))
local ok, err = fs.make_dir(dest)
if not ok then return nil, err end
filename = dir.base_name(modname)
end
else
local ok, err = fs.make_dir(dest)
if not ok then return nil, err end
end
local ok = fs.copy(dir.path(file), dir.path(dest, filename), perms)
if not ok then
return nil, "Failed copying "..file
end
end
end
return true
end
--- Write to the current directory the contents of a table,
-- where each key is a file name and its value is the file content.
-- @param files table: The table of files to be written.
local function extract_from_rockspec(files)
for name, content in pairs(files) do
local fd = io.open(dir.path(fs.current_dir(), name), "w+")
fd:write(content)
fd:close()
end
end
--- Applies patches inlined in the build.patches section
-- and extracts files inlined in the build.extra_files section
-- of a rockspec.
-- @param rockspec table: A rockspec table.
-- @return boolean or (nil, string): True if succeeded or
-- nil and an error message.
function build.apply_patches(rockspec)
assert(type(rockspec) == "table")
local build_spec = rockspec.build
if build_spec.extra_files then
extract_from_rockspec(build_spec.extra_files)
end
if build_spec.patches then
extract_from_rockspec(build_spec.patches)
for patch, patchdata in util.sortedpairs(build_spec.patches) do
util.printout("Applying patch "..patch.."...")
local ok, err = fs.apply_patch(tostring(patch), patchdata)
if not ok then
return nil, "Failed applying patch "..patch
end
end
end
return true
end
local function install_default_docs(name, version)
local patterns = { "readme", "license", "copying", ".*%.md" }
local dest = dir.path(path.install_dir(name, version), "doc")
local has_dir = false
for file in fs.dir() do
for _, pattern in ipairs(patterns) do
if file:lower():match("^"..pattern) then
if not has_dir then
fs.make_dir(dest)
has_dir = true
end
fs.copy(file, dest, cfg.perm_read)
break
end
end
end
end
--- Build and install a rock given a rockspec.
-- @param rockspec_file string: local or remote filename of a rockspec.
-- @param need_to_fetch boolean: true if sources need to be fetched,
-- false if the rockspec was obtained from inside a source rock.
-- @param minimal_mode boolean: true if there's no need to fetch,
-- unpack or change dir (this is used by "luarocks make"). Implies
-- need_to_fetch = false.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for no trees.
-- @param build_only_deps boolean: true to build the listed dependencies only.
-- @return (string, string) or (nil, string, [string]): Name and version of
-- installed rock if succeeded or nil and an error message followed by an error code.
function build.build_rockspec(rockspec_file, need_to_fetch, minimal_mode, deps_mode, build_only_deps)
assert(type(rockspec_file) == "string")
assert(type(need_to_fetch) == "boolean")
local rockspec, err, errcode = fetch.load_rockspec(rockspec_file)
if err then
return nil, err, errcode
elseif not rockspec.build then
return nil, "Rockspec error: build table not specified"
elseif not rockspec.build.type then
return nil, "Rockspec error: build type not specified"
end
local ok
if not build_only_deps then
ok, err, errcode = deps.check_external_deps(rockspec, "build")
if err then
return nil, err, errcode
end
end
if deps_mode == "none" then
util.printerr("Warning: skipping dependency checks.")
else
local ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
if err then
return nil, err, errcode
end
end
local name, version = rockspec.name, rockspec.version
if build_only_deps then
util.printout("Stopping after installing dependencies for " ..name.." "..version)
util.printout()
return name, version
end
if repos.is_installed(name, version) then
repos.delete_version(name, version, deps_mode)
end
if not minimal_mode then
local source_dir
if need_to_fetch then
ok, source_dir, errcode = fetch.fetch_sources(rockspec, true)
if not ok then
return nil, source_dir, errcode
end
local ok, err = fs.change_dir(source_dir)
if not ok then return nil, err end
elseif rockspec.source.file then
local ok, err = fs.unpack_archive(rockspec.source.file)
if not ok then
return nil, err
end
end
fs.change_dir(rockspec.source.dir)
end
local dirs = {
lua = { name = path.lua_dir(name, version), is_module_path = true, perms = cfg.perm_read },
lib = { name = path.lib_dir(name, version), is_module_path = true, perms = cfg.perm_exec },
conf = { name = path.conf_dir(name, version), is_module_path = false, perms = cfg.perm_read },
bin = { name = path.bin_dir(name, version), is_module_path = false, perms = cfg.perm_exec },
}
for _, d in pairs(dirs) do
local ok, err = fs.make_dir(d.name)
if not ok then return nil, err end
end
local rollback = util.schedule_function(function()
fs.delete(path.install_dir(name, version))
fs.remove_dir_if_empty(path.versions_dir(name))
end)
local build_spec = rockspec.build
if not minimal_mode then
ok, err = build.apply_patches(rockspec)
if err then
return nil, err
end
end
if build_spec.type ~= "none" then
-- Temporary compatibility
if build_spec.type == "module" then
util.printout("Do not use 'module' as a build type. Use 'builtin' instead.")
build_spec.type = "builtin"
end
if cfg.accepted_build_types and util.array_contains(cfg.accepted_build_types, build_spec.type) then
return nil, "This rockspec uses the '"..build_spec.type.."' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration."
end
local build_type
ok, build_type = pcall(require, "luarocks.build." .. build_spec.type)
if not ok or not type(build_type) == "table" then
return nil, "Failed initializing build back-end for build type '"..build_spec.type.."': "..build_type
end
ok, err = build_type.run(rockspec)
if not ok then
return nil, "Build error: " .. err
end
end
if build_spec.install then
for id, install_dir in pairs(dirs) do
ok, err = install_files(build_spec.install[id], install_dir.name, install_dir.is_module_path, install_dir.perms)
if not ok then
return nil, err
end
end
end
local copy_directories = build_spec.copy_directories
local copying_default = false
if not copy_directories then
copy_directories = {"doc"}
copying_default = true
end
local any_docs = false
for _, copy_dir in pairs(copy_directories) do
if fs.is_dir(copy_dir) then
local dest = dir.path(path.install_dir(name, version), copy_dir)
fs.make_dir(dest)
fs.copy_contents(copy_dir, dest)
any_docs = true
else
if not copying_default then
return nil, "Directory '"..copy_dir.."' not found"
end
end
end
if not any_docs then
install_default_docs(name, version)
end
for _, d in pairs(dirs) do
fs.remove_dir_if_empty(d.name)
end
fs.pop_dir()
fs.copy(rockspec.local_filename, path.rockspec_file(name, version), cfg.perm_read)
if need_to_fetch then
fs.pop_dir()
end
ok, err = manif.make_rock_manifest(name, version)
if err then return nil, err end
ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode)
if err then return nil, err end
util.remove_scheduled_function(rollback)
rollback = util.schedule_function(function()
repos.delete_version(name, version, deps_mode)
end)
ok, err = repos.run_hook(rockspec, "post_install")
if err then return nil, err end
util.announce_install(rockspec)
util.remove_scheduled_function(rollback)
return name, version
end
--- Build and install a rock.
-- @param rock_file string: local or remote filename of a rock.
-- @param need_to_fetch boolean: true if sources need to be fetched,
-- false if the rockspec was obtained from inside a source rock.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @param build_only_deps boolean: true to build the listed dependencies only.
-- @return boolean or (nil, string, [string]): True if build was successful,
-- or false and an error message and an optional error code.
function build.build_rock(rock_file, need_to_fetch, deps_mode, build_only_deps)
assert(type(rock_file) == "string")
assert(type(need_to_fetch) == "boolean")
local ok, err, errcode
local unpack_dir
unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_file)
if not unpack_dir then
return nil, err, errcode
end
local rockspec_file = path.rockspec_name_from_rock(rock_file)
ok, err = fs.change_dir(unpack_dir)
if not ok then return nil, err end
ok, err, errcode = build.build_rockspec(rockspec_file, need_to_fetch, false, deps_mode, build_only_deps)
fs.pop_dir()
return ok, err, errcode
end
local function do_build(name, version, deps_mode, build_only_deps)
if name:match("%.rockspec$") then
return build.build_rockspec(name, true, false, deps_mode, build_only_deps)
elseif name:match("%.src%.rock$") then
return build.build_rock(name, false, deps_mode, build_only_deps)
elseif name:match("%.all%.rock$") then
local install = require("luarocks.install")
local install_fun = build_only_deps and install.install_binary_rock_deps or install.install_binary_rock
return install_fun(name, deps_mode)
elseif name:match("%.rock$") then
return build.build_rock(name, true, deps_mode, build_only_deps)
elseif not name:match(dir.separator) then
local search = require("luarocks.search")
return search.act_on_src_or_rockspec(do_build, name:lower(), version, nil, deps_mode, build_only_deps)
end
return nil, "Don't know what to do with "..name
end
--- Driver function for "build" command.
-- @param name string: A local or remote rockspec or rock file.
-- If a package name is given, forwards the request to "search" and,
-- if returned a result, installs the matching rock.
-- @param version string: When passing a package name, a version number may
-- also be given.
-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
-- error message otherwise. exitcode is optionally returned.
function build.command(flags, name, version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("build")
end
assert(type(version) == "string" or not version)
if flags["pack-binary-rock"] then
return pack.pack_binary_rock(name, version, do_build, name, version, deps.get_deps_mode(flags))
else
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
ok, err = do_build(name, version, deps.get_deps_mode(flags), flags["only-deps"])
if not ok then return nil, err end
name, version = ok, err
if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then
local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"])
if not ok then util.printerr(err) end
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return name, version
end
end
return build

View File

@ -0,0 +1,78 @@
--- Module handling the LuaRocks local cache.
-- Adds a rock or rockspec to a rocks server.
local cache = {}
package.loaded["luarocks.cache"] = cache
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
function cache.get_upload_server(server)
if not server then server = cfg.upload_server end
if not server then
return nil, "No server specified and no default configured with upload_server."
end
return server, cfg.upload_servers and cfg.upload_servers[server]
end
function cache.get_server_urls(server, upload_server)
local download_url = server
local login_url = nil
if upload_server then
if upload_server.rsync then download_url = "rsync://"..upload_server.rsync
elseif upload_server.http then download_url = "http://"..upload_server.http
elseif upload_server.ftp then download_url = "ftp://"..upload_server.ftp
end
if upload_server.ftp then login_url = "ftp://"..upload_server.ftp
elseif upload_server.sftp then login_url = "sftp://"..upload_server.sftp
end
end
return download_url, login_url
end
function cache.split_server_url(server, url, user, password)
local protocol, server_path = dir.split_url(url)
if server_path:match("@") then
local credentials
credentials, server_path = server_path:match("([^@]*)@(.*)")
if credentials:match(":") then
user, password = credentials:match("([^:]*):(.*)")
else
user = credentials
end
end
local local_cache = cfg.local_cache .. "/" .. server
return local_cache, protocol, server_path, user, password
end
function cache.refresh_local_cache(server, url, user, password)
local local_cache, protocol, server_path, user, password = cache.split_server_url(server, url, user, password)
local ok, err = fs.make_dir(local_cache)
if not ok then
return nil, "Failed creating local cache dir: "..err
end
fs.change_dir(local_cache)
if not ok then return nil, err end
util.printout("Refreshing cache "..local_cache.."...")
-- TODO abstract away explicit 'wget' call
local ok = false
if protocol == "rsync" then
local srv, path = server_path:match("([^/]+)(/.+)")
ok = fs.execute(cfg.variables.RSYNC.." "..cfg.variables.RSYNCFLAGS.." -e ssh "..user.."@"..srv..":"..path.."/ "..local_cache.."/")
else
local login_info = ""
if user then login_info = " --user="..user end
if password then login_info = login_info .. " --password="..password end
ok = fs.execute(cfg.variables.WGET.." --no-cache -q -m -np -nd "..protocol.."://"..server_path..login_info)
end
if not ok then
return nil, "Failed downloading cache."
end
return local_cache, protocol, server_path, user, password
end
return cache

View File

@ -0,0 +1,760 @@
--- Configuration for LuaRocks.
-- Tries to load the user's configuration file and
-- defines defaults for unset values. See the
-- <a href="http://luarocks.org/en/Config_file_format">config
-- file format documentation</a> for details.
--
-- End-users shouldn't edit this file. They can override any defaults
-- set in this file using their system-wide or user-specific configuration
-- files. Run `luarocks` with no arguments to see the locations of
-- these files in your platform.
local rawset, next, table, pairs, require, io, os, setmetatable, pcall, ipairs, package, tonumber, type, assert, _VERSION =
rawset, next, table, pairs, require, io, os, setmetatable, pcall, ipairs, package, tonumber, type, assert, _VERSION
--module("luarocks.cfg")
local cfg = {}
package.loaded["luarocks.cfg"] = cfg
local util = require("luarocks.util")
cfg.lua_version = _VERSION:match(" (5%.[123])$") or "5.1"
local version_suffix = cfg.lua_version:gsub("%.", "_")
-- Load site-local global configurations
local ok, site_config = pcall(require, "luarocks.site_config_"..version_suffix)
if not ok then
ok, site_config = pcall(require, "luarocks.site_config")
end
if not ok then
io.stderr:write("Site-local luarocks/site_config.lua file not found. Incomplete installation?\n")
site_config = {}
end
cfg.program_version = "2.4.2"
cfg.program_series = "2.4"
cfg.major_version = (cfg.program_version:match("([^.]%.[^.])")) or cfg.program_series
cfg.variables = {}
cfg.rocks_trees = {}
cfg.platforms = {}
local persist = require("luarocks.persist")
cfg.errorcodes = setmetatable({
OK = 0,
UNSPECIFIED = 1,
PERMISSIONDENIED = 2,
CONFIGFILE = 3,
CRASH = 99
},{
__index = function(t, key)
local val = rawget(t, key)
if not val then
error("'"..tostring(key).."' is not a valid errorcode", 2)
end
return val
end
})
local popen_ok, popen_result = pcall(io.popen, "")
if popen_ok then
if popen_result then
popen_result:close()
end
else
io.stderr:write("Your version of Lua does not support io.popen,\n")
io.stderr:write("which is required by LuaRocks. Please check your Lua installation.\n")
os.exit(cfg.errorcodes.UNSPECIFIED)
end
-- System detection:
-- A proper installation of LuaRocks will hardcode the system
-- and proc values with site_config.LUAROCKS_UNAME_S and site_config.LUAROCKS_UNAME_M,
-- so that this detection does not run every time. When it is
-- performed, we use the Unix way to identify the system,
-- even on Windows (assuming UnxUtils or Cygwin).
local system = site_config.LUAROCKS_UNAME_S or io.popen("uname -s"):read("*l")
local proc = site_config.LUAROCKS_UNAME_M or io.popen("uname -m"):read("*l")
if proc:match("i[%d]86") then
cfg.target_cpu = "x86"
elseif proc:match("amd64") or proc:match("x86_64") then
cfg.target_cpu = "x86_64"
elseif proc:match("Power Macintosh") then
cfg.target_cpu = "powerpc"
else
cfg.target_cpu = proc
end
if system == "FreeBSD" then
cfg.platforms.unix = true
cfg.platforms.freebsd = true
cfg.platforms.bsd = true
elseif system == "OpenBSD" then
cfg.platforms.unix = true
cfg.platforms.openbsd = true
cfg.platforms.bsd = true
elseif system == "NetBSD" then
cfg.platforms.unix = true
cfg.platforms.netbsd = true
cfg.platforms.bsd = true
elseif system == "Darwin" then
cfg.platforms.unix = true
cfg.platforms.macosx = true
cfg.platforms.bsd = true
elseif system == "Linux" then
cfg.platforms.unix = true
cfg.platforms.linux = true
elseif system == "SunOS" then
cfg.platforms.unix = true
cfg.platforms.solaris = true
elseif system and system:match("^CYGWIN") then
cfg.platforms.unix = true
cfg.platforms.cygwin = true
elseif system and system:match("^MSYS") then
cfg.platforms.unix = true
cfg.platforms.msys = true
cfg.platforms.cygwin = true
elseif system and system:match("^Windows") then
cfg.platforms.windows = true
cfg.platforms.win32 = true
elseif system and system:match("^MINGW") then
cfg.platforms.windows = true
cfg.platforms.mingw32 = true
cfg.platforms.win32 = true
elseif system == "Haiku" then
cfg.platforms.unix = true
cfg.platforms.haiku = true
else
cfg.platforms.unix = true
-- Fall back to Unix in unknown systems.
end
-- Set order for platform overrides.
-- More general platform identifiers should be listed first,
-- more specific ones later.
local platform_order = {
-- Unixes
"unix",
"bsd",
"solaris",
"netbsd",
"openbsd",
"freebsd",
"linux",
"macosx",
"cygwin",
"msys",
"haiku",
-- Windows
"win32",
"mingw32",
"windows",
}
-- Path configuration:
local sys_config_file, home_config_file
local sys_config_file_default, home_config_file_default
local sys_config_dir, home_config_dir
local sys_config_ok, home_config_ok = false, false
local extra_luarocks_module_dir
sys_config_dir = site_config.LUAROCKS_SYSCONFDIR or site_config.LUAROCKS_PREFIX
if cfg.platforms.windows then
cfg.home = os.getenv("APPDATA") or "c:"
sys_config_dir = sys_config_dir or "c:/luarocks"
home_config_dir = cfg.home.."/luarocks"
cfg.home_tree = cfg.home.."/luarocks/"
else
cfg.home = os.getenv("HOME") or ""
sys_config_dir = sys_config_dir or "/etc/luarocks"
home_config_dir = cfg.home.."/.luarocks"
cfg.home_tree = (os.getenv("USER") ~= "root") and cfg.home.."/.luarocks/"
end
-- Create global environment for the config files;
local env_for_config_file = function()
local e
e = {
home = cfg.home,
lua_version = cfg.lua_version,
platforms = util.make_shallow_copy(cfg.platforms),
processor = cfg.target_cpu, -- remains for compat reasons
target_cpu = cfg.target_cpu, -- replaces `processor`
os_getenv = os.getenv,
dump_env = function()
-- debug function, calling it from a config file will show all
-- available globals to that config file
print(util.show_table(e, "global environment"))
end,
}
return e
end
-- Merge values from config files read into the `cfg` table
local merge_overrides = function(overrides)
-- remove some stuff we do not want to integrate
overrides.os_getenv = nil
overrides.dump_env = nil
-- remove tables to be copied verbatim instead of deeply merged
if overrides.rocks_trees then cfg.rocks_trees = nil end
if overrides.rocks_servers then cfg.rocks_servers = nil end
-- perform actual merge
util.deep_merge(cfg, overrides)
end
-- load config file from a list until first succesful one. Info is
-- added to `cfg` module table, returns filepath of succesfully loaded
-- file or nil if it failed
local load_config_file = function(list)
for _, filepath in ipairs(list) do
local result, err, errcode = persist.load_into_table(filepath, env_for_config_file())
if (not result) and errcode ~= "open" then
-- errcode is either "load" or "run"; bad config file, so error out
io.stderr:write(err.."\n")
os.exit(cfg.errorcodes.CONFIGFILE)
end
if result then
-- succes in loading and running, merge contents and exit
merge_overrides(result)
return filepath
end
end
return nil -- nothing was loaded
end
-- Load system configuration file
do
sys_config_file_default = sys_config_dir.."/config-"..cfg.lua_version..".lua"
sys_config_file = load_config_file({
site_config.LUAROCKS_SYSCONFIG or sys_config_file_default,
sys_config_dir.."/config.lua",
})
sys_config_ok = (sys_config_file ~= nil)
end
-- Load user configuration file (if allowed)
if not site_config.LUAROCKS_FORCE_CONFIG then
home_config_file_default = home_config_dir.."/config-"..cfg.lua_version..".lua"
local config_env_var = "LUAROCKS_CONFIG_" .. version_suffix
local config_env_value = os.getenv(config_env_var)
if not config_env_value then
config_env_var = "LUAROCKS_CONFIG"
config_env_value = os.getenv(config_env_var)
end
-- first try environment provided file, so we can explicitly warn when it is missing
if config_env_value then
local list = { config_env_value }
home_config_file = load_config_file(list)
home_config_ok = (home_config_file ~= nil)
if not home_config_ok then
io.stderr:write("Warning: could not load configuration file `"..config_env_value.."` given in environment variable "..config_env_var.."\n")
end
end
-- try the alternative defaults if there was no environment specified file or it didn't work
if not home_config_ok then
local list = {
home_config_file_default,
home_config_dir.."/config.lua",
}
home_config_file = load_config_file(list)
home_config_ok = (home_config_file ~= nil)
end
end
if not next(cfg.rocks_trees) then
if cfg.home_tree then
table.insert(cfg.rocks_trees, { name = "user", root = cfg.home_tree } )
end
if site_config.LUAROCKS_ROCKS_TREE then
table.insert(cfg.rocks_trees, { name = "system", root = site_config.LUAROCKS_ROCKS_TREE } )
end
end
-- update platforms list; keyed -> array
do
-- if explicitly given by user,
if cfg.platforms[1] then
local is_windows = cfg.platforms.windows
-- Clear auto-detected values
for k, _ in pairs(cfg.platforms) do
if type(k) == "string" then
cfg.platforms[k] = nil
end
end
-- and set the ones given by the user.
for _, plat in ipairs(cfg.platforms) do
cfg.platforms[plat] = true
end
-- If no major platform family was set by the user,
if not (cfg.platforms.unix or cfg.platforms.windows) then
-- set some fallback defaults in case the user provides an incomplete configuration.
-- LuaRocks expects a set of defaults to be available.
-- This is used for setting defaults here only; the platform overrides
-- will use only the user's list.
if is_windows then
cfg.platforms.windows = true
table.insert(cfg.platforms, "windows")
else
cfg.platforms.unix = true
table.insert(cfg.platforms, "unix")
end
end
else
-- Sort detected platform defaults
local order = {}
for i, v in ipairs(platform_order) do
order[v] = i
end
local entries = {}
for k, v in pairs(cfg.platforms) do
if type(k) == "string" and v == true then
table.insert(entries, k)
end
end
table.sort(entries, function(a, b) return order[a] < order[b] end)
util.deep_merge(cfg.platforms, entries)
end
end
-- Configure defaults:
local defaults = {
local_by_default = false,
accept_unknown_fields = false,
fs_use_modules = true,
hooks_enabled = true,
deps_mode = "one",
check_certificates = false,
perm_read = "0644",
perm_exec = "0755",
lua_modules_path = "/share/lua/"..cfg.lua_version,
lib_modules_path = "/lib/lua/"..cfg.lua_version,
rocks_subdir = site_config.LUAROCKS_ROCKS_SUBDIR or "/lib/luarocks/rocks",
arch = "unknown",
lib_extension = "unknown",
obj_extension = "unknown",
link_lua_explicitly = false,
rocks_servers = {
{
"https://luarocks.org",
"https://raw.githubusercontent.com/rocks-moonscript-org/moonrocks-mirror/master/",
"http://luafr.org/moonrocks/",
"http://luarocks.logiceditor.com/rocks",
}
},
disabled_servers = {},
upload = {
server = "https://luarocks.org",
tool_version = "1.0.0",
api_version = "1",
},
lua_extension = "lua",
lua_interpreter = site_config.LUA_INTERPRETER or "lua",
downloader = site_config.LUAROCKS_DOWNLOADER or "wget",
md5checker = site_config.LUAROCKS_MD5CHECKER or "md5sum",
connection_timeout = 30, -- 0 = no timeout
variables = {
MAKE = "make",
CC = "cc",
LD = "ld",
CVS = "cvs",
GIT = "git",
SSCM = "sscm",
SVN = "svn",
HG = "hg",
RSYNC = "rsync",
WGET = "wget",
SCP = "scp",
CURL = "curl",
PWD = "pwd",
MKDIR = "mkdir",
RMDIR = "rmdir",
CP = "cp",
LS = "ls",
RM = "rm",
FIND = "find",
TEST = "test",
CHMOD = "chmod",
MKTEMP = "mktemp",
ZIP = "zip",
UNZIP = "unzip -n",
GUNZIP = "gunzip",
BUNZIP2 = "bunzip2",
TAR = "tar",
MD5SUM = "md5sum",
OPENSSL = "openssl",
MD5 = "md5",
STAT = "stat",
TOUCH = "touch",
CMAKE = "cmake",
SEVENZ = "7z",
RSYNCFLAGS = "--exclude=.git -Oavz",
STATFLAG = "-c '%a'",
CURLNOCERTFLAG = "",
WGETNOCERTFLAG = "",
},
external_deps_subdirs = site_config.LUAROCKS_EXTERNAL_DEPS_SUBDIRS or {
bin = "bin",
lib = "lib",
include = "include"
},
runtime_external_deps_subdirs = site_config.LUAROCKS_RUNTIME_EXTERNAL_DEPS_SUBDIRS or {
bin = "bin",
lib = "lib",
include = "include"
},
rocks_provided = {}
}
if cfg.platforms.windows then
local full_prefix = (site_config.LUAROCKS_PREFIX or (os.getenv("PROGRAMFILES")..[[\LuaRocks]]))
extra_luarocks_module_dir = full_prefix.."/lua/?.lua"
home_config_file = home_config_file and home_config_file:gsub("\\","/")
defaults.fs_use_modules = false
defaults.arch = "win32-"..cfg.target_cpu
defaults.lib_extension = "dll"
defaults.external_lib_extension = "dll"
defaults.obj_extension = "obj"
defaults.external_deps_dirs = { "c:/external/" }
defaults.variables.LUA_BINDIR = site_config.LUA_BINDIR and site_config.LUA_BINDIR:gsub("\\", "/") or "c:/lua"..cfg.lua_version.."/bin"
defaults.variables.LUA_INCDIR = site_config.LUA_INCDIR and site_config.LUA_INCDIR:gsub("\\", "/") or "c:/lua"..cfg.lua_version.."/include"
defaults.variables.LUA_LIBDIR = site_config.LUA_LIBDIR and site_config.LUA_LIBDIR:gsub("\\", "/") or "c:/lua"..cfg.lua_version.."/lib"
defaults.makefile = "Makefile.win"
defaults.variables.MAKE = "nmake"
defaults.variables.CC = "cl"
defaults.variables.RC = "rc"
defaults.variables.WRAPPER = full_prefix.."\\rclauncher.c"
defaults.variables.LD = "link"
defaults.variables.MT = "mt"
defaults.variables.LUALIB = "lua"..cfg.lua_version..".lib"
defaults.variables.CFLAGS = "/nologo /MD /O2"
defaults.variables.LIBFLAG = "/nologo /dll"
local bins = { "SEVENZ", "CP", "FIND", "LS", "MD5SUM",
"MKDIR", "MV", "PWD", "RMDIR", "TEST", "UNAME", "WGET" }
for _, var in ipairs(bins) do
if defaults.variables[var] then
defaults.variables[var] = full_prefix.."\\tools\\"..defaults.variables[var]
end
end
defaults.external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "?.lib", "?.dll", "lib?.dll" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "?.dll", "lib?.dll" },
include = { "?.h" }
}
defaults.export_path = "SET PATH=%s"
defaults.export_path_separator = ";"
defaults.export_lua_path = "SET LUA_PATH=%s"
defaults.export_lua_cpath = "SET LUA_CPATH=%s"
defaults.wrapper_suffix = ".bat"
local localappdata = os.getenv("LOCALAPPDATA")
if not localappdata then
-- for Windows versions below Vista
localappdata = os.getenv("USERPROFILE").."/Local Settings/Application Data"
end
defaults.local_cache = localappdata.."/LuaRocks/Cache"
defaults.web_browser = "start"
end
if cfg.platforms.mingw32 then
defaults.obj_extension = "o"
defaults.cmake_generator = "MinGW Makefiles"
defaults.variables.MAKE = "mingw32-make"
defaults.variables.CC = "mingw32-gcc"
defaults.variables.RC = "windres"
defaults.variables.LD = "mingw32-gcc"
defaults.variables.CFLAGS = "-O2"
defaults.variables.LIBFLAG = "-shared"
defaults.makefile = "Makefile"
defaults.external_deps_patterns = {
bin = { "?.exe", "?.bat" },
-- mingw lookup list from http://stackoverflow.com/a/15853231/1793220
-- ...should we keep ?.lib at the end? It's not in the above list.
lib = { "lib?.dll.a", "?.dll.a", "lib?.a", "cyg?.dll", "lib?.dll", "?.dll", "?.lib" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "cyg?.dll", "?.dll", "lib?.dll" },
include = { "?.h" }
}
end
if cfg.platforms.unix then
defaults.lib_extension = "so"
defaults.external_lib_extension = "so"
defaults.obj_extension = "o"
defaults.external_deps_dirs = { "/usr/local", "/usr" }
defaults.variables.LUA_BINDIR = site_config.LUA_BINDIR or "/usr/local/bin"
defaults.variables.LUA_INCDIR = site_config.LUA_INCDIR or "/usr/local/include"
defaults.variables.LUA_LIBDIR = site_config.LUA_LIBDIR or "/usr/local/lib"
defaults.variables.CFLAGS = "-O2"
defaults.cmake_generator = "Unix Makefiles"
defaults.variables.CC = "gcc"
defaults.variables.LD = "gcc"
defaults.gcc_rpath = true
defaults.variables.LIBFLAG = "-shared"
defaults.external_deps_patterns = {
bin = { "?" },
lib = { "lib?.a", "lib?.so", "lib?.so.*" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?" },
lib = { "lib?.so", "lib?.so.*" },
include = { "?.h" }
}
defaults.export_path = "export PATH='%s'"
defaults.export_path_separator = ":"
defaults.export_lua_path = "export LUA_PATH='%s'"
defaults.export_lua_cpath = "export LUA_CPATH='%s'"
defaults.wrapper_suffix = ""
defaults.local_cache = cfg.home.."/.cache/luarocks"
if not defaults.variables.CFLAGS:match("-fPIC") then
defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC"
end
defaults.web_browser = "xdg-open"
end
if cfg.platforms.cygwin then
defaults.lib_extension = "so" -- can be overridden in the config file for mingw builds
defaults.arch = "cygwin-"..cfg.target_cpu
defaults.cmake_generator = "Unix Makefiles"
defaults.variables.CC = "echo -llua | xargs gcc"
defaults.variables.LD = "echo -llua | xargs gcc"
defaults.variables.LIBFLAG = "-shared"
defaults.link_lua_explicitly = true
end
if cfg.platforms.msys then
-- msys is basically cygwin made out of mingw, meaning the subsytem is unixish
-- enough, yet we can freely mix with native win32
defaults.external_deps_patterns = {
bin = { "?.exe", "?.bat", "?" },
lib = { "lib?.so", "lib?.so.*", "lib?.dll.a", "?.dll.a",
"lib?.a", "lib?.dll", "?.dll", "?.lib" },
include = { "?.h" }
}
defaults.runtime_external_deps_patterns = {
bin = { "?.exe", "?.bat" },
lib = { "lib?.so", "?.dll", "lib?.dll" },
include = { "?.h" }
}
end
if cfg.platforms.bsd then
defaults.variables.MAKE = "gmake"
defaults.variables.STATFLAG = "-f '%OLp'"
end
if cfg.platforms.macosx then
defaults.variables.MAKE = "make"
defaults.external_lib_extension = "dylib"
defaults.arch = "macosx-"..cfg.target_cpu
defaults.variables.LIBFLAG = "-bundle -undefined dynamic_lookup -all_load"
defaults.variables.STAT = "/usr/bin/stat"
defaults.variables.STATFLAG = "-f '%A'"
local version = io.popen("sw_vers -productVersion"):read("*l")
version = tonumber(version and version:match("^[^.]+%.([^.]+)")) or 3
if version >= 10 then
version = 8
elseif version >= 5 then
version = 5
else
defaults.gcc_rpath = false
end
defaults.variables.CC = "env MACOSX_DEPLOYMENT_TARGET=10."..version.." gcc"
defaults.variables.LD = "env MACOSX_DEPLOYMENT_TARGET=10."..version.." gcc"
defaults.web_browser = "open"
end
if cfg.platforms.linux then
defaults.arch = "linux-"..cfg.target_cpu
end
if cfg.platforms.freebsd then
defaults.arch = "freebsd-"..cfg.target_cpu
defaults.gcc_rpath = false
defaults.variables.CC = "cc"
defaults.variables.LD = "cc"
end
if cfg.platforms.openbsd then
defaults.arch = "openbsd-"..cfg.target_cpu
end
if cfg.platforms.netbsd then
defaults.arch = "netbsd-"..cfg.target_cpu
end
if cfg.platforms.solaris then
defaults.arch = "solaris-"..cfg.target_cpu
--defaults.platforms = {"unix", "solaris"}
defaults.variables.MAKE = "gmake"
end
-- Expose some more values detected by LuaRocks for use by rockspec authors.
defaults.variables.LIB_EXTENSION = defaults.lib_extension
defaults.variables.OBJ_EXTENSION = defaults.obj_extension
defaults.variables.LUAROCKS_PREFIX = site_config.LUAROCKS_PREFIX
defaults.variables.LUA = site_config.LUA_DIR_SET and (defaults.variables.LUA_BINDIR.."/"..defaults.lua_interpreter) or defaults.lua_interpreter
-- Add built-in modules to rocks_provided
defaults.rocks_provided["lua"] = cfg.lua_version.."-1"
if bit32 then -- Lua 5.2+
defaults.rocks_provided["bit32"] = cfg.lua_version.."-1"
end
if utf8 then -- Lua 5.3+
defaults.rocks_provided["utf8"] = cfg.lua_version.."-1"
end
if package.loaded.jit then
-- LuaJIT
local lj_version = package.loaded.jit.version:match("LuaJIT (.*)"):gsub("%-","")
--defaults.rocks_provided["luajit"] = lj_version.."-1"
defaults.rocks_provided["luabitop"] = lj_version.."-1"
end
-- Use defaults:
-- Populate some arrays with values from their 'defaults' counterparts
-- if they were not already set by user.
for _, entry in ipairs({"variables", "rocks_provided"}) do
if not cfg[entry] then
cfg[entry] = {}
end
for k,v in pairs(defaults[entry]) do
if not cfg[entry][k] then
cfg[entry][k] = v
end
end
end
-- For values not set in the config file, use values from the 'defaults' table.
local cfg_mt = {
__index = function(t, k)
local default = defaults[k]
if default then
rawset(t, k, default)
end
return default
end
}
setmetatable(cfg, cfg_mt)
if not cfg.check_certificates then
cfg.variables.CURLNOCERTFLAG = "-k"
cfg.variables.WGETNOCERTFLAG = "--no-check-certificate"
end
function cfg.make_paths_from_tree(tree)
local lua_path, lib_path, bin_path
if type(tree) == "string" then
lua_path = tree..cfg.lua_modules_path
lib_path = tree..cfg.lib_modules_path
bin_path = tree.."/bin"
else
lua_path = tree.lua_dir or tree.root..cfg.lua_modules_path
lib_path = tree.lib_dir or tree.root..cfg.lib_modules_path
bin_path = tree.bin_dir or tree.root.."/bin"
end
return lua_path, lib_path, bin_path
end
function cfg.package_paths(current)
local new_path, new_cpath, new_bin = {}, {}, {}
local function add_tree_to_paths(tree)
local lua_path, lib_path, bin_path = cfg.make_paths_from_tree(tree)
table.insert(new_path, lua_path.."/?.lua")
table.insert(new_path, lua_path.."/?/init.lua")
table.insert(new_cpath, lib_path.."/?."..cfg.lib_extension)
table.insert(new_bin, bin_path)
end
if current then
add_tree_to_paths(current)
end
for _,tree in ipairs(cfg.rocks_trees) do
add_tree_to_paths(tree)
end
if extra_luarocks_module_dir then
table.insert(new_path, extra_luarocks_module_dir)
end
return table.concat(new_path, ";"), table.concat(new_cpath, ";"), table.concat(new_bin, cfg.export_path_separator)
end
function cfg.init_package_paths()
local lr_path, lr_cpath, lr_bin = cfg.package_paths()
package.path = util.remove_path_dupes(package.path .. ";" .. lr_path, ";")
package.cpath = util.remove_path_dupes(package.cpath .. ";" .. lr_cpath, ";")
end
function cfg.which_config()
local ret = {
system = {
file = sys_config_file or sys_config_file_default,
ok = sys_config_ok,
},
user = {
file = home_config_file or home_config_file_default,
ok = home_config_ok,
}
}
ret.nearest = (ret.user.ok and ret.user.file) or ret.system.file
return ret
end
cfg.user_agent = "LuaRocks/"..cfg.program_version.." "..cfg.arch
cfg.http_proxy = os.getenv("http_proxy")
cfg.https_proxy = os.getenv("https_proxy")
cfg.no_proxy = os.getenv("no_proxy")
--- Check if platform was detected
-- @param query string: The platform name to check.
-- @return boolean: true if LuaRocks is currently running on queried platform.
function cfg.is_platform(query)
assert(type(query) == "string")
for _, platform in ipairs(cfg.platforms) do
if platform == query then
return true
end
end
end
return cfg

View File

@ -0,0 +1,199 @@
--- Functions for command-line scripts.
local command_line = {}
local unpack = unpack or table.unpack
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local path = require("luarocks.path")
local dir = require("luarocks.dir")
local deps = require("luarocks.deps")
local fs = require("luarocks.fs")
local program = util.this_program("luarocks")
local function error_handler(err)
return debug.traceback("LuaRocks "..cfg.program_version..
" bug (please report at https://github.com/keplerproject/luarocks/issues).\n"..err, 2)
end
--- Display an error message and exit.
-- @param message string: The error message.
-- @param exitcode number: the exitcode to use
local function die(message, exitcode)
assert(type(message) == "string")
util.printerr("\nError: "..message)
local ok, err = xpcall(util.run_scheduled_functions, error_handler)
if not ok then
util.printerr("\nError: "..err)
exitcode = cfg.errorcodes.CRASH
end
os.exit(exitcode or cfg.errorcodes.UNSPECIFIED)
end
local function replace_tree(flags, tree)
tree = dir.normalize(tree)
flags["tree"] = tree
path.use_tree(tree)
end
--- Main command-line processor.
-- Parses input arguments and calls the appropriate driver function
-- to execute the action requested on the command-line, forwarding
-- to it any additional arguments passed by the user.
-- Uses the global table "commands", which contains
-- the loaded modules representing commands.
-- @param ... string: Arguments given on the command-line.
function command_line.run_command(...)
local args = {...}
local cmdline_vars = {}
for i = #args, 1, -1 do
local arg = args[i]
if arg:match("^[^-][^=]*=") then
local var, val = arg:match("^([A-Z_][A-Z0-9_]*)=(.*)")
if val then
cmdline_vars[var] = val
table.remove(args, i)
else
die("Invalid assignment: "..arg)
end
end
end
local nonflags = { util.parse_flags(unpack(args)) }
local flags = table.remove(nonflags, 1)
if flags.ERROR then
die(flags.ERROR.." See --help.")
end
if flags["from"] then flags["server"] = flags["from"] end
if flags["only-from"] then flags["only-server"] = flags["only-from"] end
if flags["only-sources-from"] then flags["only-sources"] = flags["only-sources-from"] end
if flags["to"] then flags["tree"] = flags["to"] end
if flags["nodeps"] then
flags["deps-mode"] = "none"
end
cfg.flags = flags
local command
if flags["verbose"] then -- setting it in the config file will kick-in earlier in the process
cfg.verbose = true
fs.verbose()
end
if flags["timeout"] then -- setting it in the config file will kick-in earlier in the process
local timeout = tonumber(flags["timeout"])
if timeout then
cfg.connection_timeout = timeout
else
die "Argument error: --timeout expects a numeric argument."
end
end
if flags["version"] then
util.printout(program.." "..cfg.program_version)
util.printout(program_description)
util.printout()
os.exit(cfg.errorcodes.OK)
elseif flags["help"] or #nonflags == 0 then
command = "help"
else
command = table.remove(nonflags, 1)
end
command = command:gsub("-", "_")
if cfg.local_by_default then
flags["local"] = true
end
if flags["deps-mode"] and not deps.check_deps_mode_flag(flags["deps-mode"]) then
die("Invalid entry for --deps-mode.")
end
if flags["branch"] then
cfg.branch = flags["branch"]
end
if flags["tree"] then
local named = false
for _, tree in ipairs(cfg.rocks_trees) do
if type(tree) == "table" and flags["tree"] == tree.name then
if not tree.root then
die("Configuration error: tree '"..tree.name.."' has no 'root' field.")
end
replace_tree(flags, tree.root)
named = true
break
end
end
if not named then
local root_dir = fs.absolute_name(flags["tree"])
replace_tree(flags, root_dir)
end
elseif flags["local"] then
if not cfg.home_tree then
die("The --local flag is meant for operating in a user's home directory.\n"..
"You are running as a superuser, which is intended for system-wide operation.\n"..
"To force using the superuser's home, use --tree explicitly.")
end
replace_tree(flags, cfg.home_tree)
else
local trees = cfg.rocks_trees
path.use_tree(trees[#trees])
end
if type(cfg.root_dir) == "string" then
cfg.root_dir = cfg.root_dir:gsub("/+$", "")
else
cfg.root_dir.root = cfg.root_dir.root:gsub("/+$", "")
end
cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "")
cfg.deploy_bin_dir = cfg.deploy_bin_dir:gsub("/+$", "")
cfg.deploy_lua_dir = cfg.deploy_lua_dir:gsub("/+$", "")
cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "")
cfg.variables.ROCKS_TREE = cfg.rocks_dir
cfg.variables.SCRIPTS_DIR = cfg.deploy_bin_dir
if flags["server"] then
local protocol, path = dir.split_url(flags["server"])
table.insert(cfg.rocks_servers, 1, protocol.."://"..path)
end
if flags["only-server"] then
cfg.rocks_servers = { flags["only-server"] }
end
if flags["only-sources"] then
cfg.only_sources_from = flags["only-sources"]
end
if command ~= "help" then
for k, v in pairs(cmdline_vars) do
cfg.variables[k] = v
end
end
if not fs.current_dir() or fs.current_dir() == "" then
die("Current directory does not exist. Please run LuaRocks from an existing directory.")
end
if commands[command] then
local cmd = require(commands[command])
local call_ok, ok, err, exitcode = xpcall(function() return cmd.command(flags, unpack(nonflags)) end, error_handler)
if not call_ok then
die(ok, cfg.errorcodes.CRASH)
elseif not ok then
die(err, exitcode)
end
else
die("Unknown command: "..command)
end
util.run_scheduled_functions()
end
return command_line

View File

@ -0,0 +1,72 @@
--- Module implementing the LuaRocks "config" command.
-- Queries information about the LuaRocks configuration.
local config_cmd = {}
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
util.add_run_function(config_cmd)
config_cmd.help_summary = "Query information about the LuaRocks configuration."
config_cmd.help_arguments = "<flag>"
config_cmd.help = [[
--lua-incdir Path to Lua header files.
--lua-libdir Path to Lua library files.
--lua-ver Lua version (in major.minor format). e.g. 5.1
--system-config Location of the system config file.
--user-config Location of the user config file.
--rock-trees Rocks trees in use. First the user tree, then the system tree.
]]
local function config_file(conf)
print(dir.normalize(conf.file))
if conf.ok then
return true
else
return nil, "file not found"
end
end
--- Driver function for "config" command.
-- @return boolean: True if succeeded, nil on errors.
function config_cmd.command(flags)
if flags["lua-incdir"] then
print(cfg.variables.LUA_INCDIR)
return true
end
if flags["lua-libdir"] then
print(cfg.variables.LUA_LIBDIR)
return true
end
if flags["lua-ver"] then
print(cfg.lua_version)
return true
end
local conf = cfg.which_config()
if flags["system-config"] then
return config_file(conf.system)
end
if flags["user-config"] then
return config_file(conf.user)
end
if flags["rock-trees"] then
for _, tree in ipairs(cfg.rocks_trees) do
if type(tree) == "string" then
util.printout(dir.normalize(tree))
else
local name = tree.name and "\t"..tree.name or ""
util.printout(dir.normalize(tree.root)..name)
end
end
return true
end
return nil, "Please provide a flag for querying configuration values. "..util.see_help("config")
end
return config_cmd

View File

@ -0,0 +1,752 @@
--- Dependency handling functions.
-- Dependencies are represented in LuaRocks through strings with
-- a package name followed by a comma-separated list of constraints.
-- Each constraint consists of an operator and a version number.
-- In this string format, version numbers are represented as
-- naturally as possible, like they are used by upstream projects
-- (e.g. "2.0beta3"). Internally, LuaRocks converts them to a purely
-- numeric representation, allowing comparison following some
-- "common sense" heuristics. The precise specification of the
-- comparison criteria is the source code of this module, but the
-- test/test_deps.lua file included with LuaRocks provides some
-- insights on what these criteria are.
local deps = {}
package.loaded["luarocks.deps"] = deps
local cfg = require("luarocks.cfg")
local manif_core = require("luarocks.manif_core")
local path = require("luarocks.path")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local operators = {
["=="] = "==",
["~="] = "~=",
[">"] = ">",
["<"] = "<",
[">="] = ">=",
["<="] = "<=",
["~>"] = "~>",
-- plus some convenience translations
[""] = "==",
["="] = "==",
["!="] = "~="
}
local deltas = {
scm = 1100,
cvs = 1000,
rc = -1000,
pre = -10000,
beta = -100000,
alpha = -1000000
}
local version_mt = {
--- Equality comparison for versions.
-- All version numbers must be equal.
-- If both versions have revision numbers, they must be equal;
-- otherwise the revision number is ignored.
-- @param v1 table: version table to compare.
-- @param v2 table: version table to compare.
-- @return boolean: true if they are considered equivalent.
__eq = function(v1, v2)
if #v1 ~= #v2 then
return false
end
for i = 1, #v1 do
if v1[i] ~= v2[i] then
return false
end
end
if v1.revision and v2.revision then
return (v1.revision == v2.revision)
end
return true
end,
--- Size comparison for versions.
-- All version numbers are compared.
-- If both versions have revision numbers, they are compared;
-- otherwise the revision number is ignored.
-- @param v1 table: version table to compare.
-- @param v2 table: version table to compare.
-- @return boolean: true if v1 is considered lower than v2.
__lt = function(v1, v2)
for i = 1, math.max(#v1, #v2) do
local v1i, v2i = v1[i] or 0, v2[i] or 0
if v1i ~= v2i then
return (v1i < v2i)
end
end
if v1.revision and v2.revision then
return (v1.revision < v2.revision)
end
return false
end
}
local version_cache = {}
setmetatable(version_cache, {
__mode = "kv"
})
--- Parse a version string, converting to table format.
-- A version table contains all components of the version string
-- converted to numeric format, stored in the array part of the table.
-- If the version contains a revision, it is stored numerically
-- in the 'revision' field. The original string representation of
-- the string is preserved in the 'string' field.
-- Returned version tables use a metatable
-- allowing later comparison through relational operators.
-- @param vstring string: A version number in string format.
-- @return table or nil: A version table or nil
-- if the input string contains invalid characters.
function deps.parse_version(vstring)
if not vstring then return nil end
assert(type(vstring) == "string")
local cached = version_cache[vstring]
if cached then
return cached
end
local version = {}
local i = 1
local function add_token(number)
version[i] = version[i] and version[i] + number/100000 or number
i = i + 1
end
-- trim leading and trailing spaces
vstring = vstring:match("^%s*(.*)%s*$")
version.string = vstring
-- store revision separately if any
local main, revision = vstring:match("(.*)%-(%d+)$")
if revision then
vstring = main
version.revision = tonumber(revision)
end
while #vstring > 0 do
-- extract a number
local token, rest = vstring:match("^(%d+)[%.%-%_]*(.*)")
if token then
add_token(tonumber(token))
else
-- extract a word
token, rest = vstring:match("^(%a+)[%.%-%_]*(.*)")
if not token then
util.printerr("Warning: version number '"..vstring.."' could not be parsed.")
version[i] = 0
break
end
version[i] = deltas[token] or (token:byte() / 1000)
end
vstring = rest
end
setmetatable(version, version_mt)
version_cache[vstring] = version
return version
end
--- Utility function to compare version numbers given as strings.
-- @param a string: one version.
-- @param b string: another version.
-- @return boolean: True if a > b.
function deps.compare_versions(a, b)
return deps.parse_version(a) > deps.parse_version(b)
end
--- Consumes a constraint from a string, converting it to table format.
-- For example, a string ">= 1.0, > 2.0" is converted to a table in the
-- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned
-- back to the caller.
-- @param input string: A list of constraints in string format.
-- @return (table, string) or nil: A table representing the same
-- constraints and the string with the unused input, or nil if the
-- input string is invalid.
local function parse_constraint(input)
assert(type(input) == "string")
local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)")
local _op = operators[op]
version = deps.parse_version(version)
if not _op then
return nil, "Encountered bad constraint operator: '"..tostring(op).."' in '"..input.."'"
end
if not version then
return nil, "Could not parse version from constraint: '"..input.."'"
end
return { op = _op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest
end
--- Convert a list of constraints from string to table format.
-- For example, a string ">= 1.0, < 2.0" is converted to a table in the format
-- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}.
-- Version tables use a metatable allowing later comparison through
-- relational operators.
-- @param input string: A list of constraints in string format.
-- @return table or nil: A table representing the same constraints,
-- or nil if the input string is invalid.
function deps.parse_constraints(input)
assert(type(input) == "string")
local constraints, constraint, oinput = {}, nil, input
while #input > 0 do
constraint, input = parse_constraint(input)
if constraint then
table.insert(constraints, constraint)
else
return nil, "Failed to parse constraint '"..tostring(oinput).."' with error: ".. input
end
end
return constraints
end
--- Convert a dependency from string to table format.
-- For example, a string "foo >= 1.0, < 2.0"
-- is converted to a table in the format
-- {name = "foo", constraints = {{op = ">=", version={1,0}},
-- {op = "<", version={2,0}}}}. Version tables use a metatable
-- allowing later comparison through relational operators.
-- @param dep string: A dependency in string format
-- as entered in rockspec files.
-- @return table or nil: A table representing the same dependency relation,
-- or nil if the input string is invalid.
function deps.parse_dep(dep)
assert(type(dep) == "string")
local name, rest = dep:match("^%s*([a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*(.*)")
if not name then return nil, "failed to extract dependency name from '"..tostring(dep).."'" end
local constraints, err = deps.parse_constraints(rest)
if not constraints then return nil, err end
return { name = name, constraints = constraints }
end
--- Convert a version table to a string.
-- @param v table: The version table
-- @param internal boolean or nil: Whether to display versions in their
-- internal representation format or how they were specified.
-- @return string: The dependency information pretty-printed as a string.
function deps.show_version(v, internal)
assert(type(v) == "table")
assert(type(internal) == "boolean" or not internal)
return (internal
and table.concat(v, ":")..(v.revision and tostring(v.revision) or "")
or v.string)
end
--- Convert a dependency in table format to a string.
-- @param dep table: The dependency in table format
-- @param internal boolean or nil: Whether to display versions in their
-- internal representation format or how they were specified.
-- @return string: The dependency information pretty-printed as a string.
function deps.show_dep(dep, internal)
assert(type(dep) == "table")
assert(type(internal) == "boolean" or not internal)
if #dep.constraints > 0 then
local pretty = {}
for _, c in ipairs(dep.constraints) do
table.insert(pretty, c.op .. " " .. deps.show_version(c.version, internal))
end
return dep.name.." "..table.concat(pretty, ", ")
else
return dep.name
end
end
--- A more lenient check for equivalence between versions.
-- This returns true if the requested components of a version
-- match and ignore the ones that were not given. For example,
-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match.
-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2"
-- doesn't.
-- @param version string or table: Version to be tested; may be
-- in string format or already parsed into a table.
-- @param requested string or table: Version requested; may be
-- in string format or already parsed into a table.
-- @return boolean: True if the tested version matches the requested
-- version, false otherwise.
local function partial_match(version, requested)
assert(type(version) == "string" or type(version) == "table")
assert(type(requested) == "string" or type(version) == "table")
if type(version) ~= "table" then version = deps.parse_version(version) end
if type(requested) ~= "table" then requested = deps.parse_version(requested) end
if not version or not requested then return false end
for i, ri in ipairs(requested) do
local vi = version[i] or 0
if ri ~= vi then return false end
end
if requested.revision then
return requested.revision == version.revision
end
return true
end
--- Check if a version satisfies a set of constraints.
-- @param version table: A version in table format
-- @param constraints table: An array of constraints in table format.
-- @return boolean: True if version satisfies all constraints,
-- false otherwise.
function deps.match_constraints(version, constraints)
assert(type(version) == "table")
assert(type(constraints) == "table")
local ok = true
setmetatable(version, version_mt)
for _, constr in pairs(constraints) do
if type(constr.version) == "string" then
constr.version = deps.parse_version(constr.version)
end
local constr_version, constr_op = constr.version, constr.op
setmetatable(constr_version, version_mt)
if constr_op == "==" then ok = version == constr_version
elseif constr_op == "~=" then ok = version ~= constr_version
elseif constr_op == ">" then ok = version > constr_version
elseif constr_op == "<" then ok = version < constr_version
elseif constr_op == ">=" then ok = version >= constr_version
elseif constr_op == "<=" then ok = version <= constr_version
elseif constr_op == "~>" then ok = partial_match(version, constr_version)
end
if not ok then break end
end
return ok
end
--- Attempt to match a dependency to an installed rock.
-- @param dep table: A dependency parsed in table format.
-- @param blacklist table: Versions that can't be accepted. Table where keys
-- are program versions and values are 'true'.
-- @return string or nil: latest installed version of the rock matching the dependency
-- or nil if it could not be matched.
local function match_dep(dep, blacklist, deps_mode)
assert(type(dep) == "table")
local versions
if cfg.rocks_provided[dep.name] then
-- provided rocks have higher priority than manifest's rocks
versions = { cfg.rocks_provided[dep.name] }
else
versions = manif_core.get_versions(dep.name, deps_mode)
end
local latest_version
for _, vstring in ipairs(versions) do
if not blacklist or not blacklist[vstring] then
local version = deps.parse_version(vstring)
if deps.match_constraints(version, dep.constraints) then
if not latest_version or version > latest_version then
latest_version = version
end
end
end
end
return latest_version and latest_version.string
end
--- Attempt to match dependencies of a rockspec to installed rocks.
-- @param rockspec table: The rockspec loaded as a table.
-- @param blacklist table or nil: Program versions to not use as valid matches.
-- Table where keys are program names and values are tables where keys
-- are program versions and values are 'true'.
-- @return table, table, table: A table where keys are dependencies parsed
-- in table format and values are tables containing fields 'name' and
-- version' representing matches; a table of missing dependencies
-- parsed as tables; and a table of "no-upgrade" missing dependencies
-- (to be used in plugin modules so that a plugin does not force upgrade of
-- its parent application).
function deps.match_deps(rockspec, blacklist, deps_mode)
assert(type(rockspec) == "table")
assert(type(blacklist) == "table" or not blacklist)
local matched, missing, no_upgrade = {}, {}, {}
for _, dep in ipairs(rockspec.dependencies) do
local found = match_dep(dep, blacklist and blacklist[dep.name] or nil, deps_mode)
if found then
if not cfg.rocks_provided[dep.name] then
matched[dep] = {name = dep.name, version = found}
end
else
if dep.constraints[1] and dep.constraints[1].no_upgrade then
no_upgrade[dep.name] = dep
else
missing[dep.name] = dep
end
end
end
return matched, missing, no_upgrade
end
--- Return a set of values of a table.
-- @param tbl table: The input table.
-- @return table: The array of keys.
local function values_set(tbl)
local set = {}
for _, v in pairs(tbl) do
set[v] = true
end
return set
end
local function rock_status(name, deps_mode)
local search = require("luarocks.search")
local installed = match_dep(search.make_query(name), nil, deps_mode)
local installation_type = cfg.rocks_provided[name] and "provided by VM" or "installed"
return installed and installed.." "..installation_type or "not installed"
end
--- Check depenendencies of a package and report any missing ones.
-- @param name string: package name.
-- @param version string: package version.
-- @param dependencies table: array of dependencies.
-- @param deps_mode string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
function deps.report_missing_dependencies(name, version, dependencies, deps_mode)
local first_missing_dep = true
for _, dep in ipairs(dependencies) do
if not match_dep(dep, nil, deps_mode) then
if first_missing_dep then
util.printout(("Missing dependencies for %s %s:"):format(name, version))
first_missing_dep = false
end
util.printout((" %s (%s)"):format(deps.show_dep(dep), rock_status(dep.name, deps_mode)))
end
end
end
--- Check dependencies of a rock and attempt to install any missing ones.
-- Packages are installed using the LuaRocks "install" command.
-- Aborts the program if a dependency could not be fulfilled.
-- @param rockspec table: A rockspec in table format.
-- @return boolean or (nil, string, [string]): True if no errors occurred, or
-- nil and an error message if any test failed, followed by an optional
-- error code.
function deps.fulfill_dependencies(rockspec, deps_mode)
local search = require("luarocks.search")
local install = require("luarocks.install")
if rockspec.supported_platforms then
if not deps.platforms_set then
deps.platforms_set = values_set(cfg.platforms)
end
local supported = nil
for _, plat in pairs(rockspec.supported_platforms) do
local neg, plat = plat:match("^(!?)(.*)")
if neg == "!" then
if deps.platforms_set[plat] then
return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
end
else
if deps.platforms_set[plat] then
supported = true
else
if supported == nil then
supported = false
end
end
end
end
if supported == false then
local plats = table.concat(cfg.platforms, ", ")
return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms."
end
end
deps.report_missing_dependencies(rockspec.name, rockspec.version, rockspec.dependencies, deps_mode)
local first_missing_dep = true
for _, dep in ipairs(rockspec.dependencies) do
if not match_dep(dep, nil, deps_mode) then
if first_missing_dep then
util.printout()
first_missing_dep = false
end
util.printout(("%s %s depends on %s (%s)"):format(
rockspec.name, rockspec.version, deps.show_dep(dep), rock_status(dep.name, deps_mode)))
if dep.constraints[1] and dep.constraints[1].no_upgrade then
util.printerr("This version of "..rockspec.name.." is designed for use with")
util.printerr(deps.show_dep(dep)..", but is configured to avoid upgrading it")
util.printerr("automatically. Please upgrade "..dep.name.." with")
util.printerr(" luarocks install "..dep.name)
util.printerr("or choose an older version of "..rockspec.name.." with")
util.printerr(" luarocks search "..rockspec.name)
return nil, "Failed matching dependencies"
end
local url, search_err = search.find_suitable_rock(dep)
if not url then
return nil, "Could not satisfy dependency "..deps.show_dep(dep)..": "..search_err
end
util.printout("Installing "..url)
local ok, install_err, errcode = install.command({deps_mode = deps_mode}, url)
if not ok then
return nil, "Failed installing dependency: "..url.." - "..install_err, errcode
end
end
end
return true
end
--- If filename matches a pattern, return the capture.
-- For example, given "libfoo.so" and "lib?.so" is a pattern,
-- returns "foo" (which can then be used to build names
-- based on other patterns.
-- @param file string: a filename
-- @param pattern string: a pattern, where ? is to be matched by the filename.
-- @return string The pattern, if found, or nil.
local function deconstruct_pattern(file, pattern)
local depattern = "^"..(pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")).."$"
return (file:match(depattern))
end
--- Construct all possible patterns for a name and add to the files array.
-- Run through the patterns array replacing all occurrences of "?"
-- with the given file name and store them in the files array.
-- @param file string A raw name (e.g. "foo")
-- @param array of string An array of patterns with "?" as the wildcard
-- (e.g. {"?.so", "lib?.so"})
-- @param files The array of constructed names
local function add_all_patterns(file, patterns, files)
for _, pattern in ipairs(patterns) do
table.insert(files, (pattern:gsub("?", file)))
end
end
--- Set up path-related variables for external dependencies.
-- For each key in the external_dependencies table in the
-- rockspec file, four variables are created: <key>_DIR, <key>_BINDIR,
-- <key>_INCDIR and <key>_LIBDIR. These are not overwritten
-- if already set (e.g. by the LuaRocks config file or through the
-- command-line). Values in the external_dependencies table
-- are tables that may contain a "header" or a "library" field,
-- with filenames to be tested for existence.
-- @param rockspec table: The rockspec table.
-- @param mode string: if "build" is given, checks all files;
-- if "install" is given, do not scan for headers.
-- @return boolean or (nil, string): True if no errors occurred, or
-- nil and an error message if any test failed.
function deps.check_external_deps(rockspec, mode)
assert(type(rockspec) == "table")
local fs = require("luarocks.fs")
local vars = rockspec.variables
local patterns = cfg.external_deps_patterns
local subdirs = cfg.external_deps_subdirs
if mode == "install" then
patterns = cfg.runtime_external_deps_patterns
subdirs = cfg.runtime_external_deps_subdirs
end
if rockspec.external_dependencies then
for name, ext_files in util.sortedpairs(rockspec.external_dependencies) do
local ok = true
local failed_files = {program = {}, header = {}, library = {}}
local failed_dirname
local failed_testfile
for _, extdir in ipairs(cfg.external_deps_dirs) do
ok = true
local prefix = vars[name.."_DIR"]
local dirs = {
BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }
}
if mode == "install" then
dirs.INCDIR = nil
end
if not prefix then
prefix = extdir
end
if type(prefix) == "table" then
if prefix.bin then
dirs.BINDIR.subdir = prefix.bin
end
if prefix.include then
if dirs.INCDIR then
dirs.INCDIR.subdir = prefix.include
end
end
if prefix.lib then
dirs.LIBDIR.subdir = prefix.lib
end
prefix = prefix.prefix
end
for dirname, dirdata in util.sortedpairs(dirs) do
local paths
local path_var_value = vars[name.."_"..dirname]
if path_var_value then
paths = { path_var_value }
elseif type(dirdata.subdir) == "table" then
paths = {}
for i,v in ipairs(dirdata.subdir) do
paths[i] = dir.path(prefix, v)
end
else
paths = { dir.path(prefix, dirdata.subdir) }
end
dirdata.dir = paths[1]
local file = ext_files[dirdata.testfile]
if file then
local files = {}
if not file:match("%.") then
add_all_patterns(file, dirdata.pattern, files)
else
for _, pattern in ipairs(dirdata.pattern) do
local matched = deconstruct_pattern(file, pattern)
if matched then
add_all_patterns(matched, dirdata.pattern, files)
end
end
table.insert(files, file)
end
local found = false
for _, f in ipairs(files) do
-- small convenience hack
if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
end
local pattern
if f:match("%*") then
pattern = f:gsub("%.", "%%."):gsub("%*", ".*")
f = "matching "..f
end
for _, d in ipairs(paths) do
if pattern then
for entry in fs.dir(d) do
if entry:match(pattern) then
found = true
break
end
end
else
found = fs.is_file(dir.path(d, f))
end
if found then
dirdata.dir = d
break
else
table.insert(failed_files[dirdata.testfile], f.." in "..d)
end
end
if found then
break
end
end
if not found then
ok = false
failed_dirname = dirname
failed_testfile = dirdata.testfile
break
end
end
end
if ok then
for dirname, dirdata in pairs(dirs) do
vars[name.."_"..dirname] = dirdata.dir
end
vars[name.."_DIR"] = prefix
break
end
end
if not ok then
local lines = {"Could not find "..failed_testfile.." file for "..name}
local failed_paths = {}
for _, failed_file in ipairs(failed_files[failed_testfile]) do
if not failed_paths[failed_file] then
failed_paths[failed_file] = true
table.insert(lines, " No file "..failed_file)
end
end
table.insert(lines, "You may have to install "..name.." in your system and/or pass "..name.."_DIR or "..name.."_"..failed_dirname.." to the luarocks command.")
table.insert(lines, "Example: luarocks install "..rockspec.name.." "..name.."_DIR=/usr/local")
return nil, table.concat(lines, "\n"), "dependency"
end
end
end
return true
end
--- Recursively add satisfied dependencies of a package to a table,
-- to build a transitive closure of all dependent packages.
-- Additionally ensures that `dependencies` table of the manifest is up-to-date.
-- @param results table: The results table being built, maps package names to versions.
-- @param manifest table: The manifest table containing dependencies.
-- @param name string: Package name.
-- @param version string: Package version.
function deps.scan_deps(results, manifest, name, version, deps_mode)
assert(type(results) == "table")
assert(type(manifest) == "table")
assert(type(name) == "string")
assert(type(version) == "string")
local fetch = require("luarocks.fetch")
if results[name] then
return
end
if not manifest.dependencies then manifest.dependencies = {} end
local dependencies = manifest.dependencies
if not dependencies[name] then dependencies[name] = {} end
local dependencies_name = dependencies[name]
local deplist = dependencies_name[version]
local rockspec, err
if not deplist then
rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version), false)
if not rockspec then
util.printerr("Couldn't load rockspec for "..name.." "..version..": "..err)
return
end
dependencies_name[version] = rockspec.dependencies
else
rockspec = { dependencies = deplist }
end
local matched = deps.match_deps(rockspec, nil, deps_mode)
results[name] = version
for _, match in pairs(matched) do
deps.scan_deps(results, manifest, match.name, match.version, deps_mode)
end
end
local valid_deps_modes = {
one = true,
order = true,
all = true,
none = true,
}
function deps.check_deps_mode_flag(flag)
return valid_deps_modes[flag]
end
function deps.get_deps_mode(flags)
if flags["deps-mode"] then
return flags["deps-mode"]
else
return cfg.deps_mode
end
end
function deps.deps_mode_to_flag(deps_mode)
return "--deps-mode="..deps_mode
end
return deps

View File

@ -0,0 +1,74 @@
--- Generic utilities for handling pathnames.
local dir = {}
package.loaded["luarocks.dir"] = dir
dir.separator = "/"
--- Strip the path off a path+filename.
-- @param pathname string: A path+name, such as "/a/b/c"
-- or "\a\b\c".
-- @return string: The filename without its path, such as "c".
function dir.base_name(pathname)
assert(type(pathname) == "string")
local base = pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)")
return base or pathname
end
--- Strip the name off a path+filename.
-- @param pathname string: A path+name, such as "/a/b/c".
-- @return string: The filename without its path, such as "/a/b".
-- For entries such as "/a/b/", "/a" is returned. If there are
-- no directory separators in input, "" is returned.
function dir.dir_name(pathname)
assert(type(pathname) == "string")
return (pathname:gsub("/*$", ""):match("(.*)[/]+[^/]*")) or ""
end
--- Describe a path in a cross-platform way.
-- Use this function to avoid platform-specific directory
-- separators in other modules. Removes trailing slashes from
-- each component given, to avoid repeated separators.
-- Separators inside strings are kept, to handle URLs containing
-- protocols.
-- @param ... strings representing directories
-- @return string: a string with a platform-specific representation
-- of the path.
function dir.path(...)
local t = {...}
while t[1] == "" do
table.remove(t, 1)
end
return (table.concat(t, "/"):gsub("([^:])/+", "%1/"):gsub("^/+", "/"):gsub("/*$", ""))
end
--- Split protocol and path from an URL or local pathname.
-- URLs should be in the "protocol://path" format.
-- For local pathnames, "file" is returned as the protocol.
-- @param url string: an URL or a local pathname.
-- @return string, string: the protocol, and the pathname without the protocol.
function dir.split_url(url)
assert(type(url) == "string")
local protocol, pathname = url:match("^([^:]*)://(.*)")
if not protocol then
protocol = "file"
pathname = url
end
return protocol, pathname
end
--- Normalize a url or local path.
-- URLs should be in the "protocol://path" format. System independent
-- forward slashes are used, removing trailing and double slashes
-- @param url string: an URL or a local pathname.
-- @return string: Normalized result.
function dir.normalize(name)
local protocol, pathname = dir.split_url(name)
pathname = pathname:gsub("\\", "/"):gsub("(.)/*$", "%1"):gsub("//", "/")
if protocol ~= "file" then pathname = protocol .."://"..pathname end
return pathname
end
return dir

View File

@ -0,0 +1,157 @@
--- Module implementing the LuaRocks "doc" command.
-- Shows documentation for an installed rock.
local doc = {}
package.loaded["luarocks.doc"] = doc
local util = require("luarocks.util")
local search = require("luarocks.search")
local path = require("luarocks.path")
local dir = require("luarocks.dir")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local download = require("luarocks.download")
util.add_run_function(doc)
doc.help_summary = "Show documentation for an installed rock."
doc.help = [[
<argument> is an existing package name.
Without any flags, tries to load the documentation
using a series of heuristics.
With these flags, return only the desired information:
--home Open the home page of project.
--list List documentation files only.
For more information about a rock, see the 'show' command.
]]
local function show_homepage(homepage, name, version)
if not homepage then
return nil, "No 'homepage' field in rockspec for "..name.." "..version
end
util.printout("Opening "..homepage.." ...")
fs.browser(homepage)
return true
end
local function try_to_open_homepage(name, version)
local temp_dir, err = fs.make_temp_dir("doc-"..name.."-"..(version or ""))
if not temp_dir then
return nil, "Failed creating temporary directory: "..err
end
util.schedule_function(fs.delete, temp_dir)
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
local filename, err = download.download("rockspec", name, version)
if not filename then return nil, err end
local rockspec, err = fetch.load_local_rockspec(filename)
if not rockspec then return nil, err end
fs.pop_dir()
local descript = rockspec.description or {}
if not descript.homepage then return nil, "No homepage defined for "..name end
return show_homepage(descript.homepage, name, version)
end
--- Driver function for "doc" command.
-- @param name or nil: an existing package name.
-- @param version string or nil: a version may also be passed.
-- @return boolean: True if succeeded, nil on errors.
function doc.command(flags, name, version)
if not name then
return nil, "Argument missing. "..util.see_help("doc")
end
name = name:lower()
local iname, iversion, repo = search.pick_installed_rock(name, version, flags["tree"])
if not iname then
util.printout(name..(version and " "..version or "").." is not installed. Looking for it in the rocks servers...")
return try_to_open_homepage(name, version)
end
name, version = iname, iversion
local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo))
if not rockspec then return nil,err end
local descript = rockspec.description or {}
if flags["home"] then
return show_homepage(descript.homepage, name, version)
end
local directory = path.install_dir(name,version,repo)
local docdir
local directories = { "doc", "docs" }
for _, d in ipairs(directories) do
local dirname = dir.path(directory, d)
if fs.is_dir(dirname) then
docdir = dirname
break
end
end
if not docdir then
if descript.homepage and not flags["list"] then
util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...")
fs.browser(descript.homepage)
return true
end
return nil, "Documentation directory not found for "..name.." "..version
end
docdir = dir.normalize(docdir):gsub("/+", "/")
local files = fs.find(docdir)
local htmlpatt = "%.html?$"
local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" }
local basenames = { "index", "readme", "manual" }
local porcelain = flags["porcelain"]
if #files > 0 then
util.title("Documentation files for "..name.." "..version, porcelain)
if porcelain then
for _, file in ipairs(files) do
util.printout(docdir.."/"..file)
end
else
util.printout(docdir.."/")
for _, file in ipairs(files) do
util.printout("\t"..file)
end
end
end
if flags["list"] then
return true
end
for _, extension in ipairs(extensions) do
for _, basename in ipairs(basenames) do
local filename = basename..extension
local found
for _, file in ipairs(files) do
if file:lower():match(filename) and ((not found) or #file < #found) then
found = file
end
end
if found then
local pathname = dir.path(docdir, found)
util.printout()
util.printout("Opening "..pathname.." ...")
util.printout()
local ok = fs.browser(pathname)
if not ok and not pathname:match(htmlpatt) then
local fd = io.open(pathname, "r")
util.printout(fd:read("*a"))
fd:close()
end
return true
end
end
end
return true
end
return doc

View File

@ -0,0 +1,109 @@
--- Module implementing the luarocks "download" command.
-- Download a rock from the repository.
local download = {}
package.loaded["luarocks.download"] = download
local util = require("luarocks.util")
local path = require("luarocks.path")
local fetch = require("luarocks.fetch")
local search = require("luarocks.search")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
util.add_run_function(download)
download.help_summary = "Download a specific rock file from a rocks server."
download.help_arguments = "[--all] [--arch=<arch> | --source | --rockspec] [<name> [<version>]]"
download.help = [[
--all Download all files if there are multiple matches.
--source Download .src.rock if available.
--rockspec Download .rockspec if available.
--arch=<arch> Download rock for a specific architecture.
]]
local function get_file(filename)
local protocol, pathname = dir.split_url(filename)
if protocol == "file" then
local ok, err = fs.copy(pathname, fs.current_dir(), cfg.perm_read)
if ok then
return pathname
else
return nil, err
end
else
return fetch.fetch_url(filename)
end
end
function download.download(arch, name, version, all)
local query = search.make_query(name, version)
if arch then query.arch = arch end
local search_err
if all then
if name == "" then query.exact_name = false end
local results = search.search_repos(query)
local has_result = false
local all_ok = true
local any_err = ""
for name, result in pairs(results) do
for version, items in pairs(result) do
for _, item in ipairs(items) do
-- Ignore provided rocks.
if item.arch ~= "installed" then
has_result = true
local filename = path.make_url(item.repo, name, version, item.arch)
local ok, err = get_file(filename)
if not ok then
all_ok = false
any_err = any_err .. "\n" .. err
end
end
end
end
end
if has_result then
return all_ok, any_err
end
else
local url
url, search_err = search.find_suitable_rock(query)
if url then
return get_file(url)
end
end
return nil, "Could not find a result named "..name..(version and " "..version or "")..
(search_err and ": "..search_err or ".")
end
--- Driver function for the "download" command.
-- @param name string: a rock name.
-- @param version string or nil: if the name of a package is given, a
-- version may also be passed.
-- @return boolean or (nil, string): true if successful or nil followed
-- by an error message.
function download.command(flags, name, version)
assert(type(version) == "string" or not version)
if type(name) ~= "string" and not flags["all"] then
return nil, "Argument missing. "..util.see_help("download")
end
if not name then name, version = "", "" end
local arch
if flags["source"] then
arch = "src"
elseif flags["rockspec"] then
arch = "rockspec"
elseif flags["arch"] then
arch = flags["arch"]
end
local dl, err = download.download(arch, name:lower(), version, flags["all"])
return dl and true, err
end
return download

View File

@ -0,0 +1,394 @@
--- Functions related to fetching and loading local and remote files.
local fetch = {}
package.loaded["luarocks.fetch"] = fetch
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local type_check = require("luarocks.type_check")
local path = require("luarocks.path")
local deps = require("luarocks.deps")
local persist = require("luarocks.persist")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
function fetch.is_basic_protocol(protocol, remote)
return protocol == "http" or protocol == "https" or protocol == "ftp" or (not remote and protocol == "file")
end
--- Fetch a local or remote file.
-- Make a remote or local URL/pathname local, fetching the file if necessary.
-- Other "fetch" and "load" functions use this function to obtain files.
-- If a local pathname is given, it is returned as a result.
-- @param url string: a local pathname or a remote URL.
-- @param filename string or nil: this function attempts to detect the
-- resulting local filename of the remote file as the basename of the URL;
-- if that is not correct (due to a redirection, for example), the local
-- filename can be given explicitly as this second argument.
-- @return string or (nil, string, [string]): the absolute local pathname for the
-- fetched file, or nil and a message in case of errors, followed by
-- an optional error code.
function fetch.fetch_url(url, filename, cache)
assert(type(url) == "string")
assert(type(filename) == "string" or not filename)
local protocol, pathname = dir.split_url(url)
if protocol == "file" then
return fs.absolute_name(pathname)
elseif fetch.is_basic_protocol(protocol, true) then
local ok, name = fs.download(url, filename, cache)
if not ok then
return nil, "Failed downloading "..url..(filename and " - "..filename or ""), "network"
end
return name
else
return nil, "Unsupported protocol "..protocol
end
end
--- For remote URLs, create a temporary directory and download URL inside it.
-- This temporary directory will be deleted on program termination.
-- For local URLs, just return the local pathname and its directory.
-- @param url string: URL to be downloaded
-- @param tmpname string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @param filename string or nil: local filename of URL to be downloaded,
-- in case it can't be inferred from the URL.
-- @return (string, string) or (nil, string, [string]): absolute local pathname of
-- the fetched file and temporary directory name; or nil and an error message
-- followed by an optional error code
function fetch.fetch_url_at_temp_dir(url, tmpname, filename)
assert(type(url) == "string")
assert(type(tmpname) == "string")
assert(type(filename) == "string" or not filename)
filename = filename or dir.base_name(url)
local protocol, pathname = dir.split_url(url)
if protocol == "file" then
if fs.exists(pathname) then
return pathname, dir.dir_name(fs.absolute_name(pathname))
else
return nil, "File not found: "..pathname
end
else
local temp_dir, err = fs.make_temp_dir(tmpname)
if not temp_dir then
return nil, "Failed creating temporary directory "..tmpname..": "..err
end
util.schedule_function(fs.delete, temp_dir)
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
local file, err, errcode = fetch.fetch_url(url, filename)
fs.pop_dir()
if not file then
return nil, "Error fetching file: "..err, errcode
end
return file, temp_dir
end
end
-- Determine base directory of a fetched URL by extracting its
-- archive and looking for a directory in the root.
-- @param file string: absolute local pathname of the fetched file
-- @param temp_dir string: temporary directory in which URL was fetched.
-- @param src_url string: URL to use when inferring base directory.
-- @param src_dir string or nil: expected base directory (inferred
-- from src_url if not given).
-- @return (string, string) or (string, nil) or (nil, string):
-- The inferred base directory and the one actually found (which may
-- be nil if not found), or nil followed by an error message.
-- The inferred dir is returned first to avoid confusion with errors,
-- because it is never nil.
function fetch.find_base_dir(file, temp_dir, src_url, src_dir)
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
fs.unpack_archive(file)
local inferred_dir = src_dir or fetch.url_to_base_dir(src_url)
local found_dir = nil
if fs.exists(inferred_dir) then
found_dir = inferred_dir
else
util.printerr("Directory "..inferred_dir.." not found")
local files = fs.list_dir()
if files then
table.sort(files)
for i,filename in ipairs(files) do
if fs.is_dir(filename) then
util.printerr("Found "..filename)
found_dir = filename
break
end
end
end
end
fs.pop_dir()
return inferred_dir, found_dir
end
--- Obtain a rock and unpack it.
-- If a directory is not given, a temporary directory will be created,
-- which will be deleted on program termination.
-- @param rock_file string: URL or filename of the rock.
-- @param dest string or nil: if given, directory will be used as
-- a permanent destination.
-- @return string or (nil, string, [string]): the directory containing the contents
-- of the unpacked rock.
function fetch.fetch_and_unpack_rock(rock_file, dest)
assert(type(rock_file) == "string")
assert(type(dest) == "string" or not dest)
local name = dir.base_name(rock_file):match("(.*)%.[^.]*%.rock")
local rock_file, err, errcode = fetch.fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name)
if not rock_file then
return nil, "Could not fetch rock file: " .. err, errcode
end
rock_file = fs.absolute_name(rock_file)
local unpack_dir
if dest then
unpack_dir = dest
local ok, err = fs.make_dir(unpack_dir)
if not ok then
return nil, "Failed unpacking rock file: " .. err
end
else
unpack_dir = fs.make_temp_dir(name)
end
if not dest then
util.schedule_function(fs.delete, unpack_dir)
end
local ok, err = fs.change_dir(unpack_dir)
if not ok then return nil, err end
ok = fs.unzip(rock_file)
if not ok then
return nil, "Failed unpacking rock file: " .. rock_file
end
fs.pop_dir()
return unpack_dir
end
function fetch.url_to_base_dir(url)
-- for extensions like foo.tar.gz, "gz" is stripped first
local known_exts = {}
for _, ext in ipairs{"zip", "git", "tgz", "tar", "gz", "bz2"} do
known_exts[ext] = ""
end
local base = dir.base_name(url)
return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", ""))
end
--- Back-end function that actually loads the local rockspec.
-- Performs some validation and postprocessing of the rockspec contents.
-- @param filename string: The local filename of the rockspec file.
-- @param quick boolean: if true, skips some steps when loading
-- rockspec.
-- @return table or (nil, string): A table representing the rockspec
-- or nil followed by an error message.
function fetch.load_local_rockspec(filename, quick)
assert(type(filename) == "string")
filename = fs.absolute_name(filename)
local rockspec, err = persist.load_into_table(filename)
if not rockspec then
return nil, "Could not load rockspec file "..filename.." ("..err..")"
end
if cfg.branch and (type(rockspec.source) == "table") then
rockspec.source.branch = cfg.branch
end
local globals = err
if rockspec.rockspec_format then
if deps.compare_versions(rockspec.rockspec_format, type_check.rockspec_format) then
return nil, "Rockspec format "..rockspec.rockspec_format.." is not supported, please upgrade LuaRocks."
end
end
if not quick then
local ok, err = type_check.type_check_rockspec(rockspec, globals)
if not ok then
return nil, filename..": "..err
end
end
util.platform_overrides(rockspec.build)
util.platform_overrides(rockspec.dependencies)
util.platform_overrides(rockspec.external_dependencies)
util.platform_overrides(rockspec.source)
util.platform_overrides(rockspec.hooks)
local basename = dir.base_name(filename)
if basename == "rockspec" then
rockspec.name = rockspec.package:lower()
else
rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")
if not rockspec.name then
return nil, "Expected filename in format 'name-version-revision.rockspec'."
end
end
local protocol, pathname = dir.split_url(rockspec.source.url)
if fetch.is_basic_protocol(protocol) then
rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url)
end
rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
-- Temporary compatibility
if rockspec.source.cvs_module then rockspec.source.module = rockspec.source.cvs_module end
if rockspec.source.cvs_tag then rockspec.source.tag = rockspec.source.cvs_tag end
local name_version = rockspec.package:lower() .. "-" .. rockspec.version
if basename ~= "rockspec" and basename ~= name_version .. ".rockspec" then
return nil, "Inconsistency between rockspec filename ("..basename..") and its contents ("..name_version..".rockspec)."
end
rockspec.local_filename = filename
local filebase = rockspec.source.file or rockspec.source.url
local base = fetch.url_to_base_dir(filebase)
rockspec.source.dir = rockspec.source.dir
or rockspec.source.module
or ((filebase:match("%.lua$") or filebase:match("%.c$")) and ".")
or base
if rockspec.dependencies then
for i = 1, #rockspec.dependencies do
local parsed, err = deps.parse_dep(rockspec.dependencies[i])
if not parsed then
return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."': "..tostring(err)
end
rockspec.dependencies[i] = parsed
end
else
rockspec.dependencies = {}
end
if not quick then
path.configure_paths(rockspec)
end
return rockspec
end
--- Load a local or remote rockspec into a table.
-- This is the entry point for the LuaRocks tools.
-- Only the LuaRocks runtime loader should use
-- load_local_rockspec directly.
-- @param filename string: Local or remote filename of a rockspec.
-- @param location string or nil: Where to download. If not given,
-- a temporary dir is created.
-- @return table or (nil, string, [string]): A table representing the rockspec
-- or nil followed by an error message and optional error code.
function fetch.load_rockspec(filename, location)
assert(type(filename) == "string")
local name
local basename = dir.base_name(filename)
if basename == "rockspec" then
name = "rockspec"
else
name = basename:match("(.*)%.rockspec")
if not name then
return nil, "Filename '"..filename.."' does not look like a rockspec."
end
end
local err, errcode
if location then
local ok, err = fs.change_dir(location)
if not ok then return nil, err end
filename, err = fetch.fetch_url(filename)
fs.pop_dir()
else
filename, err, errcode = fetch.fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name)
end
if not filename then
return nil, err, errcode
end
return fetch.load_local_rockspec(filename)
end
--- Download sources for building a rock using the basic URL downloader.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Whether to extract the sources from
-- the fetched source tarball or not.
-- @param dest_dir string or nil: If set, will extract to the given directory;
-- if not given, will extract to a temporary directory.
-- @return (string, string) or (nil, string, [string]): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message and optional error code.
function fetch.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(extract) == "boolean")
assert(type(dest_dir) == "string" or not dest_dir)
local url = rockspec.source.url
local name = rockspec.name.."-"..rockspec.version
local filename = rockspec.source.file
local source_file, store_dir
local ok, err, errcode
if dest_dir then
ok, err = fs.change_dir(dest_dir)
if not ok then return nil, err, "dest_dir" end
source_file, err, errcode = fetch.fetch_url(url, filename)
fs.pop_dir()
store_dir = dest_dir
else
source_file, store_dir, errcode = fetch.fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
end
if not source_file then
return nil, err or store_dir, errcode
end
if rockspec.source.md5 then
if not fs.check_md5(source_file, rockspec.source.md5) then
return nil, "MD5 check for "..filename.." has failed.", "md5"
end
end
if extract then
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
ok, err = fs.unpack_archive(rockspec.source.file)
if not ok then return nil, err end
if not fs.exists(rockspec.source.dir) then
return nil, "Directory "..rockspec.source.dir.." not found inside archive "..rockspec.source.file, "source.dir", source_file, store_dir
end
fs.pop_dir()
end
return source_file, store_dir
end
--- Download sources for building a rock, calling the appropriate protocol method.
-- @param rockspec table: The rockspec table
-- @param extract boolean: When downloading compressed formats, whether to extract
-- the sources from the fetched archive or not.
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- if not given, will extract to a temporary directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function fetch.fetch_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(extract) == "boolean")
assert(type(dest_dir) == "string" or not dest_dir)
local protocol = rockspec.source.protocol
local ok, proto
if fetch.is_basic_protocol(protocol) then
proto = fetch
else
ok, proto = pcall(require, "luarocks.fetch."..protocol:gsub("[+-]", "_"))
if not ok then
return nil, "Unknown protocol "..protocol
end
end
if cfg.only_sources_from
and rockspec.source.pathname
and #rockspec.source.pathname > 0 then
if #cfg.only_sources_from == 0 then
return nil, "Can't download "..rockspec.source.url.." -- download from remote servers disabled"
elseif rockspec.source.pathname:find(cfg.only_sources_from, 1, true) ~= 1 then
return nil, "Can't download "..rockspec.source.url.." -- only downloading from "..cfg.only_sources_from
end
end
return proto.get_sources(rockspec, extract, dest_dir)
end
return fetch

View File

@ -0,0 +1,55 @@
--- Fetch back-end for retrieving sources from CVS.
local cvs = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Download sources for building a rock, using CVS.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function cvs.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local cvs_cmd = rockspec.variables.CVS
local ok, err_msg = fs.is_tool_available(cvs_cmd, "CVS")
if not ok then
return nil, err_msg
end
local name_version = rockspec.name .. "-" .. rockspec.version
local module = rockspec.source.module or dir.base_name(rockspec.source.url)
local command = {cvs_cmd, "-d"..rockspec.source.pathname, "export", module}
if rockspec.source.tag then
table.insert(command, 4, "-r")
table.insert(command, 5, rockspec.source.tag)
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
if not fs.execute(unpack(command)) then
return nil, "Failed fetching files from CVS."
end
fs.pop_dir()
return module, store_dir
end
return cvs

View File

@ -0,0 +1,92 @@
--- Fetch back-end for retrieving sources from GIT.
local git = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Git >= 1.7.10 can clone a branch **or tag**, < 1.7.10 by branch only. We
-- need to know this in order to build the appropriate command; if we can't
-- clone by tag then we'll have to issue a subsequent command to check out the
-- given tag.
-- @return boolean: Whether Git can clone by tag.
local function git_can_clone_by_tag(git_cmd)
local version_string = io.popen(fs.Q(git_cmd)..' --version'):read()
local major, minor, tiny = version_string:match('(%d-)%.(%d+)%.?(%d*)')
major, minor, tiny = tonumber(major), tonumber(minor), tonumber(tiny) or 0
local value = major > 1 or (major == 1 and (minor > 7 or (minor == 7 and tiny >= 10)))
git_can_clone_by_tag = function() return value end
return value
end
--- Download sources for building a rock, using git.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function git.get_sources(rockspec, extract, dest_dir, depth)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local git_cmd = rockspec.variables.GIT
local name_version = rockspec.name .. "-" .. rockspec.version
local module = dir.base_name(rockspec.source.url)
-- Strip off .git from base name if present
module = module:gsub("%.git$", "")
local ok, err_msg = fs.is_tool_available(git_cmd, "Git")
if not ok then
return nil, err_msg
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
store_dir = fs.absolute_name(store_dir)
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
local command = {fs.Q(git_cmd), "clone", depth or "--depth=1", rockspec.source.url, module}
local tag_or_branch = rockspec.source.tag or rockspec.source.branch
-- If the tag or branch is explicitly set to "master" in the rockspec, then
-- we can avoid passing it to Git since it's the default.
if tag_or_branch == "master" then tag_or_branch = nil end
if tag_or_branch then
if git_can_clone_by_tag(git_cmd) then
-- The argument to `--branch` can actually be a branch or a tag as of
-- Git 1.7.10.
table.insert(command, 3, "--branch=" .. tag_or_branch)
end
end
if not fs.execute(unpack(command)) then
return nil, "Failed cloning git repository."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
if tag_or_branch and not git_can_clone_by_tag() then
local checkout_command = {fs.Q(git_cmd), "checkout", tag_or_branch}
if not fs.execute(unpack(checkout_command)) then
return nil, 'Failed to check out the "' .. tag_or_branch ..'" tag or branch.'
end
end
fs.delete(dir.path(store_dir, module, ".git"))
fs.delete(dir.path(store_dir, module, ".gitignore"))
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return git

View File

@ -0,0 +1,19 @@
--- Fetch back-end for retrieving sources from local Git repositories.
local git_file = {}
local git = require("luarocks.fetch.git")
--- Fetch sources for building a rock from a local Git repository.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function git_file.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^git.file://", "")
return git.get_sources(rockspec, extract, dest_dir)
end
return git_file

View File

@ -0,0 +1,26 @@
--- Fetch back-end for retrieving sources from Git repositories
-- that use http:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `git clone http://example.com/foo.git`
-- you can use this in the rockspec:
-- source = { url = "git+http://example.com/foo.git" }
-- Prefer using the normal git:// fetch mode as it is more widely
-- available in older versions of LuaRocks.
local git_http = {}
local git = require("luarocks.fetch.git")
--- Fetch sources for building a rock from a local Git repository.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function git_http.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^git.", "")
return git.get_sources(rockspec, extract, dest_dir, "--")
end
return git_http

View File

@ -0,0 +1,7 @@
--- Fetch back-end for retrieving sources from Git repositories
-- that use https:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `git clone https://example.com/foo.git`
-- you can use this in the rockspec:
-- source = { url = "git+https://example.com/foo.git" }
return require "luarocks.fetch.git_http"

View File

@ -0,0 +1,32 @@
--- Fetch back-end for retrieving sources from Git repositories
-- that use ssh:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `git clone ssh://git@example.com/path/foo.git
-- you can use this in the rockspec:
-- source = { url = "git+ssh://git@example.com/path/foo.git" }
-- It also handles scp-style ssh urls: git@example.com:path/foo.git,
-- but you have to prepend the "git+ssh://" and why not use the "newer"
-- style anyway?
local git_ssh = {}
local git = require("luarocks.fetch.git")
--- Fetch sources for building a rock from a local Git repository.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function git_ssh.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^git.", "")
-- Handle old-style scp-like git ssh urls
if rockspec.source.url:match("^ssh://[^/]+:[^%d]") then
rockspec.source.url = rockspec.source.url:gsub("^ssh://", "")
end
return git.get_sources(rockspec, extract, dest_dir, "--")
end
return git_ssh

View File

@ -0,0 +1,65 @@
--- Fetch back-end for retrieving sources from HG.
local hg = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Download sources for building a rock, using hg.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function hg.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local hg_cmd = rockspec.variables.HG
local ok, err_msg = fs.is_tool_available(hg_cmd, "Mercurial")
if not ok then
return nil, err_msg
end
local name_version = rockspec.name .. "-" .. rockspec.version
-- Strip off special hg:// protocol type
local url = rockspec.source.url:gsub("^hg://", "")
local module = dir.base_name(url)
local command = {hg_cmd, "clone", url, module}
local tag_or_branch = rockspec.source.tag or rockspec.source.branch
if tag_or_branch then
command = {hg_cmd, "clone", "--rev", tag_or_branch, url, module}
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
if not fs.execute(unpack(command)) then
return nil, "Failed cloning hg repository."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
fs.delete(dir.path(store_dir, module, ".hg"))
fs.delete(dir.path(store_dir, module, ".hgignore"))
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return hg

View File

@ -0,0 +1,24 @@
--- Fetch back-end for retrieving sources from hg repositories
-- that use http:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `hg clone http://example.com/foo`
-- you can use this in the rockspec:
-- source = { url = "hg+http://example.com/foo" }
local hg_http = {}
local hg = require("luarocks.fetch.hg")
--- Download sources for building a rock, using hg over http.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function hg_http.get_sources(rockspec, extract, dest_dir)
rockspec.source.url = rockspec.source.url:gsub("^hg.", "")
return hg.get_sources(rockspec, extract, dest_dir)
end
return hg_http

View File

@ -0,0 +1,8 @@
--- Fetch back-end for retrieving sources from hg repositories
-- that use https:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `hg clone https://example.com/foo`
-- you can use this in the rockspec:
-- source = { url = "hg+https://example.com/foo" }
return require "luarocks.fetch.hg_http"

View File

@ -0,0 +1,8 @@
--- Fetch back-end for retrieving sources from hg repositories
-- that use ssh:// transport. For example, for fetching a repository
-- that requires the following command line:
-- `hg clone ssh://example.com/foo`
-- you can use this in the rockspec:
-- source = { url = "hg+ssh://example.com/foo" }
return require "luarocks.fetch.hg_http"

View File

@ -0,0 +1,44 @@
--- Fetch back-end for retrieving sources from Surround SCM Server
local sscm = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
--- Download sources via Surround SCM Server for building a rock.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function sscm.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local sscm_cmd = rockspec.variables.SSCM
local module = rockspec.source.module or dir.base_name(rockspec.source.url)
local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)")
if not branch or not repository then
return nil, "Error retrieving branch and repository from rockspec."
end
-- Search for working directory.
local working_dir
local tmp = io.popen(string.format(sscm_cmd..[[ property "/" -d -b%s -p%s]], branch, repository))
for line in tmp:lines() do
--%c because a chr(13) comes in the end.
working_dir = string.match(line, "Working directory:[%s]*(.*)%c$")
if working_dir then break end
end
tmp:close()
if not working_dir then
return nil, "Error retrieving working directory from SSCM."
end
if not fs.execute(sscm_cmd, "get", "*", "-e" , "-r", "-b"..branch, "-p"..repository, "-tmodify", "-wreplace") then
return nil, "Failed fetching files from SSCM."
end
-- FIXME: This function does not honor the dest_dir parameter.
return module, working_dir
end
return sscm

View File

@ -0,0 +1,64 @@
--- Fetch back-end for retrieving sources from Subversion.
local svn = {}
local unpack = unpack or table.unpack
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Download sources for building a rock, using Subversion.
-- @param rockspec table: The rockspec table
-- @param extract boolean: Unused in this module (required for API purposes.)
-- @param dest_dir string or nil: If set, will extract to the given directory.
-- @return (string, string) or (nil, string): The absolute pathname of
-- the fetched source tarball and the temporary directory created to
-- store it; or nil and an error message.
function svn.get_sources(rockspec, extract, dest_dir)
assert(type(rockspec) == "table")
assert(type(dest_dir) == "string" or not dest_dir)
local svn_cmd = rockspec.variables.SVN
local ok, err_msg = fs.is_tool_available(svn_cmd, "--version", "Subversion")
if not ok then
return nil, err_msg
end
local name_version = rockspec.name .. "-" .. rockspec.version
local module = rockspec.source.module or dir.base_name(rockspec.source.url)
local url = rockspec.source.url:gsub("^svn://", "")
local command = {svn_cmd, "checkout", url, module}
if rockspec.source.tag then
table.insert(command, 5, "-r")
table.insert(command, 6, rockspec.source.tag)
end
local store_dir
if not dest_dir then
store_dir = fs.make_temp_dir(name_version)
if not store_dir then
return nil, "Failed creating temporary directory."
end
util.schedule_function(fs.delete, store_dir)
else
store_dir = dest_dir
end
local ok, err = fs.change_dir(store_dir)
if not ok then return nil, err end
if not fs.execute(unpack(command)) then
return nil, "Failed fetching files from Subversion."
end
ok, err = fs.change_dir(module)
if not ok then return nil, err end
for _, d in ipairs(fs.find(".")) do
if dir.base_name(d) == ".svn" then
fs.delete(dir.path(store_dir, module, d))
end
end
fs.pop_dir()
fs.pop_dir()
return module, store_dir
end
return svn

View File

@ -0,0 +1,76 @@
--- Proxy module for filesystem and platform abstractions.
-- All code using "fs" code should require "luarocks.fs",
-- and not the various platform-specific implementations.
-- However, see the documentation of the implementation
-- for the API reference.
local pairs = pairs
local fs = {}
package.loaded["luarocks.fs"] = fs
local cfg = require("luarocks.cfg")
local pack = table.pack or function(...) return { n = select("#", ...), ... } end
local unpack = table.unpack or unpack
local old_popen, old_exec
fs.verbose = function() -- patch io.popen and os.execute to display commands in verbose mode
if old_popen or old_exec then return end
old_popen = io.popen
io.popen = function(one, two)
if two == nil then
print("\nio.popen: ", one)
else
print("\nio.popen: ", one, "Mode:", two)
end
return old_popen(one, two)
end
old_exec = os.execute
os.execute = function(cmd)
-- redact api keys if present
print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap, key) return cap.."<redacted>/" end)) )
local code = pack(old_exec(cmd))
print("Results: "..tostring(code.n))
for i = 1,code.n do
print(" "..tostring(i).." ("..type(code[i]).."): "..tostring(code[i]))
end
return unpack(code, 1, code.n)
end
end
if cfg.verbose then fs.verbose() end
local function load_fns(fs_table)
for name, fn in pairs(fs_table) do
if not fs[name] then
fs[name] = fn
end
end
end
-- Load platform-specific functions
local loaded_platform = nil
for _, platform in ipairs(cfg.platforms) do
local ok, fs_plat = pcall(require, "luarocks.fs."..platform)
if ok and fs_plat then
loaded_platform = platform
load_fns(fs_plat)
break
end
end
-- Load platform-independent pure-Lua functionality
local fs_lua = require("luarocks.fs.lua")
load_fns(fs_lua)
-- Load platform-specific fallbacks for missing Lua modules
local ok, fs_plat_tools = pcall(require, "luarocks.fs."..loaded_platform..".tools")
if ok and fs_plat_tools then
load_fns(fs_plat_tools)
load_fns(require("luarocks.fs.tools"))
end
return fs

View File

@ -0,0 +1,873 @@
--- Native Lua implementation of filesystem and platform abstractions,
-- using LuaFileSystem, LZLib, MD5 and LuaCurl.
-- module("luarocks.fs.lua")
local fs_lua = {}
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local path = require("luarocks.path")
local socket_ok, zip_ok, unzip_ok, lfs_ok, md5_ok, posix_ok, _
local http, ftp, lrzip, luazip, lfs, md5, posix
if cfg.fs_use_modules then
socket_ok, http = pcall(require, "socket.http")
_, ftp = pcall(require, "socket.ftp")
zip_ok, lrzip = pcall(require, "luarocks.tools.zip")
unzip_ok, luazip = pcall(require, "zip"); _G.zip = nil
lfs_ok, lfs = pcall(require, "lfs")
md5_ok, md5 = pcall(require, "md5")
posix_ok, posix = pcall(require, "posix")
end
local patch = require("luarocks.tools.patch")
local dir_stack = {}
local dir_separator = "/"
--- Test is file/dir is writable.
-- Warning: testing if a file/dir is writable does not guarantee
-- that it will remain writable and therefore it is no replacement
-- for checking the result of subsequent operations.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function fs_lua.is_writable(file)
assert(file)
file = dir.normalize(file)
local result
if fs.is_dir(file) then
local file2 = dir.path(file, '.tmpluarockstestwritable')
local fh = io.open(file2, 'wb')
result = fh ~= nil
if fh then fh:close() end
os.remove(file2)
else
local fh = io.open(file, 'r+b')
result = fh ~= nil
if fh then fh:close() end
end
return result
end
local function quote_args(command, ...)
local out = { command }
for _, arg in ipairs({...}) do
assert(type(arg) == "string")
out[#out+1] = fs.Q(arg)
end
return table.concat(out, " ")
end
--- Run the given command, quoting its arguments.
-- The command is executed in the current directory in the dir stack.
-- @param command string: The command to be executed. No quoting/escaping
-- is applied.
-- @param ... Strings containing additional arguments, which are quoted.
-- @return boolean: true if command succeeds (status code 0), false
-- otherwise.
function fs_lua.execute(command, ...)
assert(type(command) == "string")
return fs.execute_string(quote_args(command, ...))
end
--- Run the given command, quoting its arguments, silencing its output.
-- The command is executed in the current directory in the dir stack.
-- Silencing is omitted if 'verbose' mode is enabled.
-- @param command string: The command to be executed. No quoting/escaping
-- is applied.
-- @param ... Strings containing additional arguments, which will be quoted.
-- @return boolean: true if command succeeds (status code 0), false
-- otherwise.
function fs_lua.execute_quiet(command, ...)
assert(type(command) == "string")
if cfg.verbose then -- omit silencing output
return fs.execute_string(quote_args(command, ...))
else
return fs.execute_string(fs.quiet(quote_args(command, ...)))
end
end
--- Checks if the given tool is available.
-- The tool is executed using a flag, usually just to ask its version.
-- @param tool_cmd string: The command to be used to check the tool's presence (e.g. hg in case of Mercurial)
-- @param tool_name string: The actual name of the tool (e.g. Mercurial)
-- @param arg string: The flag to pass to the tool. '--version' by default.
function fs_lua.is_tool_available(tool_cmd, tool_name, arg)
assert(type(tool_cmd) == "string")
assert(type(tool_name) == "string")
arg = arg or "--version"
assert(type(arg) == "string")
if not fs.execute_quiet(fs.Q(tool_cmd), arg) then
local msg = "'%s' program not found. Make sure %s is installed and is available in your PATH " ..
"(or you may want to edit the 'variables.%s' value in file '%s')"
return nil, msg:format(tool_cmd, tool_name, tool_name:upper(), cfg.which_config().nearest)
else
return true
end
end
--- Check the MD5 checksum for a file.
-- @param file string: The file to be checked.
-- @param md5sum string: The string with the expected MD5 checksum.
-- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false + msg if not
-- or if it could not perform the check for any reason.
function fs_lua.check_md5(file, md5sum)
file = dir.normalize(file)
local computed, msg = fs.get_md5(file)
if not computed then
return false, msg
end
if computed:match("^"..md5sum) then
return true
else
return false, "Mismatch MD5 hash for file "..file
end
end
--- List the contents of a directory.
-- @param at string or nil: directory to list (will be the current
-- directory if none is given).
-- @return table: an array of strings with the filenames representing
-- the contents of a directory.
function fs_lua.list_dir(at)
local result = {}
for file in fs.dir(at) do
result[#result+1] = file
end
return result
end
--- Iterate over the contents of a directory.
-- @param at string or nil: directory to list (will be the current
-- directory if none is given).
-- @return function: an iterator function suitable for use with
-- the for statement.
function fs_lua.dir(at)
if not at then
at = fs.current_dir()
end
at = dir.normalize(at)
if not fs.is_dir(at) then
return function() end
end
return coroutine.wrap(function() fs.dir_iterator(at) end)
end
---------------------------------------------------------------------
-- LuaFileSystem functions
---------------------------------------------------------------------
if lfs_ok then
--- Run the given command.
-- The command is executed in the current directory in the dir stack.
-- @param cmd string: No quoting/escaping is applied to the command.
-- @return boolean: true if command succeeds (status code 0), false
-- otherwise.
function fs_lua.execute_string(cmd)
local code = os.execute(cmd)
return (code == 0 or code == true)
end
--- Obtain current directory.
-- Uses the module's internal dir stack.
-- @return string: the absolute pathname of the current directory.
function fs_lua.current_dir()
return lfs.currentdir()
end
--- Change the current directory.
-- Uses the module's internal dir stack. This does not have exact
-- semantics of chdir, as it does not handle errors the same way,
-- but works well for our purposes for now.
-- @param d string: The directory to switch to.
function fs_lua.change_dir(d)
table.insert(dir_stack, lfs.currentdir())
d = dir.normalize(d)
return lfs.chdir(d)
end
--- Change directory to root.
-- Allows leaving a directory (e.g. for deleting it) in
-- a crossplatform way.
function fs_lua.change_dir_to_root()
local current = lfs.currentdir()
if not current or current == "" then
return false
end
table.insert(dir_stack, current)
lfs.chdir("/") -- works on Windows too
return true
end
--- Change working directory to the previous in the dir stack.
-- @return true if a pop ocurred, false if the stack was empty.
function fs_lua.pop_dir()
local d = table.remove(dir_stack)
if d then
lfs.chdir(d)
return true
else
return false
end
end
--- Create a directory if it does not already exist.
-- If any of the higher levels in the path name do not exist
-- too, they are created as well.
-- @param directory string: pathname of directory to create.
-- @return boolean or (boolean, string): true on success or (false, error message) on failure.
function fs_lua.make_dir(directory)
assert(type(directory) == "string")
directory = dir.normalize(directory)
local path = nil
if directory:sub(2, 2) == ":" then
path = directory:sub(1, 2)
directory = directory:sub(4)
else
if directory:match("^/") then
path = ""
end
end
for d in directory:gmatch("([^"..dir.separator.."]+)"..dir.separator.."*") do
path = path and path .. dir.separator .. d or d
local mode = lfs.attributes(path, "mode")
if not mode then
local ok, err = lfs.mkdir(path)
if not ok then
return false, err
end
ok, err = fs.chmod(path, cfg.perm_exec)
if not ok then
return false, err
end
elseif mode ~= "directory" then
return false, path.." is not a directory"
end
end
return true
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param d string: pathname of directory to remove.
function fs_lua.remove_dir_if_empty(d)
assert(d)
d = dir.normalize(d)
lfs.rmdir(d)
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param d string: pathname of directory to remove.
function fs_lua.remove_dir_tree_if_empty(d)
assert(d)
d = dir.normalize(d)
for i=1,10 do
lfs.rmdir(d)
d = dir.dir_name(d)
end
end
--- Copy a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Permissions for destination file,
-- or nil to use the source filename permissions
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.copy(src, dest, perms)
assert(src and dest)
src = dir.normalize(src)
dest = dir.normalize(dest)
local destmode = lfs.attributes(dest, "mode")
if destmode == "directory" then
dest = dir.path(dest, dir.base_name(src))
end
if not perms then perms = fs.get_permissions(src) end
local src_h, err = io.open(src, "rb")
if not src_h then return nil, err end
local dest_h, err = io.open(dest, "w+b")
if not dest_h then src_h:close() return nil, err end
while true do
local block = src_h:read(8192)
if not block then break end
dest_h:write(block)
end
src_h:close()
dest_h:close()
fs.chmod(dest, perms)
return true
end
--- Implementation function for recursive copy of directory contents.
-- Assumes paths are normalized.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Optional permissions.
-- If not given, permissions of the source are copied over to the destination.
-- @return boolean or (boolean, string): true on success, false on failure
local function recursive_copy(src, dest, perms)
local srcmode = lfs.attributes(src, "mode")
if srcmode == "file" then
local ok = fs.copy(src, dest, perms)
if not ok then return false end
elseif srcmode == "directory" then
local subdir = dir.path(dest, dir.base_name(src))
local ok, err = fs.make_dir(subdir)
if not ok then return nil, err end
for file in lfs.dir(src) do
if file ~= "." and file ~= ".." then
local ok = recursive_copy(dir.path(src, file), subdir, perms)
if not ok then return false end
end
end
end
return true
end
--- Recursively copy the contents of a directory.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Optional permissions.
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.copy_contents(src, dest, perms)
assert(src and dest)
src = dir.normalize(src)
dest = dir.normalize(dest)
assert(lfs.attributes(src, "mode") == "directory")
for file in lfs.dir(src) do
if file ~= "." and file ~= ".." then
local ok = recursive_copy(dir.path(src, file), dest, perms)
if not ok then
return false, "Failed copying "..src.." to "..dest
end
end
end
return true
end
--- Implementation function for recursive removal of directories.
-- Assumes paths are normalized.
-- @param name string: Pathname of file
-- @return boolean or (boolean, string): true on success,
-- or nil and an error message on failure.
local function recursive_delete(name)
local ok = os.remove(name)
if ok then return true end
local pok, ok, err = pcall(function()
for file in lfs.dir(name) do
if file ~= "." and file ~= ".." then
local ok, err = recursive_delete(dir.path(name, file))
if not ok then return nil, err end
end
end
local ok, err = lfs.rmdir(name)
return ok, (not ok) and err
end)
if pok then
return ok, err
else
return pok, ok
end
end
--- Delete a file or a directory and all its contents.
-- @param name string: Pathname of source
-- @return nil
function fs_lua.delete(name)
name = dir.normalize(name)
recursive_delete(name)
end
--- Internal implementation function for fs.dir.
-- Yields a filename on each iteration.
-- @param at string: directory to list
-- @return nil
function fs_lua.dir_iterator(at)
for file in lfs.dir(at) do
if file ~= "." and file ~= ".." then
coroutine.yield(file)
end
end
end
--- Implementation function for recursive find.
-- Assumes paths are normalized.
-- @param cwd string: Current working directory in recursion.
-- @param prefix string: Auxiliary prefix string to form pathname.
-- @param result table: Array of strings where results are collected.
local function recursive_find(cwd, prefix, result)
for file in lfs.dir(cwd) do
if file ~= "." and file ~= ".." then
local item = prefix .. file
table.insert(result, item)
local pathname = dir.path(cwd, file)
if lfs.attributes(pathname, "mode") == "directory" then
recursive_find(pathname, item..dir_separator, result)
end
end
end
end
--- Recursively scan the contents of a directory.
-- @param at string or nil: directory to scan (will be the current
-- directory if none is given).
-- @return table: an array of strings with the filenames representing
-- the contents of a directory.
function fs_lua.find(at)
assert(type(at) == "string" or not at)
if not at then
at = fs.current_dir()
end
at = dir.normalize(at)
if not fs.is_dir(at) then
return {}
end
local result = {}
recursive_find(at, "", result)
return result
end
--- Test for existance of a file.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function fs_lua.exists(file)
assert(file)
file = dir.normalize(file)
return type(lfs.attributes(file)) == "table"
end
--- Test is pathname is a directory.
-- @param file string: pathname to test
-- @return boolean: true if it is a directory, false otherwise.
function fs_lua.is_dir(file)
assert(file)
file = dir.normalize(file)
return lfs.attributes(file, "mode") == "directory"
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a file, false otherwise.
function fs_lua.is_file(file)
assert(file)
file = dir.normalize(file)
return lfs.attributes(file, "mode") == "file"
end
function fs_lua.set_time(file, time)
file = dir.normalize(file)
return lfs.touch(file, time)
end
end
---------------------------------------------------------------------
-- LuaZip functions
---------------------------------------------------------------------
if zip_ok then
function fs_lua.zip(zipfile, ...)
return lrzip.zip(zipfile, ...)
end
end
if unzip_ok then
--- Uncompress files from a .zip archive.
-- @param zipfile string: pathname of .zip archive to be extracted.
-- @return boolean: true on success, false on failure.
function fs_lua.unzip(zipfile)
local zipfile, err = luazip.open(zipfile)
if not zipfile then return nil, err end
local files = zipfile:files()
local file = files()
repeat
if file.filename:sub(#file.filename) == "/" then
local ok, err = fs.make_dir(dir.path(fs.current_dir(), file.filename))
if not ok then return nil, err end
else
local base = dir.dir_name(file.filename)
if base ~= "" then
base = dir.path(fs.current_dir(), base)
if not fs.is_dir(base) then
local ok, err = fs.make_dir(base)
if not ok then return nil, err end
end
end
local rf, err = zipfile:open(file.filename)
if not rf then zipfile:close(); return nil, err end
local contents = rf:read("*a")
rf:close()
local wf, err = io.open(dir.path(fs.current_dir(), file.filename), "wb")
if not wf then zipfile:close(); return nil, err end
wf:write(contents)
wf:close()
end
file = files()
until not file
zipfile:close()
return true
end
end
---------------------------------------------------------------------
-- LuaSocket functions
---------------------------------------------------------------------
if socket_ok then
local ltn12 = require("ltn12")
local luasec_ok, https = pcall(require, "ssl.https")
local redirect_protocols = {
http = http,
https = luasec_ok and https,
}
local function request(url, method, http, loop_control)
local result = {}
local proxy = cfg.http_proxy
if type(proxy) ~= "string" then proxy = nil end
-- LuaSocket's http.request crashes when given URLs missing the scheme part.
if proxy and not proxy:find("://") then
proxy = "http://" .. proxy
end
if cfg.show_downloads then
io.write(method.." "..url.." ...\n")
end
local dots = 0
if cfg.connection_timeout and cfg.connection_timeout > 0 then
http.TIMEOUT = cfg.connection_timeout
end
local res, status, headers, err = http.request {
url = url,
proxy = proxy,
method = method,
redirect = false,
sink = ltn12.sink.table(result),
step = cfg.show_downloads and function(...)
io.write(".")
io.flush()
dots = dots + 1
if dots == 70 then
io.write("\n")
dots = 0
end
return ltn12.pump.step(...)
end,
headers = {
["user-agent"] = cfg.user_agent.." via LuaSocket"
},
}
if cfg.show_downloads then
io.write("\n")
end
if not res then
return nil, status
elseif status == 301 or status == 302 then
local location = headers.location
if location then
local protocol, rest = dir.split_url(location)
if redirect_protocols[protocol] then
if not loop_control then
loop_control = {}
elseif loop_control[location] then
return nil, "Redirection loop -- broken URL?"
end
loop_control[url] = true
return request(location, method, redirect_protocols[protocol], loop_control)
else
return nil, "URL redirected to unsupported protocol - install luasec to get HTTPS support.", "https"
end
end
return nil, err
elseif status ~= 200 then
return nil, err
else
return result, status, headers, err
end
end
local function http_request(url, http, cached)
if cached then
local tsfd = io.open(cached..".timestamp", "r")
if tsfd then
local timestamp = tsfd:read("*a")
tsfd:close()
local result, status, headers, err = request(url, "HEAD", http)
if status == 200 and headers["last-modified"] == timestamp then
return true
end
if not result then
return nil, status, headers
end
end
end
local result, status, headers, err = request(url, "GET", http)
if result then
if cached and headers["last-modified"] then
local tsfd = io.open(cached..".timestamp", "w")
if tsfd then
tsfd:write(headers["last-modified"])
tsfd:close()
end
end
return table.concat(result)
else
return nil, status, headers
end
end
local downloader_warning = false
--- Download a remote file.
-- @param url string: URL to be fetched.
-- @param filename string or nil: this function attempts to detect the
-- resulting local filename of the remote file as the basename of the URL;
-- if that is not correct (due to a redirection, for example), the local
-- filename can be given explicitly as this second argument.
-- @return (boolean, string): true and the filename on success,
-- false and the error message on failure.
function fs_lua.download(url, filename, cache)
assert(type(url) == "string")
assert(type(filename) == "string" or not filename)
filename = fs.absolute_name(filename or dir.base_name(url))
-- delegate to the configured downloader so we don't have to deal with whitelists
if cfg.no_proxy then
return fs.use_downloader(url, filename, cache)
end
local content, err, https_err
if util.starts_with(url, "http:") then
content, err, https_err = http_request(url, http, cache and filename)
elseif util.starts_with(url, "ftp:") then
content, err = ftp.get(url)
elseif util.starts_with(url, "https:") then
-- skip LuaSec when proxy is enabled since it is not supported
if luasec_ok and not cfg.https_proxy then
content, err = http_request(url, https, cache and filename)
else
https_err = true
end
else
err = "Unsupported protocol"
end
if https_err then
if not downloader_warning then
util.printerr("Warning: falling back to "..cfg.downloader.." - install luasec to get native HTTPS support")
downloader_warning = true
end
return fs.use_downloader(url, filename, cache)
end
if cache and content == true then
return true, filename
end
if not content then
return false, tostring(err)
end
local file = io.open(filename, "wb")
if not file then return false end
file:write(content)
file:close()
return true, filename
end
else --...if socket_ok == false then
function fs_lua.download(url, filename, cache)
return fs.use_downloader(url, filename, cache)
end
end
---------------------------------------------------------------------
-- MD5 functions
---------------------------------------------------------------------
if md5_ok then
-- Support the interface of lmd5 by lhf in addition to md5 by Roberto
-- and the keplerproject.
if not md5.sumhexa and md5.digest then
md5.sumhexa = function(msg)
return md5.digest(msg)
end
end
--- Get the MD5 checksum for a file.
-- @param file string: The file to be computed.
-- @return string: The MD5 checksum or nil + error
function fs_lua.get_md5(file)
file = fs.absolute_name(file)
local file_handler = io.open(file, "rb")
if not file_handler then return nil, "Failed to open file for reading: "..file end
local computed = md5.sumhexa(file_handler:read("*a"))
file_handler:close()
if computed then return computed end
return nil, "Failed to compute MD5 hash for file "..file
end
end
---------------------------------------------------------------------
-- POSIX functions
---------------------------------------------------------------------
if posix_ok then
local octal_to_rwx = {
["0"] = "---",
["1"] = "--x",
["2"] = "-w-",
["3"] = "-wx",
["4"] = "r--",
["5"] = "r-x",
["6"] = "rw-",
["7"] = "rwx",
}
function fs_lua.chmod(file, mode)
-- LuaPosix (as of 5.1.15) does not support octal notation...
if mode:sub(1,1) == "0" then
local new_mode = {}
for c in mode:sub(-3):gmatch(".") do
table.insert(new_mode, octal_to_rwx[c])
end
mode = table.concat(new_mode)
end
local err = posix.chmod(file, mode)
return err == 0
end
function fs_lua.get_permissions(file)
return posix.stat(file, "mode")
end
--- Create a temporary directory.
-- @param name string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function fs_lua.make_temp_dir(name)
assert(type(name) == "string")
name = dir.normalize(name)
return posix.mkdtemp((os.getenv("TMPDIR") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-XXXXXX")
end
end
---------------------------------------------------------------------
-- Other functions
---------------------------------------------------------------------
--- Apply a patch.
-- @param patchname string: The filename of the patch.
-- @param patchdata string or nil: The actual patch as a string.
function fs_lua.apply_patch(patchname, patchdata)
local p, all_ok = patch.read_patch(patchname, patchdata)
if not all_ok then
return nil, "Failed reading patch "..patchname
end
if p then
return patch.apply_patch(p, 1)
end
end
--- Move a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perms string or nil: Permissions for destination file,
-- or nil to use the source filename permissions.
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.move(src, dest, perms)
assert(src and dest)
if fs.exists(dest) and not fs.is_dir(dest) then
return false, "File already exists: "..dest
end
local ok, err = fs.copy(src, dest, perms)
if not ok then
return false, err
end
fs.delete(src)
if fs.exists(src) then
return false, "Failed move: could not delete "..src.." after copy."
end
return true
end
--- Check if user has write permissions for the command.
-- Assumes the configuration variables under cfg have been previously set up.
-- @param flags table: the flags table passed to run() drivers.
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function fs_lua.check_command_permissions(flags)
local root_dir = path.root_dir(cfg.rocks_dir)
local ok = true
local err = ""
for _, dir in ipairs { cfg.rocks_dir, root_dir } do
if fs.exists(dir) and not fs.is_writable(dir) then
ok = false
err = "Your user does not have write permissions in " .. dir
break
end
end
if ok and not fs.exists(root_dir) then
local root = fs.root_of(root_dir)
local parent = root_dir
repeat
parent = dir.dir_name(parent)
if parent == "" then
parent = root
end
until parent == root or fs.exists(parent)
if not fs.is_writable(parent) then
ok = false
err = root_dir.." does not exist and your user does not have write permissions in " .. parent
end
end
if ok then
return true
else
if flags["local"] then
err = err .. " \n-- please check your permissions."
else
err = err .. " \n-- you may want to run as a privileged user or use your local tree with --local."
end
return nil, err
end
end
--- Check whether a file is a Lua script
-- When the file can be succesfully compiled by the configured
-- Lua interpreter, it's considered to be a valid Lua file.
-- @param name filename of file to check
-- @return boolean true, if it is a Lua script, false otherwise
function fs_lua.is_lua(name)
name = name:gsub([[%\]],"/") -- normalize on fw slash to prevent escaping issues
local lua = fs.Q(dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)) -- get lua interpreter configured
-- execute on configured interpreter, might not be the same as the interpreter LR is run on
local result = fs.execute_string(lua..[[ -e "if loadfile(']]..name..[[') then os.exit() else os.exit(1) end"]])
return (result == true)
end
return fs_lua

View File

@ -0,0 +1,156 @@
--- Common fs operations implemented with third-party tools.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local vars = cfg.variables
local dir_stack = {}
--- Obtain current directory.
-- Uses the module's internal directory stack.
-- @return string: the absolute pathname of the current directory.
function tools.current_dir()
local current = cfg.cache_pwd
if not current then
local pipe = io.popen(fs.quiet_stderr(fs.Q(vars.PWD)))
current = pipe:read("*l")
pipe:close()
cfg.cache_pwd = current
end
for _, directory in ipairs(dir_stack) do
current = fs.absolute_name(directory, current)
end
return current
end
--- Change the current directory.
-- Uses the module's internal directory stack. This does not have exact
-- semantics of chdir, as it does not handle errors the same way,
-- but works well for our purposes for now.
-- @param directory string: The directory to switch to.
-- @return boolean or (nil, string): true if successful, (nil, error message) if failed.
function tools.change_dir(directory)
assert(type(directory) == "string")
if fs.is_dir(directory) then
table.insert(dir_stack, directory)
return true
end
return nil, "directory not found: "..directory
end
--- Change directory to root.
-- Allows leaving a directory (e.g. for deleting it) in
-- a crossplatform way.
function tools.change_dir_to_root()
table.insert(dir_stack, "/")
end
--- Change working directory to the previous in the directory stack.
function tools.pop_dir()
local directory = table.remove(dir_stack)
return directory ~= nil
end
--- Run the given command.
-- The command is executed in the current directory in the directory stack.
-- @param cmd string: No quoting/escaping is applied to the command.
-- @return boolean: true if command succeeds (status code 0), false
-- otherwise.
function tools.execute_string(cmd)
local current = fs.current_dir()
if not current then return false end
cmd = fs.command_at(current, cmd)
local code = os.execute(cmd)
if code == 0 or code == true then
return true
else
return false
end
end
--- Internal implementation function for fs.dir.
-- Yields a filename on each iteration.
-- @param at string: directory to list
-- @return nil
function tools.dir_iterator(at)
local pipe = io.popen(fs.command_at(at, fs.Q(vars.LS)))
for file in pipe:lines() do
if file ~= "." and file ~= ".." then
coroutine.yield(file)
end
end
pipe:close()
end
--- Download a remote file.
-- @param url string: URL to be fetched.
-- @param filename string or nil: this function attempts to detect the
-- resulting local filename of the remote file as the basename of the URL;
-- if that is not correct (due to a redirection, for example), the local
-- filename can be given explicitly as this second argument.
-- @return (boolean, string): true and the filename on success,
-- false and the error message on failure.
function tools.use_downloader(url, filename, cache)
assert(type(url) == "string")
assert(type(filename) == "string" or not filename)
filename = fs.absolute_name(filename or dir.base_name(url))
local ok
if cfg.downloader == "wget" then
local wget_cmd = fs.Q(vars.WGET).." "..vars.WGETNOCERTFLAG.." --no-cache --user-agent=\""..cfg.user_agent.." via wget\" --quiet "
if cfg.connection_timeout and cfg.connection_timeout > 0 then
wget_cmd = wget_cmd .. "--timeout="..tonumber(cfg.connection_timeout).." --tries=1 "
end
if cache then
-- --timestamping is incompatible with --output-document,
-- but that's not a problem for our use cases.
fs.change_dir(dir.dir_name(filename))
ok = fs.execute_quiet(wget_cmd.." --timestamping ", url)
fs.pop_dir()
elseif filename then
ok = fs.execute_quiet(wget_cmd.." --output-document ", filename, url)
else
ok = fs.execute_quiet(wget_cmd, url)
end
elseif cfg.downloader == "curl" then
local curl_cmd = fs.Q(vars.CURL).." "..vars.CURLNOCERTFLAG.." -f -L --user-agent \""..cfg.user_agent.." via curl\" "
if cfg.connection_timeout and cfg.connection_timeout > 0 then
curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
end
ok = fs.execute_string(fs.quiet_stderr(curl_cmd..fs.Q(url).." > "..fs.Q(filename)))
end
if ok then
return true, filename
else
return false
end
end
local md5_cmd = {
md5sum = fs.Q(vars.MD5SUM),
openssl = fs.Q(vars.OPENSSL).." md5",
md5 = fs.Q(vars.MD5),
}
--- Get the MD5 checksum for a file.
-- @param file string: The file to be computed.
-- @return string: The MD5 checksum or nil + message
function tools.get_md5(file)
local cmd = md5_cmd[cfg.md5checker]
if not cmd then return nil, "no MD5 checker command configured" end
local pipe = io.popen(cmd.." "..fs.Q(fs.absolute_name(file)))
local computed = pipe:read("*a")
pipe:close()
if computed then
computed = computed:match("("..("%x"):rep(32)..")")
end
if computed then return computed end
return nil, "Failed to compute MD5 hash for file "..tostring(fs.absolute_name(file))
end
return tools

View File

@ -0,0 +1,135 @@
--- Unix implementation of filesystem and platform abstractions.
local unix = {}
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
--- Annotate command string for quiet execution.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with silencing annotation.
function unix.quiet(cmd)
return cmd.." 1> /dev/null 2> /dev/null"
end
--- Annotate command string for execution with quiet stderr.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with stderr silencing annotation.
function unix.quiet_stderr(cmd)
return cmd.." 2> /dev/null"
end
--- Quote argument for shell processing.
-- Adds single quotes and escapes.
-- @param arg string: Unquoted argument.
-- @return string: Quoted argument.
function unix.Q(arg)
assert(type(arg) == "string")
return "'" .. arg:gsub("'", "'\\''") .. "'"
end
--- Return an absolute pathname from a potentially relative one.
-- @param pathname string: pathname to convert.
-- @param relative_to string or nil: path to prepend when making
-- pathname absolute, or the current dir in the dir stack if
-- not given.
-- @return string: The pathname converted to absolute.
function unix.absolute_name(pathname, relative_to)
assert(type(pathname) == "string")
assert(type(relative_to) == "string" or not relative_to)
relative_to = relative_to or fs.current_dir()
if pathname:sub(1,1) == "/" then
return pathname
else
return relative_to .. "/" .. pathname
end
end
--- Return the root directory for the given path.
-- In Unix, root is always "/".
-- @param pathname string: pathname to use.
-- @return string: The root of the given pathname.
function unix.root_of(_)
return "/"
end
--- Create a wrapper to make a script executable from the command-line.
-- @param file string: Pathname of script to be made executable.
-- @param dest string: Directory where to put the wrapper.
-- @param name string: rock name to be used in loader context.
-- @param version string: rock version to be used in loader context.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function unix.wrap_script(file, dest, name, version)
assert(type(file) == "string")
assert(type(dest) == "string")
local base = dir.base_name(file)
local wrapname = fs.is_dir(dest) and dest.."/"..base or dest
local lpath, lcpath = cfg.package_paths()
local wrapper = io.open(wrapname, "w")
if not wrapper then
return nil, "Could not open "..wrapname.." for writing."
end
wrapper:write("#!/bin/sh\n\n")
local lua = dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)
local ppaths = "package.path="..util.LQ(lpath..";").."..package.path; package.cpath="..util.LQ(lcpath..";").."..package.cpath"
local addctx = "local k,l,_=pcall(require,"..util.LQ("luarocks.loader")..") _=k and l.add_context("..util.LQ(name)..","..util.LQ(version)..")"
wrapper:write('exec '..fs.Q(lua)..' -e '..fs.Q(ppaths)..' -e '..fs.Q(addctx)..' '..fs.Q(file)..' "$@"\n')
wrapper:close()
if fs.chmod(wrapname, cfg.perm_exec) then
return true
else
return nil, "Could not make "..wrapname.." executable."
end
end
--- Check if a file (typically inside path.bin_dir) is an actual binary
-- or a Lua wrapper.
-- @param filename string: the file name with full path.
-- @return boolean: returns true if file is an actual binary
-- (or if it couldn't check) or false if it is a Lua wrapper.
function unix.is_actual_binary(filename)
if filename:match("%.lua$") then
return false
end
local file = io.open(filename)
if not file then
return true
end
local first = file:read(2)
file:close()
if not first then
util.printerr("Warning: could not read "..filename)
return true
end
return first ~= "#!"
end
function unix.copy_binary(filename, dest)
return fs.copy(filename, dest, cfg.perm_exec)
end
--- Move a file on top of the other.
-- The new file ceases to exist under its original name,
-- and takes over the name of the old file.
-- On Unix this is done through a single rename operation.
-- @param old_file The name of the original file,
-- which will be the new name of new_file.
-- @param new_file The name of the new file,
-- which will replace old_file.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function unix.replace_file(old_file, new_file)
return os.rename(new_file, old_file)
end
function unix.tmpname()
return os.tmpname()
end
return unix

View File

@ -0,0 +1,237 @@
--- fs operations implemented with third-party tools for Unix platform abstractions.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local vars = cfg.variables
--- Adds prefix to command to make it run from a directory.
-- @param directory string: Path to a directory.
-- @param cmd string: A command-line string.
-- @return string: The command-line with prefix.
function tools.command_at(directory, cmd)
return "cd " .. fs.Q(fs.absolute_name(directory)) .. " && " .. cmd
end
--- Create a directory if it does not already exist.
-- If any of the higher levels in the path name does not exist
-- too, they are created as well.
-- @param directory string: pathname of directory to create.
-- @return boolean: true on success, false on failure.
function tools.make_dir(directory)
assert(directory)
local ok, err = fs.execute(vars.MKDIR.." -p", directory)
if not ok then
err = "failed making directory "..directory
end
return ok, err
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param directory string: pathname of directory to remove.
function tools.remove_dir_if_empty(directory)
assert(directory)
fs.execute_quiet(vars.RMDIR, directory)
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param directory string: pathname of directory to remove.
function tools.remove_dir_tree_if_empty(directory)
assert(directory)
fs.execute_quiet(vars.RMDIR, "-p", directory)
end
--- Copy a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @param perm string or nil: Permissions for destination file,
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy(src, dest, perm)
assert(src and dest)
if fs.execute(vars.CP, src, dest) then
if perm then
if fs.is_dir(dest) then
dest = dir.path(dest, dir.base_name(src))
end
if fs.chmod(dest, perm) then
return true
else
return false, "Failed setting permissions of "..dest
end
end
return true
else
return false, "Failed copying "..src.." to "..dest
end
end
--- Recursively copy the contents of a directory.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy_contents(src, dest)
assert(src and dest)
if fs.execute_quiet(vars.CP.." -pPR "..fs.Q(src).."/* "..fs.Q(dest)) then
return true
else
return false, "Failed copying "..src.." to "..dest
end
end
--- Delete a file or a directory and all its contents.
-- For safety, this only accepts absolute paths.
-- @param arg string: Pathname of source
-- @return nil
function tools.delete(arg)
assert(arg)
assert(arg:sub(1,1) == "/")
fs.execute_quiet(vars.RM, "-rf", arg)
end
--- Recursively scan the contents of a directory.
-- @param at string or nil: directory to scan (will be the current
-- directory if none is given).
-- @return table: an array of strings with the filenames representing
-- the contents of a directory.
function tools.find(at)
assert(type(at) == "string" or not at)
if not at then
at = fs.current_dir()
end
if not fs.is_dir(at) then
return {}
end
local result = {}
local pipe = io.popen(fs.command_at(at, fs.quiet_stderr(vars.FIND.." *")))
for file in pipe:lines() do
table.insert(result, file)
end
pipe:close()
return result
end
--- Compress files in a .zip archive.
-- @param zipfile string: pathname of .zip archive to be created.
-- @param ... Filenames to be stored in the archive are given as
-- additional arguments.
-- @return boolean: true on success, false on failure.
function tools.zip(zipfile, ...)
return fs.execute(vars.ZIP.." -r", zipfile, ...)
end
--- Uncompress files from a .zip archive.
-- @param zipfile string: pathname of .zip archive to be extracted.
-- @return boolean: true on success, false on failure.
function tools.unzip(zipfile)
assert(zipfile)
return fs.execute_quiet(vars.UNZIP, zipfile)
end
--- Test is file/directory exists
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function tools.exists(file)
assert(file)
return fs.execute(vars.TEST, "-e", file)
end
--- Test is pathname is a directory.
-- @param file string: pathname to test
-- @return boolean: true if it is a directory, false otherwise.
function tools.is_dir(file)
assert(file)
return fs.execute(vars.TEST, "-d", file)
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a regular file, false otherwise.
function tools.is_file(file)
assert(file)
return fs.execute(vars.TEST, "-f", file)
end
function tools.chmod(pathname, mode)
if mode then
return fs.execute(vars.CHMOD, mode, pathname)
else
return false
end
end
--- Unpack an archive.
-- Extract the contents of an archive, detecting its format by
-- filename extension.
-- @param archive string: Filename of archive.
-- @return boolean or (boolean, string): true on success, false and an error message on failure.
function tools.unpack_archive(archive)
assert(type(archive) == "string")
local pipe_to_tar = " | "..vars.TAR.." -xf -"
if not cfg.verbose then
pipe_to_tar = " 2> /dev/null"..fs.quiet(pipe_to_tar)
end
local ok
if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
ok = fs.execute_string(vars.GUNZIP.." -c "..fs.Q(archive)..pipe_to_tar)
elseif archive:match("%.tar%.bz2$") then
ok = fs.execute_string(vars.BUNZIP2.." -c "..fs.Q(archive)..pipe_to_tar)
elseif archive:match("%.zip$") then
ok = fs.execute_quiet(vars.UNZIP, archive)
elseif archive:match("%.lua$") or archive:match("%.c$") then
-- Ignore .lua and .c files; they don't need to be extracted.
return true
else
return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
end
if not ok then
return false, "Failed extracting "..archive
end
return true
end
function tools.get_permissions(filename)
local pipe = io.popen(vars.STAT.." "..vars.STATFLAG.." "..fs.Q(filename))
local ret = pipe:read("*l")
pipe:close()
return ret
end
function tools.browser(url)
return fs.execute(cfg.web_browser, url)
end
function tools.set_time(file, time)
file = dir.normalize(file)
return fs.execute(vars.TOUCH, "-d", "@"..tostring(time), file)
end
--- Create a temporary directory.
-- @param name string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function tools.make_temp_dir(name)
assert(type(name) == "string")
name = dir.normalize(name)
local template = (os.getenv("TMPDIR") or "/tmp") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-XXXXXX"
local pipe = io.popen(vars.MKTEMP.." -d "..fs.Q(template))
local dirname = pipe:read("*l")
pipe:close()
if dirname and dirname:match("^/") then
return dirname
end
return nil, "Failed to create temporary directory "..tostring(dirname)
end
return tools

View File

@ -0,0 +1,266 @@
--- Windows implementation of filesystem and platform abstractions.
-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
-- used by this module.
local win32 = {}
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
math.randomseed(os.time())
-- Monkey patch io.popen and os.execute to make sure quoting
-- works as expected.
-- See http://lua-users.org/lists/lua-l/2013-11/msg00367.html
local _prefix = "type NUL && "
local _popen, _execute = io.popen, os.execute
io.popen = function(cmd, ...) return _popen(_prefix..cmd, ...) end
os.execute = function(cmd, ...) return _execute(_prefix..cmd, ...) end
--- Annotate command string for quiet execution.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with silencing annotation.
function win32.quiet(cmd)
return cmd.." 2> NUL 1> NUL"
end
--- Annotate command string for execution with quiet stderr.
-- @param cmd string: A command-line string.
-- @return string: The command-line, with stderr silencing annotation.
function win32.quiet_stderr(cmd)
return cmd.." 2> NUL"
end
-- Split path into root and the rest.
-- Root part consists of an optional drive letter (e.g. "C:")
-- and an optional directory separator.
local function split_root(path)
local root = ""
if path:match("^.:") then
root = path:sub(1, 2)
path = path:sub(3)
end
if path:match("^[\\/]") then
root = path:sub(1, 1)
path = path:sub(2)
end
return root, path
end
--- Quote argument for shell processing. Fixes paths on Windows.
-- Adds double quotes and escapes.
-- @param arg string: Unquoted argument.
-- @return string: Quoted argument.
function win32.Q(arg)
assert(type(arg) == "string")
-- Use Windows-specific directory separator for paths.
-- Paths should be converted to absolute by now.
if split_root(arg) ~= "" then
arg = arg:gsub("/", "\\")
end
if arg == "\\" then
return '\\' -- CHDIR needs special handling for root dir
end
-- URLs and anything else
arg = arg:gsub('\\(\\*)"', '\\%1%1"')
arg = arg:gsub('\\+$', '%0%0')
arg = arg:gsub('"', '\\"')
arg = arg:gsub('(\\*)%%', '%1%1"%%"')
return '"' .. arg .. '"'
end
--- Quote argument for shell processing in batch files.
-- Adds double quotes and escapes.
-- @param arg string: Unquoted argument.
-- @return string: Quoted argument.
function win32.Qb(arg)
assert(type(arg) == "string")
-- Use Windows-specific directory separator for paths.
-- Paths should be converted to absolute by now.
if split_root(arg) ~= "" then
arg = arg:gsub("/", "\\")
end
if arg == "\\" then
return '\\' -- CHDIR needs special handling for root dir
end
-- URLs and anything else
arg = arg:gsub('\\(\\*)"', '\\%1%1"')
arg = arg:gsub('\\+$', '%0%0')
arg = arg:gsub('"', '\\"')
arg = arg:gsub('%%', '%%%%')
return '"' .. arg .. '"'
end
--- Return an absolute pathname from a potentially relative one.
-- @param pathname string: pathname to convert.
-- @param relative_to string or nil: path to prepend when making
-- pathname absolute, or the current dir in the dir stack if
-- not given.
-- @return string: The pathname converted to absolute.
function win32.absolute_name(pathname, relative_to)
assert(type(pathname) == "string")
assert(type(relative_to) == "string" or not relative_to)
relative_to = relative_to or fs.current_dir()
local root, rest = split_root(pathname)
if root:match("[\\/]$") then
-- It's an absolute path already.
return pathname
else
-- It's a relative path, join it with base path.
-- This drops drive letter from paths like "C:foo".
return relative_to .. "/" .. rest
end
end
--- Return the root directory for the given path.
-- For example, for "c:\hello", returns "c:\"
-- @param pathname string: pathname to use.
-- @return string: The root of the given pathname.
function win32.root_of(pathname)
return (split_root(fs.absolute_name(pathname)))
end
--- Create a wrapper to make a script executable from the command-line.
-- @param file string: Pathname of script to be made executable.
-- @param dest string: Directory where to put the wrapper.
-- @param name string: rock name to be used in loader context.
-- @param version string: rock version to be used in loader context.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function win32.wrap_script(file, dest, name, version)
assert(type(file) == "string")
assert(type(dest) == "string")
local base = dir.base_name(file)
local wrapname = fs.is_dir(dest) and dest.."/"..base or dest
wrapname = wrapname..".bat"
local lpath, lcpath = cfg.package_paths()
lpath = util.remove_path_dupes(lpath, ";")
lcpath = util.remove_path_dupes(lcpath, ";")
local wrapper = io.open(wrapname, "w")
if not wrapper then
return nil, "Could not open "..wrapname.." for writing."
end
wrapper:write("@echo off\n")
local lua = dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)
local ppaths = "package.path="..util.LQ(lpath..";").."..package.path; package.cpath="..util.LQ(lcpath..";").."..package.cpath"
local addctx = "local k,l,_=pcall(require,"..util.LQ("luarocks.loader")..") _=k and l.add_context("..util.LQ(name)..","..util.LQ(version)..")"
wrapper:write(fs.Qb(lua)..' -e '..fs.Qb(ppaths)..' -e '..fs.Qb(addctx)..' '..fs.Qb(file)..' %*\n')
wrapper:write("exit /b %ERRORLEVEL%\n")
wrapper:close()
return true
end
function win32.is_actual_binary(name)
name = name:lower()
if name:match("%.bat$") or name:match("%.exe$") then
return true
end
return false
end
function win32.copy_binary(filename, dest)
local ok, err = fs.copy(filename, dest)
if not ok then
return nil, err
end
local exe_pattern = "%.[Ee][Xx][Ee]$"
local base = dir.base_name(filename)
dest = dir.dir_name(dest)
if base:match(exe_pattern) then
base = base:gsub(exe_pattern, ".lua")
local helpname = dest.."/"..base
local helper = io.open(helpname, "w")
if not helper then
return nil, "Could not open "..helpname.." for writing."
end
helper:write('package.path=\"'..package.path:gsub("\\","\\\\")..';\"..package.path\n')
helper:write('package.cpath=\"'..package.path:gsub("\\","\\\\")..';\"..package.cpath\n')
helper:close()
end
return true
end
function win32.chmod(filename, mode)
return true
end
function win32.get_permissions(filename)
return ""
end
--- Move a file on top of the other.
-- The new file ceases to exist under its original name,
-- and takes over the name of the old file.
-- On Windows this is done by removing the original file and
-- renaming the new file to its original name.
-- @param old_file The name of the original file,
-- which will be the new name of new_file.
-- @param new_file The name of the new file,
-- which will replace old_file.
-- @return boolean or (nil, string): True if succeeded, or nil and
-- an error message.
function win32.replace_file(old_file, new_file)
os.remove(old_file)
return os.rename(new_file, old_file)
end
--- Test is file/dir is writable.
-- Warning: testing if a file/dir is writable does not guarantee
-- that it will remain writable and therefore it is no replacement
-- for checking the result of subsequent operations.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function win32.is_writable(file)
assert(file)
file = dir.normalize(file)
local result
local tmpname = 'tmpluarockstestwritable.deleteme'
if fs.is_dir(file) then
local file2 = dir.path(file, tmpname)
local fh = io.open(file2, 'wb')
result = fh ~= nil
if fh then fh:close() end
if result then
-- the above test might give a false positive when writing to
-- c:\program files\ because of VirtualStore redirection on Vista and up
-- So check whether it's really there
result = fs.exists(file2)
end
os.remove(file2)
else
local fh = io.open(file, 'r+b')
result = fh ~= nil
if fh then fh:close() end
end
return result
end
--- Create a temporary directory.
-- @param name string: name pattern to use for avoiding conflicts
-- when creating temporary directory.
-- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
function win32.make_temp_dir(name)
assert(type(name) == "string")
name = dir.normalize(name)
local temp_dir = os.getenv("TMP") .. "/luarocks_" .. name:gsub(dir.separator, "_") .. "-" .. tostring(math.floor(math.random() * 10000))
local ok, err = fs.make_dir(temp_dir)
if ok then
return temp_dir
else
return nil, err
end
end
function win32.tmpname()
return os.getenv("TMP")..os.tmpname()
end
return win32

View File

@ -0,0 +1,227 @@
--- fs operations implemented with third-party tools for Windows platform abstractions.
-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
-- used by this module.
local tools = {}
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local vars = cfg.variables
--- Adds prefix to command to make it run from a directory.
-- @param directory string: Path to a directory.
-- @param cmd string: A command-line string.
-- @return string: The command-line with prefix.
function tools.command_at(directory, cmd)
local drive = directory:match("^([A-Za-z]:)")
cmd = "cd " .. fs.Q(directory) .. " & " .. cmd
if drive then
cmd = drive .. " & " .. cmd
end
return cmd
end
--- Create a directory if it does not already exist.
-- If any of the higher levels in the path name does not exist
-- too, they are created as well.
-- @param directory string: pathname of directory to create.
-- @return boolean: true on success, false on failure.
function tools.make_dir(directory)
assert(directory)
directory = dir.normalize(directory)
fs.execute_quiet(fs.Q(vars.MKDIR).." -p ", directory)
if not fs.is_dir(directory) then
return false, "failed making directory "..directory
end
return true
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param directory string: pathname of directory to remove.
function tools.remove_dir_if_empty(directory)
assert(directory)
fs.execute_quiet(fs.Q(vars.RMDIR), directory)
end
--- Remove a directory if it is empty.
-- Does not return errors (for example, if directory is not empty or
-- if already does not exist)
-- @param directory string: pathname of directory to remove.
function tools.remove_dir_tree_if_empty(directory)
assert(directory)
fs.execute_quiet(fs.Q(vars.RMDIR), directory)
end
--- Copy a file.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy(src, dest)
assert(src and dest)
if dest:match("[/\\]$") then dest = dest:sub(1, -2) end
local ok = fs.execute(fs.Q(vars.CP), src, dest)
if ok then
return true
else
return false, "Failed copying "..src.." to "..dest
end
end
--- Recursively copy the contents of a directory.
-- @param src string: Pathname of source
-- @param dest string: Pathname of destination
-- @return boolean or (boolean, string): true on success, false on failure,
-- plus an error message.
function tools.copy_contents(src, dest)
assert(src and dest)
if fs.execute_quiet(fs.Q(vars.CP), "-dR", src.."\\*.*", dest) then
return true
else
return false, "Failed copying "..src.." to "..dest
end
end
--- Delete a file or a directory and all its contents.
-- For safety, this only accepts absolute paths.
-- @param arg string: Pathname of source
-- @return nil
function tools.delete(arg)
assert(arg)
assert(arg:match("^[a-zA-Z]?:?[\\/]"))
fs.execute_quiet("if exist "..fs.Q(arg.."\\").." ( RMDIR /S /Q "..fs.Q(arg).." ) else ( DEL /Q /F "..fs.Q(arg).." )")
end
--- Recursively scan the contents of a directory.
-- @param at string or nil: directory to scan (will be the current
-- directory if none is given).
-- @return table: an array of strings with the filenames representing
-- the contents of a directory. Paths are returned with forward slashes.
function tools.find(at)
assert(type(at) == "string" or not at)
if not at then
at = fs.current_dir()
end
if not fs.is_dir(at) then
return {}
end
local result = {}
local pipe = io.popen(fs.command_at(at, fs.quiet_stderr(fs.Q(vars.FIND))))
for file in pipe:lines() do
-- Windows find is a bit different
local first_two = file:sub(1,2)
if first_two == ".\\" or first_two == "./" then file=file:sub(3) end
if file ~= "." then
table.insert(result, (file:gsub("\\", "/")))
end
end
pipe:close()
return result
end
--- Compress files in a .zip archive.
-- @param zipfile string: pathname of .zip archive to be created.
-- @param ... Filenames to be stored in the archive are given as
-- additional arguments.
-- @return boolean: true on success, false on failure.
function tools.zip(zipfile, ...)
return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa a -tzip", zipfile, ...)
end
--- Uncompress files from a .zip archive.
-- @param zipfile string: pathname of .zip archive to be extracted.
-- @return boolean: true on success, false on failure.
function tools.unzip(zipfile)
assert(zipfile)
return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", zipfile)
end
--- Test is pathname is a directory.
-- @param file string: pathname to test
-- @return boolean: true if it is a directory, false otherwise.
function tools.is_dir(file)
assert(file)
return fs.execute_quiet("if not exist " .. fs.Q(file.."\\").." invalidcommandname")
end
--- Test is pathname is a regular file.
-- @param file string: pathname to test
-- @return boolean: true if it is a regular file, false otherwise.
function tools.is_file(file)
assert(file)
return fs.execute(fs.Q(vars.TEST).." -f", file)
end
--- Strip the last extension of a filename.
-- Example: "foo.tar.gz" becomes "foo.tar".
-- If filename has no dots, returns it unchanged.
-- @param filename string: The file name to strip.
-- @return string: The stripped name.
local function strip_extension(filename)
assert(type(filename) == "string")
return (filename:gsub("%.[^.]+$", "")) or filename
end
--- Uncompress gzip file.
-- @param archive string: Filename of archive.
-- @return boolean : success status
local function gunzip(archive)
return fs.execute_quiet(fs.Q(vars.SEVENZ).." -aoa x", archive)
end
--- Unpack an archive.
-- Extract the contents of an archive, detecting its format by
-- filename extension.
-- @param archive string: Filename of archive.
-- @return boolean or (boolean, string): true on success, false and an error message on failure.
function tools.unpack_archive(archive)
assert(type(archive) == "string")
local ok
local sevenzx = fs.Q(vars.SEVENZ).." -aoa x"
if archive:match("%.tar%.gz$") then
ok = gunzip(archive)
if ok then
ok = fs.execute_quiet(sevenzx, strip_extension(archive))
end
elseif archive:match("%.tgz$") then
ok = gunzip(archive)
if ok then
ok = fs.execute_quiet(sevenzx, strip_extension(archive)..".tar")
end
elseif archive:match("%.tar%.bz2$") then
ok = fs.execute_quiet(sevenzx, archive)
if ok then
ok = fs.execute_quiet(sevenzx, strip_extension(archive))
end
elseif archive:match("%.zip$") then
ok = fs.execute_quiet(sevenzx, archive)
elseif archive:match("%.lua$") or archive:match("%.c$") then
-- Ignore .lua and .c files; they don't need to be extracted.
return true
else
return false, "Couldn't extract archive "..archive..": unrecognized filename extension"
end
if not ok then
return false, "Failed extracting "..archive
end
return true
end
--- Test for existance of a file.
-- @param file string: filename to test
-- @return boolean: true if file exists, false otherwise.
function tools.exists(file)
assert(file)
return fs.execute_quiet("if not exist " .. fs.Q(file) .. " invalidcommandname")
end
function tools.browser(url)
return fs.execute(cfg.web_browser..' "Starting docs..." '..fs.Q(url))
end
return tools

View File

@ -0,0 +1,118 @@
--- Module implementing the LuaRocks "help" command.
-- This is a generic help display module, which
-- uses a global table called "commands" to find commands
-- to show help for; each command should be represented by a
-- table containing "help" and "help_summary" fields.
local help = {}
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local program = util.this_program("luarocks")
util.add_run_function(help)
help.help_summary = "Help on commands. Type '"..program.." help <command>' for more."
help.help_arguments = "[<command>]"
help.help = [[
<command> is the command to show help for.
]]
local function print_banner()
util.printout("\nLuaRocks "..cfg.program_version..", a module deployment system for Lua")
end
local function print_section(section)
util.printout("\n"..section)
end
local function get_status(status)
if status then
return "ok"
else
return "not found"
end
end
--- Driver function for the "help" command.
-- @param command string or nil: command to show help for; if not
-- given, help summaries for all commands are shown.
-- @return boolean or (nil, string): true if there were no errors
-- or nil and an error message if an invalid command was requested.
function help.command(flags, command)
if not command then
local conf = cfg.which_config()
print_banner()
print_section("NAME")
util.printout("\t"..program..[[ - ]]..program_description)
print_section("SYNOPSIS")
util.printout("\t"..program..[[ [--from=<server> | --only-from=<server>] [--to=<tree>] [VAR=VALUE]... <command> [<argument>] ]])
print_section("GENERAL OPTIONS")
util.printout([[
These apply to all commands, as appropriate:
--server=<server> Fetch rocks/rockspecs from this server
(takes priority over config file)
--only-server=<server> Fetch rocks/rockspecs from this server only
(overrides any entries in the config file)
--only-sources=<url> Restrict downloads to paths matching the
given URL.
--tree=<tree> Which tree to operate on.
--local Use the tree in the user's home directory.
To enable it, see ']]..program..[[ help path'.
--verbose Display verbose output of commands executed.
--timeout=<seconds> Timeout on network operations, in seconds.
0 means no timeout (wait forever).
Default is ]]..tostring(cfg.connection_timeout)..[[.]])
print_section("VARIABLES")
util.printout([[
Variables from the "variables" table of the configuration file
can be overriden with VAR=VALUE assignments.]])
print_section("COMMANDS")
for name, command in util.sortedpairs(commands) do
local cmd = require(command)
util.printout("", name)
util.printout("\t", cmd.help_summary)
end
print_section("CONFIGURATION")
util.printout("\tLua version: " .. cfg.lua_version)
util.printout("\tConfiguration files:")
util.printout("\t\tSystem: ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.ok) ..")")
if conf.user.file then
util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.ok) ..")\n")
else
util.printout("\t\tUser : disabled in this LuaRocks installation.\n")
end
util.printout("\tRocks trees in use: ")
for _, tree in ipairs(cfg.rocks_trees) do
if type(tree) == "string" then
util.printout("\t\t"..dir.normalize(tree))
else
local name = tree.name and " (\""..tree.name.."\")" or ""
util.printout("\t\t"..dir.normalize(tree.root)..name)
end
end
else
command = command:gsub("-", "_")
local cmd = commands[command] and require(commands[command])
if cmd then
local arguments = cmd.help_arguments or "<argument>"
print_banner()
print_section("NAME")
util.printout("\t"..program.." "..command.." - "..cmd.help_summary)
print_section("SYNOPSIS")
util.printout("\t"..program.." "..command.." "..arguments)
print_section("DESCRIPTION")
util.printout("",(cmd.help:gsub("\n","\n\t"):gsub("\n\t$","")))
print_section("SEE ALSO")
util.printout("","'"..program.." help' for general options and configuration.\n")
else
return nil, "Unknown command: "..command
end
end
return true
end
return help

View File

@ -0,0 +1,186 @@
--- Module which builds the index.html page to be used in rocks servers.
local index = {}
package.loaded["luarocks.index"] = index
local util = require("luarocks.util")
local fs = require("luarocks.fs")
local deps = require("luarocks.deps")
local persist = require("luarocks.persist")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local ext_url_target = ' target="_blank"'
local index_header = [[
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Available rocks</title>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
<style>
body {
background-color: white;
font-family: "bitstream vera sans", "verdana", "sans";
font-size: 14px;
}
a {
color: #0000c0;
text-decoration: none;
}
a.pkg {
color: black;
}
a:hover {
text-decoration: underline;
}
td.main {
border-style: none;
}
blockquote {
font-size: 12px;
}
td.package {
background-color: #f0f0f0;
vertical-align: top;
}
td.spacer {
height: 5px;
}
td.version {
background-color: #d0d0d0;
vertical-align: top;
text-align: left;
padding: 5px;
width: 100px;
}
p.manifest {
font-size: 8px;
}
</style>
</head>
<body>
<h1>Available rocks</h1>
<p>
Lua modules available from this location for use with <a href="http://www.luarocks.org">LuaRocks</a>:
</p>
<table class="main">
]]
local index_package_begin = [[
<td class="package">
<p><a name="$anchor"></a><a href="#$anchor" class="pkg"><b>$package</b></a> - $summary<br/>
</p><blockquote><p>$detailed<br/>
$externaldependencies
<font size="-1"><a href="$original">latest sources</a> $homepage | License: $license</font></p>
</blockquote></a></td>
<td class="version">
]]
local index_package_end = [[
</td></tr>
<tr><td colspan="2" class="spacer"></td></tr>
]]
local index_footer_begin = [[
</table>
<p class="manifest">
<a href="manifest">manifest file</a>
]]
local index_manifest_ver = [[
&bull; <a href="manifest-$VER">Lua $VER manifest file</a> (<a href="manifest-$VER.zip">zip</a>)
]]
local index_footer_end = [[
</p>
</body>
</html>
]]
function index.format_external_dependencies(rockspec)
if rockspec.external_dependencies then
local deplist = {}
local listed_set = {}
local plats = nil
for name, desc in util.sortedpairs(rockspec.external_dependencies) do
if name ~= "platforms" then
table.insert(deplist, name:lower())
listed_set[name] = true
else
plats = desc
end
end
if plats then
for plat, entries in util.sortedpairs(plats) do
for name, desc in util.sortedpairs(entries) do
if not listed_set[name] then
table.insert(deplist, name:lower() .. " (on "..plat..")")
end
end
end
end
return '<p><b>External dependencies:</b> ' .. table.concat(deplist, ',&nbsp;').. '</p>'
else
return ""
end
end
function index.make_index(repo)
if not fs.is_dir(repo) then
return nil, "Cannot access repository at "..repo
end
local manifest = manif.load_manifest(repo)
local out = io.open(dir.path(repo, "index.html"), "w")
out:write(index_header)
for package, version_list in util.sortedpairs(manifest.repository) do
local latest_rockspec = nil
local output = index_package_begin
for version, data in util.sortedpairs(version_list, deps.compare_versions) do
local versions = {}
output = output..version..':&nbsp;'
table.sort(data, function(a,b) return a.arch < b.arch end)
for _, item in ipairs(data) do
local file
if item.arch == 'rockspec' then
file = ("%s-%s.rockspec"):format(package, version)
if not latest_rockspec then latest_rockspec = file end
else
file = ("%s-%s.%s.rock"):format(package, version, item.arch)
end
table.insert(versions, '<a href="'..file..'">'..item.arch..'</a>')
end
output = output .. table.concat(versions, ',&nbsp;') .. '<br/>'
end
output = output .. index_package_end
if latest_rockspec then
local rockspec = persist.load_into_table(dir.path(repo, latest_rockspec))
local descript = rockspec.description or {}
local vars = {
anchor = package,
package = rockspec.package,
original = rockspec.source.url,
summary = descript.summary or "",
detailed = descript.detailed or "",
license = descript.license or "N/A",
homepage = descript.homepage and ('| <a href="'..descript.homepage..'"'..ext_url_target..'>project homepage</a>') or "",
externaldependencies = index.format_external_dependencies(rockspec)
}
vars.detailed = vars.detailed:gsub("\n\n", "</p><p>"):gsub("%s+", " ")
vars.detailed = vars.detailed:gsub("(https?://[a-zA-Z0-9%.%%-_%+%[%]=%?&/$@;:]+)", '<a href="%1"'..ext_url_target..'>%1</a>')
output = output:gsub("$(%w+)", vars)
else
output = output:gsub("$anchor", package)
output = output:gsub("$package", package)
output = output:gsub("$(%w+)", "")
end
out:write(output)
end
out:write(index_footer_begin)
for ver in util.lua_versions() do
out:write((index_manifest_ver:gsub("$VER", ver)))
end
out:write(index_footer_end)
out:close()
end
return index

View File

@ -0,0 +1,188 @@
--- Module implementing the LuaRocks "install" command.
-- Installs binary rocks.
local install = {}
package.loaded["luarocks.install"] = install
local path = require("luarocks.path")
local repos = require("luarocks.repos")
local fetch = require("luarocks.fetch")
local util = require("luarocks.util")
local fs = require("luarocks.fs")
local deps = require("luarocks.deps")
local manif = require("luarocks.manif")
local remove = require("luarocks.remove")
local cfg = require("luarocks.cfg")
util.add_run_function(install)
install.help_summary = "Install a rock."
install.help_arguments = "{<rock>|<name> [<version>]}"
install.help = [[
Argument may be the name of a rock to be fetched from a repository
or a filename of a locally available rock.
--keep Do not remove previously installed versions of the
rock after installing a new one. This behavior can
be made permanent by setting keep_other_versions=true
in the configuration file.
--only-deps Installs only the dependencies of the rock.
]]..util.deps_mode_help()
--- Install a binary rock.
-- @param rock_file string: local or remote filename of a rock.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @return (string, string) or (nil, string, [string]): Name and version of
-- installed rock if succeeded or nil and an error message followed by an error code.
function install.install_binary_rock(rock_file, deps_mode)
assert(type(rock_file) == "string")
local name, version, arch = path.parse_name(rock_file)
if not name then
return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
end
if arch ~= "all" and arch ~= cfg.arch then
return nil, "Incompatible architecture "..arch, "arch"
end
if repos.is_installed(name, version) then
repos.delete_version(name, version, deps_mode)
end
local rollback = util.schedule_function(function()
fs.delete(path.install_dir(name, version))
fs.remove_dir_if_empty(path.versions_dir(name))
end)
local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
if not ok then return nil, err, errcode end
local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version))
if err then
return nil, "Failed loading rockspec for installed package: "..err, errcode
end
if deps_mode == "none" then
util.printerr("Warning: skipping dependency checks.")
else
ok, err, errcode = deps.check_external_deps(rockspec, "install")
if err then return nil, err, errcode end
end
-- For compatibility with .rock files built with LuaRocks 1
if not fs.exists(path.rock_manifest_file(name, version)) then
ok, err = manif.make_rock_manifest(name, version)
if err then return nil, err end
end
if deps_mode ~= "none" then
ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
if err then return nil, err, errcode end
end
ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode)
if err then return nil, err end
util.remove_scheduled_function(rollback)
rollback = util.schedule_function(function()
repos.delete_version(name, version, deps_mode)
end)
ok, err = repos.run_hook(rockspec, "post_install")
if err then return nil, err end
util.announce_install(rockspec)
util.remove_scheduled_function(rollback)
return name, version
end
--- Installs the dependencies of a binary rock.
-- @param rock_file string: local or remote filename of a rock.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @return (string, string) or (nil, string, [string]): Name and version of
-- the rock whose dependencies were installed if succeeded or nil and an error message
-- followed by an error code.
function install.install_binary_rock_deps(rock_file, deps_mode)
assert(type(rock_file) == "string")
local name, version, arch = path.parse_name(rock_file)
if not name then
return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
end
if arch ~= "all" and arch ~= cfg.arch then
return nil, "Incompatible architecture "..arch, "arch"
end
local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
if not ok then return nil, err, errcode end
local rockspec, err, errcode = fetch.load_rockspec(path.rockspec_file(name, version))
if err then
return nil, "Failed loading rockspec for installed package: "..err, errcode
end
ok, err, errcode = deps.fulfill_dependencies(rockspec, deps_mode)
if err then return nil, err, errcode end
util.printout()
util.printout("Successfully installed dependencies for " ..name.." "..version)
return name, version
end
--- Driver function for the "install" command.
-- @param name string: name of a binary rock. If an URL or pathname
-- to a binary rock is given, fetches and installs it. If a rockspec or a
-- source rock is given, forwards the request to the "build" command.
-- If a package name is given, forwards the request to "search" and,
-- if returned a result, installs the matching rock.
-- @param version string: When passing a package name, a version number
-- may also be given.
-- @return boolean or (nil, string, exitcode): True if installation was
-- successful, nil and an error message otherwise. exitcode is optionally returned.
function install.command(flags, name, version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("install")
end
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
if name:match("%.rockspec$") or name:match("%.src%.rock$") then
local build = require("luarocks.build")
return build.command(flags, name)
elseif name:match("%.rock$") then
if flags["only-deps"] then
ok, err = install.install_binary_rock_deps(name, deps.get_deps_mode(flags))
else
ok, err = install.install_binary_rock(name, deps.get_deps_mode(flags))
end
if not ok then return nil, err end
name, version = ok, err
if (not flags["only-deps"]) and (not flags["keep"]) and not cfg.keep_other_versions then
local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"])
if not ok then util.printerr(err) end
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return name, version
else
local search = require("luarocks.search")
local url, err = search.find_suitable_rock(search.make_query(name:lower(), version))
if not url then
return nil, err
end
util.printout("Installing "..url)
return install.command(flags, url)
end
end
return install

View File

@ -0,0 +1,55 @@
--- Module implementing the LuaRocks "lint" command.
-- Utility function that checks syntax of the rockspec.
local lint = {}
package.loaded["luarocks.lint"] = lint
local util = require("luarocks.util")
local download = require("luarocks.download")
local fetch = require("luarocks.fetch")
util.add_run_function(lint)
lint.help_summary = "Check syntax of a rockspec."
lint.help_arguments = "<rockspec>"
lint.help = [[
This is a utility function that checks the syntax of a rockspec.
It returns success or failure if the text of a rockspec is
syntactically correct.
]]
function lint.command(flags, input)
if not input then
return nil, "Argument missing. "..util.see_help("lint")
end
local filename = input
if not input:match(".rockspec$") then
local err
filename, err = download.download("rockspec", input:lower())
if not filename then
return nil, err
end
end
local rs, err = fetch.load_local_rockspec(filename)
if not rs then
return nil, "Failed loading rockspec: "..err
end
local ok = true
-- This should have been done in the type checker,
-- but it would break compatibility of other commands.
-- Making 'lint' alone be stricter shouldn't be a problem,
-- because extra-strict checks is what lint-type commands
-- are all about.
if not rs.description.license then
util.printerr("Rockspec has no license field.")
ok = false
end
return ok, ok or filename.." failed consistency checks."
end
return lint

View File

@ -0,0 +1,97 @@
--- Module implementing the LuaRocks "list" command.
-- Lists currently installed rocks.
local list = {}
package.loaded["luarocks.list"] = list
local search = require("luarocks.search")
local deps = require("luarocks.deps")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local path = require("luarocks.path")
util.add_run_function(list)
list.help_summary = "List currently installed rocks."
list.help_arguments = "[--porcelain] <filter>"
list.help = [[
<filter> is a substring of a rock name to filter by.
--outdated List only rocks for which there is a
higher version available in the rocks server.
--porcelain Produce machine-friendly output.
]]
local function check_outdated(trees, query)
local results_installed = {}
for _, tree in ipairs(trees) do
search.manifest_search(results_installed, path.rocks_dir(tree), query)
end
local outdated = {}
for name, versions in util.sortedpairs(results_installed) do
versions = util.keys(versions)
table.sort(versions, deps.compare_versions)
local latest_installed = versions[1]
local query_available = search.make_query(name:lower())
query.exact_name = true
local results_available, err = search.search_repos(query_available)
if results_available[name] then
local available_versions = util.keys(results_available[name])
table.sort(available_versions, deps.compare_versions)
local latest_available = available_versions[1]
local latest_available_repo = results_available[name][latest_available][1].repo
if deps.compare_versions(latest_available, latest_installed) then
table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo })
end
end
end
return outdated
end
local function list_outdated(trees, query, porcelain)
util.title("Outdated rocks:", porcelain)
local outdated = check_outdated(trees, query)
for _, item in ipairs(outdated) do
if porcelain then
util.printout(item.name, item.installed, item.available, item.repo)
else
util.printout(item.name)
util.printout(" "..item.installed.." < "..item.available.." at "..item.repo)
util.printout()
end
end
return true
end
--- Driver function for "list" command.
-- @param filter string or nil: A substring of a rock name to filter by.
-- @param version string or nil: a version may also be passed.
-- @return boolean: True if succeeded, nil on errors.
function list.command(flags, filter, version)
local query = search.make_query(filter and filter:lower() or "", version)
query.exact_name = false
local trees = cfg.rocks_trees
if flags["tree"] then
trees = { flags["tree"] }
end
if flags["outdated"] then
return list_outdated(trees, query, flags["porcelain"])
end
local results = {}
for _, tree in ipairs(trees) do
local ok, err, errcode = search.manifest_search(results, path.rocks_dir(tree), query)
if not ok and errcode ~= "open" then
util.warning(err)
end
end
util.title("Installed rocks:", flags["porcelain"])
search.print_results(results, flags["porcelain"])
return true
end
return list

View File

@ -0,0 +1,249 @@
--- A module which installs a Lua package loader that is LuaRocks-aware.
-- This loader uses dependency information from the LuaRocks tree to load
-- correct versions of modules. It does this by constructing a "context"
-- table in the environment, which records which versions of packages were
-- used to load previous modules, so that the loader chooses versions
-- that are declared to be compatible with the ones loaded earlier.
local loaders = package.loaders or package.searchers
local package, require, ipairs, table, type, next, tostring, error =
package, require, ipairs, table, type, next, tostring, error
local unpack = unpack or table.unpack
--module("luarocks.loader")
local loader = {}
package.loaded["luarocks.loader"] = loader
local cfg = require("luarocks.cfg")
cfg.init_package_paths()
local path = require("luarocks.path")
local manif_core = require("luarocks.manif_core")
local deps = require("luarocks.deps")
local util = require("luarocks.util")
-- Workaround for wrappers produced by older versions of LuaRocks
local temporary_global = false
if luarocks then
-- The site_config.lua file generated by old versions uses module(),
-- so it produces a global `luarocks` table. Since we have the table,
-- add the `loader` field to make the old wrappers happy.
luarocks.loader = loader
else
-- When a new version is installed on top of an old version,
-- site_config.lua may be replaced, and then it no longer creates
-- a global.
-- Detect when being called via -lluarocks.loader; this is
-- most likely a wrapper.
local info = debug.getinfo(2, "nS")
if info.what == "C" and not info.name then
luarocks = { loader = loader }
temporary_global = true
-- For the other half of this hack,
-- see the next use of `temporary_global` below.
end
end
loader.context = {}
-- Contains a table when rocks trees are loaded,
-- or 'false' to indicate rocks trees failed to load.
-- 'nil' indicates rocks trees were not attempted to be loaded yet.
loader.rocks_trees = nil
local function load_rocks_trees()
local any_ok = false
local trees = {}
for _, tree in ipairs(cfg.rocks_trees) do
local manifest, err = manif_core.load_local_manifest(path.rocks_dir(tree))
if manifest then
any_ok = true
table.insert(trees, {tree=tree, manifest=manifest})
end
end
if not any_ok then
loader.rocks_trees = false
return false
end
loader.rocks_trees = trees
return true
end
--- Process the dependencies of a package to determine its dependency
-- chain for loading modules.
-- @param name string: The name of an installed rock.
-- @param version string: The version of the rock, in string format
function loader.add_context(name, version)
-- assert(type(name) == "string")
-- assert(type(version) == "string")
if temporary_global then
-- The first thing a wrapper does is to call add_context.
-- From here on, it's safe to clean the global environment.
luarocks = nil
temporary_global = false
end
if loader.context[name] then
return
end
loader.context[name] = version
if not loader.rocks_trees and not load_rocks_trees() then
return nil
end
for _, tree in ipairs(loader.rocks_trees) do
local manifest = tree.manifest
local pkgdeps
if manifest.dependencies and manifest.dependencies[name] then
pkgdeps = manifest.dependencies[name][version]
end
if not pkgdeps then
return nil
end
for _, dep in ipairs(pkgdeps) do
local pkg, constraints = dep.name, dep.constraints
for _, tree in ipairs(loader.rocks_trees) do
local entries = tree.manifest.repository[pkg]
if entries then
for version, pkgs in util.sortedpairs(entries, deps.compare_versions) do
if (not constraints) or deps.match_constraints(deps.parse_version(version), constraints) then
loader.add_context(pkg, version)
end
end
end
end
end
end
end
--- Internal sorting function.
-- @param a table: A provider table.
-- @param b table: Another provider table.
-- @return boolean: True if the version of a is greater than that of b.
local function sort_versions(a,b)
return a.version > b.version
end
--- Request module to be loaded through other loaders,
-- once the proper name of the module has been determined.
-- For example, in case the module "socket.core" has been requested
-- to the LuaRocks loader and it determined based on context that
-- the version 2.0.2 needs to be loaded and it is not the current
-- version, the module requested for the other loaders will be
-- "socket.core_2_0_2".
-- @param module The module name requested by the user, such as "socket.core"
-- @param name The rock name, such as "luasocket"
-- @param version The rock version, such as "2.0.2-1"
-- @param module_name The actual module name, such as "socket.core" or "socket.core_2_0_2".
-- @return table or (nil, string): The module table as returned by some other loader,
-- or nil followed by an error message if no other loader managed to load the module.
local function call_other_loaders(module, name, version, module_name)
for i, a_loader in ipairs(loaders) do
if a_loader ~= loader.luarocks_loader then
local results = { a_loader(module_name) }
if type(results[1]) == "function" then
return unpack(results)
end
end
end
return "Failed loading module "..module.." in LuaRocks rock "..name.." "..version
end
--- Search for a module in the rocks trees
-- @param module string: module name (eg. "socket.core")
-- @param filter_file_name function(string, string, string, string, number):
-- a function that takes the module file name (eg "socket/core.so"), the rock name
-- (eg "luasocket"), the version (eg "2.0.2-1"), the path of the rocks tree
-- (eg "/usr/local"), and the numeric index of the matching entry, so the
-- filter function can know if the matching module was the first entry or not.
-- @return string, string, string, (string or table):
-- * name of the rock containing the module (eg. "luasocket")
-- * version of the rock (eg. "2.0.2-1")
-- * return value of filter_file_name
-- * tree of the module (string or table in `rocks_trees` format)
local function select_module(module, filter_file_name)
--assert(type(module) == "string")
--assert(type(filter_module_name) == "function")
if not loader.rocks_trees and not load_rocks_trees() then
return nil
end
local providers = {}
for _, tree in ipairs(loader.rocks_trees) do
local entries = tree.manifest.modules[module]
if entries then
for i, entry in ipairs(entries) do
local name, version = entry:match("^([^/]*)/(.*)$")
local file_name = tree.manifest.repository[name][version][1].modules[module]
if type(file_name) ~= "string" then
error("Invalid data in manifest file for module "..tostring(module).." (invalid data for "..tostring(name).." "..tostring(version)..")")
end
file_name = filter_file_name(file_name, name, version, tree.tree, i)
if loader.context[name] == version then
return name, version, file_name
end
version = deps.parse_version(version)
table.insert(providers, {name = name, version = version, module_name = file_name, tree = tree})
end
end
end
if next(providers) then
table.sort(providers, sort_versions)
local first = providers[1]
return first.name, first.version.string, first.module_name, first.tree
end
end
--- Search for a module
-- @param module string: module name (eg. "socket.core")
-- @return string, string, string, (string or table):
-- * name of the rock containing the module (eg. "luasocket")
-- * version of the rock (eg. "2.0.2-1")
-- * name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned).
-- * tree of the module (string or table in `rocks_trees` format)
local function pick_module(module)
return
select_module(module, function(file_name, name, version, tree, i)
if i > 1 then
file_name = path.versioned_name(file_name, "", name, version)
end
return path.path_to_module(file_name)
end)
end
--- Return the pathname of the file that would be loaded for a module.
-- @param module string: module name (eg. "socket.core")
-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
function loader.which(module)
local _, _, file_name = select_module(module, path.which_i)
return file_name
end
--- Package loader for LuaRocks support.
-- A module is searched in installed rocks that match the
-- current LuaRocks context. If module is not part of the
-- context, or if a context has not yet been set, the module
-- in the package with the highest version is used.
-- @param module string: The module name, like in plain require().
-- @return table: The module table (typically), like in plain
-- require(). See <a href="http://www.lua.org/manual/5.1/manual.html#pdf-require">require()</a>
-- in the Lua reference manual for details.
function loader.luarocks_loader(module)
local name, version, module_name = pick_module(module)
if not name then
return "No LuaRocks module found for "..module
else
loader.add_context(name, version)
return call_other_loaders(module, name, version, module_name)
end
end
table.insert(loaders, 1, loader.luarocks_loader)
return loader

View File

@ -0,0 +1,92 @@
--- Module implementing the LuaRocks "make" command.
-- Builds sources in the current directory, but unlike "build",
-- it does not fetch sources, etc., assuming everything is
-- available in the current directory.
local make = {}
package.loaded["luarocks.make"] = make
local build = require("luarocks.build")
local fs = require("luarocks.fs")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local fetch = require("luarocks.fetch")
local pack = require("luarocks.pack")
local remove = require("luarocks.remove")
local deps = require("luarocks.deps")
local manif = require("luarocks.manif")
util.add_run_function(make)
make.help_summary = "Compile package in current directory using a rockspec."
make.help_arguments = "[--pack-binary-rock] [<rockspec>]"
make.help = [[
Builds sources in the current directory, but unlike "build",
it does not fetch sources, etc., assuming everything is
available in the current directory. If no argument is given,
it looks for a rockspec in the current directory and in "rockspec/"
and "rockspecs/" subdirectories, picking the rockspec with newest version
or without version name. If rockspecs for different rocks are found
or there are several rockspecs without version, you must specify which to use,
through the command-line.
This command is useful as a tool for debugging rockspecs.
To install rocks, you'll normally want to use the "install" and
"build" commands. See the help on those for details.
--pack-binary-rock Do not install rock. Instead, produce a .rock file
with the contents of compilation in the current
directory.
--keep Do not remove previously installed versions of the
rock after installing a new one. This behavior can
be made permanent by setting keep_other_versions=true
in the configuration file.
--branch=<name> Override the `source.branch` field in the loaded
rockspec. Allows to specify a different branch to
fetch. Particularly for SCM rocks.
]]
--- Driver function for "make" command.
-- @param name string: A local rockspec.
-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
-- error message otherwise. exitcode is optionally returned.
function make.command(flags, rockspec)
assert(type(rockspec) == "string" or not rockspec)
if not rockspec then
local err
rockspec, err = util.get_default_rockspec()
if not rockspec then
return nil, err
end
end
if not rockspec:match("rockspec$") then
return nil, "Invalid argument: 'make' takes a rockspec as a parameter. "..util.see_help("make")
end
if flags["pack-binary-rock"] then
local rspec, err, errcode = fetch.load_rockspec(rockspec)
if not rspec then
return nil, err
end
return pack.pack_binary_rock(rspec.name, rspec.version, build.build_rockspec, rockspec, false, true, deps.get_deps_mode(flags))
else
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
ok, err = build.build_rockspec(rockspec, false, true, deps.get_deps_mode(flags))
if not ok then return nil, err end
local name, version = ok, err
if (not flags["keep"]) and not cfg.keep_other_versions then
local ok, err = remove.remove_other_versions(name, version, flags["force"], flags["force-fast"])
if not ok then util.printerr(err) end
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return name, version
end
end
return make

View File

@ -0,0 +1,53 @@
--- Module implementing the luarocks-admin "make_manifest" command.
-- Compile a manifest file for a repository.
local make_manifest = {}
package.loaded["luarocks.make_manifest"] = make_manifest
local manif = require("luarocks.manif")
local index = require("luarocks.index")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local deps = require("luarocks.deps")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
util.add_run_function(make_manifest)
make_manifest.help_summary = "Compile a manifest file for a repository."
make_manifest.help = [[
<argument>, if given, is a local repository pathname.
--local-tree If given, do not write versioned versions of the manifest file.
Use this when rebuilding the manifest of a local rocks tree.
]]
--- Driver function for "make_manifest" command.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository configured as cfg.rocks_dir is used.
-- @return boolean or (nil, string): True if manifest was generated,
-- or nil and an error message.
function make_manifest.command(flags, repo)
assert(type(repo) == "string" or not repo)
repo = repo or cfg.rocks_dir
util.printout("Making manifest for "..repo)
if repo:match("/lib/luarocks") and not flags["local-tree"] then
util.warning("This looks like a local rocks tree, but you did not pass --local-tree.")
end
local ok, err = manif.make_manifest(repo, deps.get_deps_mode(flags), not flags["local-tree"])
if ok and not flags["local-tree"] then
util.printout("Generating index.html for "..repo)
index.make_index(repo)
end
if flags["local-tree"] then
for luaver in util.lua_versions() do
fs.delete(dir.path(repo, "manifest-"..luaver))
end
end
return ok, err
end
return make_manifest

View File

@ -0,0 +1,628 @@
--- Module for handling manifest files and tables.
-- Manifest files describe the contents of a LuaRocks tree or server.
-- They are loaded into manifest tables, which are then used for
-- performing searches, matching dependencies, etc.
local manif = {}
package.loaded["luarocks.manif"] = manif
local manif_core = require("luarocks.manif_core")
local persist = require("luarocks.persist")
local fetch = require("luarocks.fetch")
local dir = require("luarocks.dir")
local fs = require("luarocks.fs")
local search = require("luarocks.search")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local path = require("luarocks.path")
local repos = require("luarocks.repos")
local deps = require("luarocks.deps")
manif.rock_manifest_cache = {}
--- Commit a table to disk in given local path.
-- @param where string: The directory where the table should be saved.
-- @param name string: The filename.
-- @param tbl table: The table to be saved.
-- @return boolean or (nil, string): true if successful, or nil and a
-- message in case of errors.
local function save_table(where, name, tbl)
assert(type(where) == "string")
assert(type(name) == "string")
assert(type(tbl) == "table")
local filename = dir.path(where, name)
local ok, err = persist.save_from_table(filename..".tmp", tbl)
if ok then
ok, err = fs.replace_file(filename, filename..".tmp")
end
return ok, err
end
function manif.load_rock_manifest(name, version, root)
assert(type(name) == "string")
assert(type(version) == "string")
local name_version = name.."/"..version
if manif.rock_manifest_cache[name_version] then
return manif.rock_manifest_cache[name_version].rock_manifest
end
local pathname = path.rock_manifest_file(name, version, root)
local rock_manifest = persist.load_into_table(pathname)
if not rock_manifest then return nil end
manif.rock_manifest_cache[name_version] = rock_manifest
return rock_manifest.rock_manifest
end
function manif.make_rock_manifest(name, version)
local install_dir = path.install_dir(name, version)
local tree = {}
for _, file in ipairs(fs.find(install_dir)) do
local full_path = dir.path(install_dir, file)
local walk = tree
local last
local last_name
for name in file:gmatch("[^/]+") do
local next = walk[name]
if not next then
next = {}
walk[name] = next
end
last = walk
last_name = name
walk = next
end
if fs.is_file(full_path) then
local sum, err = fs.get_md5(full_path)
if not sum then
return nil, "Failed producing checksum: "..tostring(err)
end
last[last_name] = sum
end
end
local rock_manifest = { rock_manifest=tree }
manif.rock_manifest_cache[name.."/"..version] = rock_manifest
save_table(install_dir, "rock_manifest", rock_manifest )
end
local function fetch_manifest_from(repo_url, filename)
local url = dir.path(repo_url, filename)
local name = repo_url:gsub("[/:]","_")
local cache_dir = dir.path(cfg.local_cache, name)
local ok = fs.make_dir(cache_dir)
if not ok then
return nil, "Failed creating temporary cache directory "..cache_dir
end
local file, err, errcode = fetch.fetch_url(url, dir.path(cache_dir, filename), true)
if not file then
return nil, "Failed fetching manifest for "..repo_url..(err and " - "..err or ""), errcode
end
return file
end
--- Load a local or remote manifest describing a repository.
-- All functions that use manifest tables assume they were obtained
-- through either this function or load_local_manifest.
-- @param repo_url string: URL or pathname for the repository.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table or (nil, string, [string]): A table representing the manifest,
-- or nil followed by an error message and an optional error code.
function manif.load_manifest(repo_url, lua_version)
assert(type(repo_url) == "string")
assert(type(lua_version) == "string" or not lua_version)
lua_version = lua_version or cfg.lua_version
local cached_manifest = manif_core.get_cached_manifest(repo_url, lua_version)
if cached_manifest then
return cached_manifest
end
local filenames = {
"manifest-"..lua_version..".zip",
"manifest-"..lua_version,
"manifest",
}
local protocol, repodir = dir.split_url(repo_url)
local pathname
if protocol == "file" then
for _, filename in ipairs(filenames) do
pathname = dir.path(repodir, filename)
if fs.exists(pathname) then
break
end
end
else
local err, errcode
for _, filename in ipairs(filenames) do
pathname, err, errcode = fetch_manifest_from(repo_url, filename)
if pathname then
break
end
end
if not pathname then
return nil, err, errcode
end
end
if pathname:match(".*%.zip$") then
pathname = fs.absolute_name(pathname)
local dir = dir.dir_name(pathname)
fs.change_dir(dir)
local nozip = pathname:match("(.*)%.zip$")
fs.delete(nozip)
local ok = fs.unzip(pathname)
fs.pop_dir()
if not ok then
fs.delete(pathname)
fs.delete(pathname..".timestamp")
return nil, "Failed extracting manifest file"
end
pathname = nozip
end
return manif_core.manifest_loader(pathname, repo_url, lua_version)
end
--- Update storage table to account for items provided by a package.
-- @param storage table: a table storing items in the following format:
-- keys are item names and values are arrays of packages providing each item,
-- where a package is specified as string `name/version`.
-- @param items table: a table mapping item names to paths.
-- @param name string: package name.
-- @param version string: package version.
local function store_package_items(storage, name, version, items)
assert(type(storage) == "table")
assert(type(items) == "table")
assert(type(name) == "string")
assert(type(version) == "string")
local package_identifier = name.."/"..version
for item_name, path in pairs(items) do
if not storage[item_name] then
storage[item_name] = {}
end
table.insert(storage[item_name], package_identifier)
end
end
--- Update storage table removing items provided by a package.
-- @param storage table: a table storing items in the following format:
-- keys are item names and values are arrays of packages providing each item,
-- where a package is specified as string `name/version`.
-- @param items table: a table mapping item names to paths.
-- @param name string: package name.
-- @param version string: package version.
local function remove_package_items(storage, name, version, items)
assert(type(storage) == "table")
assert(type(items) == "table")
assert(type(name) == "string")
assert(type(version) == "string")
local package_identifier = name.."/"..version
for item_name, path in pairs(items) do
local all_identifiers = storage[item_name]
for i, identifier in ipairs(all_identifiers) do
if identifier == package_identifier then
table.remove(all_identifiers, i)
break
end
end
if #all_identifiers == 0 then
storage[item_name] = nil
end
end
end
--- Sort function for ordering rock identifiers in a manifest's
-- modules table. Rocks are ordered alphabetically by name, and then
-- by version which greater first.
-- @param a string: Version to compare.
-- @param b string: Version to compare.
-- @return boolean: The comparison result, according to the
-- rule outlined above.
local function sort_pkgs(a, b)
assert(type(a) == "string")
assert(type(b) == "string")
local na, va = a:match("(.*)/(.*)$")
local nb, vb = b:match("(.*)/(.*)$")
return (na == nb) and deps.compare_versions(va, vb) or na < nb
end
--- Sort items of a package matching table by version number (higher versions first).
-- @param tbl table: the package matching table: keys should be strings
-- and values arrays of strings with packages names in "name/version" format.
local function sort_package_matching_table(tbl)
assert(type(tbl) == "table")
if next(tbl) then
for item, pkgs in pairs(tbl) do
if #pkgs > 1 then
table.sort(pkgs, sort_pkgs)
-- Remove duplicates from the sorted array.
local prev = nil
local i = 1
while pkgs[i] do
local curr = pkgs[i]
if curr == prev then
table.remove(pkgs, i)
else
prev = curr
i = i + 1
end
end
end
end
end
end
--- Process the dependencies of a manifest table to determine its dependency
-- chains for loading modules. The manifest dependencies information is filled
-- and any dependency inconsistencies or missing dependencies are reported to
-- standard error.
-- @param manifest table: a manifest table.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for no trees.
local function update_dependencies(manifest, deps_mode)
assert(type(manifest) == "table")
assert(type(deps_mode) == "string")
for pkg, versions in pairs(manifest.repository) do
for version, repositories in pairs(versions) do
for _, repo in ipairs(repositories) do
if repo.arch == "installed" then
repo.dependencies = {}
deps.scan_deps(repo.dependencies, manifest, pkg, version, deps_mode)
repo.dependencies[pkg] = nil
end
end
end
end
end
--- Filter manifest table by Lua version, removing rockspecs whose Lua version
-- does not match.
-- @param manifest table: a manifest table.
-- @param lua_version string or nil: filter by Lua version
-- @param repodir string: directory of repository being scanned
-- @param cache table: temporary rockspec cache table
local function filter_by_lua_version(manifest, lua_version, repodir, cache)
assert(type(manifest) == "table")
assert(type(repodir) == "string")
assert((not cache) or type(cache) == "table")
cache = cache or {}
lua_version = deps.parse_version(lua_version)
for pkg, versions in pairs(manifest.repository) do
local to_remove = {}
for version, repositories in pairs(versions) do
for _, repo in ipairs(repositories) do
if repo.arch == "rockspec" then
local pathname = dir.path(repodir, pkg.."-"..version..".rockspec")
local rockspec, err = cache[pathname]
if not rockspec then
rockspec, err = fetch.load_local_rockspec(pathname, true)
end
if rockspec then
cache[pathname] = rockspec
for _, dep in ipairs(rockspec.dependencies) do
if dep.name == "lua" then
if not deps.match_constraints(lua_version, dep.constraints) then
table.insert(to_remove, version)
end
break
end
end
else
util.printerr("Error loading rockspec for "..pkg.." "..version..": "..err)
end
end
end
end
if next(to_remove) then
for _, incompat in ipairs(to_remove) do
versions[incompat] = nil
end
if not next(versions) then
manifest.repository[pkg] = nil
end
end
end
end
--- Store search results in a manifest table.
-- @param results table: The search results as returned by search.disk_search.
-- @param manifest table: A manifest table (must contain repository, modules, commands tables).
-- It will be altered to include the search results.
-- @return boolean or (nil, string): true in case of success, or nil followed by an error message.
local function store_results(results, manifest)
assert(type(results) == "table")
assert(type(manifest) == "table")
for name, versions in pairs(results) do
local pkgtable = manifest.repository[name] or {}
for version, entries in pairs(versions) do
local versiontable = {}
for _, entry in ipairs(entries) do
local entrytable = {}
entrytable.arch = entry.arch
if entry.arch == "installed" then
local rock_manifest = manif.load_rock_manifest(name, version)
if not rock_manifest then
return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
end
entrytable.modules = repos.package_modules(name, version)
store_package_items(manifest.modules, name, version, entrytable.modules)
entrytable.commands = repos.package_commands(name, version)
store_package_items(manifest.commands, name, version, entrytable.commands)
end
table.insert(versiontable, entrytable)
end
pkgtable[version] = versiontable
end
manifest.repository[name] = pkgtable
end
sort_package_matching_table(manifest.modules)
sort_package_matching_table(manifest.commands)
return true
end
--- Scan a LuaRocks repository and output a manifest file.
-- A file called 'manifest' will be written in the root of the given
-- repository directory.
-- @param repo A local repository directory.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for the default dependency mode from the configuration.
-- @param remote boolean: 'true' if making a manifest for a rocks server.
-- @return boolean or (nil, string): True if manifest was generated,
-- or nil and an error message.
function manif.make_manifest(repo, deps_mode, remote)
assert(type(repo) == "string")
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
if not fs.is_dir(repo) then
return nil, "Cannot access repository at "..repo
end
local query = search.make_query("")
query.exact_name = false
query.arch = "any"
local results = search.disk_search(repo, query)
local manifest = { repository = {}, modules = {}, commands = {} }
manif_core.cache_manifest(repo, nil, manifest)
local ok, err = store_results(results, manifest)
if not ok then return nil, err end
if remote then
local cache = {}
for luaver in util.lua_versions() do
local vmanifest = { repository = {}, modules = {}, commands = {} }
local ok, err = store_results(results, vmanifest)
filter_by_lua_version(vmanifest, luaver, repo, cache)
save_table(repo, "manifest-"..luaver, vmanifest)
end
else
update_dependencies(manifest, deps_mode)
end
return save_table(repo, "manifest", manifest)
end
--- Update manifest file for a local repository
-- adding information about a version of a package installed in that repository.
-- @param name string: Name of a package from the repository.
-- @param version string: Version of a package from the repository.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository is used.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for using the default dependency mode from the configuration.
-- @return boolean or (nil, string): True if manifest was updated successfully,
-- or nil and an error message.
function manif.add_to_manifest(name, version, repo, deps_mode)
assert(type(name) == "string")
assert(type(version) == "string")
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
local manifest, err = manif_core.load_local_manifest(rocks_dir)
if not manifest then
util.printerr("No existing manifest. Attempting to rebuild...")
-- Manifest built by `manif.make_manifest` should already
-- include information about given name and version,
-- no need to update it.
return manif.make_manifest(rocks_dir, deps_mode)
end
local results = {[name] = {[version] = {{arch = "installed", repo = rocks_dir}}}}
local ok, err = store_results(results, manifest)
if not ok then return nil, err end
update_dependencies(manifest, deps_mode)
return save_table(rocks_dir, "manifest", manifest)
end
--- Update manifest file for a local repository
-- removing information about a version of a package.
-- @param name string: Name of a package removed from the repository.
-- @param version string: Version of a package removed from the repository.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository is used.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for using the default dependency mode from the configuration.
-- @return boolean or (nil, string): True if manifest was updated successfully,
-- or nil and an error message.
function manif.remove_from_manifest(name, version, repo, deps_mode)
assert(type(name) == "string")
assert(type(version) == "string")
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
local manifest, err = manif_core.load_local_manifest(rocks_dir)
if not manifest then
util.printerr("No existing manifest. Attempting to rebuild...")
-- Manifest built by `manif.make_manifest` should already
-- include up-to-date information, no need to update it.
return manif.make_manifest(rocks_dir, deps_mode)
end
local package_entry = manifest.repository[name]
local version_entry = package_entry[version][1]
remove_package_items(manifest.modules, name, version, version_entry.modules)
remove_package_items(manifest.commands, name, version, version_entry.commands)
package_entry[version] = nil
manifest.dependencies[name][version] = nil
if not next(package_entry) then
-- No more versions of this package.
manifest.repository[name] = nil
manifest.dependencies[name] = nil
end
update_dependencies(manifest, deps_mode)
return save_table(rocks_dir, "manifest", manifest)
end
--- Report missing dependencies for all rocks installed in a repository.
-- @param repo string or nil: Pathname of a local repository. If not given,
-- the default local repository is used.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for using the default dependency mode from the configuration.
function manif.check_dependencies(repo, deps_mode)
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
assert(type(deps_mode) == "string")
if deps_mode == "none" then deps_mode = cfg.deps_mode end
local manifest = manif_core.load_local_manifest(rocks_dir)
if not manifest then
return
end
for name, versions in util.sortedpairs(manifest.repository) do
for version, version_entries in util.sortedpairs(versions, deps.compare_versions) do
for _, entry in ipairs(version_entries) do
if entry.arch == "installed" then
if manifest.dependencies[name] and manifest.dependencies[name][version] then
deps.report_missing_dependencies(name, version, manifest.dependencies[name][version], deps_mode)
end
end
end
end
end
end
function manif.zip_manifests()
for ver in util.lua_versions() do
local file = "manifest-"..ver
local zip = file..".zip"
fs.delete(dir.path(fs.current_dir(), zip))
fs.zip(zip, file)
end
end
--- Get type and name of an item (a module or a command) provided by a file.
-- @param deploy_type string: rock manifest subtree the file comes from ("bin", "lua", or "lib").
-- @param file_path string: path to the file relatively to deploy_type subdirectory.
-- @return (string, string): item type ("module" or "command") and name.
function manif.get_provided_item(deploy_type, file_path)
assert(type(deploy_type) == "string")
assert(type(file_path) == "string")
local item_type = deploy_type == "bin" and "command" or "module"
local item_name = item_type == "command" and file_path or path.path_to_module(file_path)
return item_type, item_name
end
local function get_providers(item_type, item_name, repo)
assert(type(item_type) == "string")
assert(type(item_name) == "string")
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
local manifest = manif_core.load_local_manifest(rocks_dir)
return manifest and manifest[item_type .. "s"][item_name]
end
--- Given a name of a module or a command, figure out which rock name and version
-- correspond to it in the rock tree manifest.
-- @param item_type string: "module" or "command".
-- @param item_name string: module or command name.
-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
-- @return (string, string) or nil: name and version of the provider rock or nil if there
-- is no provider.
function manif.get_current_provider(item_type, item_name, repo)
local providers = get_providers(item_type, item_name, repo)
if providers then
return providers[1]:match("([^/]*)/([^/]*)")
end
end
function manif.get_next_provider(item_type, item_name, repo)
local providers = get_providers(item_type, item_name, repo)
if providers and providers[2] then
return providers[2]:match("([^/]*)/([^/]*)")
end
end
--- Given a name of a module or a command provided by a package, figure out
-- which file provides it.
-- @param name string: package name.
-- @param version string: package version.
-- @param item_type string: "module" or "command".
-- @param item_name string: module or command name.
-- @param root string or nil: A local root dir for a rocks tree. If not given, the default is used.
-- @return (string, string): rock manifest subtree the file comes from ("bin", "lua", or "lib")
-- and path to the providing file relatively to that subtree.
function manif.get_providing_file(name, version, item_type, item_name, repo)
local rocks_dir = path.rocks_dir(repo or cfg.root_dir)
local manifest = manif_core.load_local_manifest(rocks_dir)
local entry_table = manifest.repository[name][version][1]
local file_path = entry_table[item_type .. "s"][item_name]
if item_type == "command" then
return "bin", file_path
end
-- A module can be in "lua" or "lib". Decide based on extension first:
-- most likely Lua modules are in "lua/" and C modules are in "lib/".
if file_path:match("%." .. cfg.lua_extension .. "$") then
return "lua", file_path
elseif file_path:match("%." .. cfg.lib_extension .. "$") then
return "lib", file_path
end
-- Fallback to rock manifest scanning.
local rock_manifest = manif.load_rock_manifest(name, version)
local subtree = rock_manifest.lib
for path_part in file_path:gmatch("[^/]+") do
if type(subtree) == "table" then
subtree = subtree[path_part]
else
-- Assume it's in "lua/" if it's not in "lib/".
return "lua", file_path
end
end
return type(subtree) == "string" and "lib" or "lua", file_path
end
return manif

View File

@ -0,0 +1,106 @@
--- Core functions for querying manifest files.
-- This module requires no specific 'fs' functionality.
local manif_core = {}
package.loaded["luarocks.manif_core"] = manif_core
local persist = require("luarocks.persist")
local type_check = require("luarocks.type_check")
local cfg = require("luarocks.cfg")
local dir = require("luarocks.dir")
local util = require("luarocks.util")
local path = require("luarocks.path")
-- Table with repository identifiers as keys and tables mapping
-- Lua versions to cached loaded manifests as values.
local manifest_cache = {}
--- Cache a loaded manifest.
-- @param repo_url string: The repository identifier.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @param manifest table: the manifest to be cached.
function manif_core.cache_manifest(repo_url, lua_version, manifest)
lua_version = lua_version or cfg.lua_version
manifest_cache[repo_url] = manifest_cache[repo_url] or {}
manifest_cache[repo_url][lua_version] = manifest
end
--- Attempt to get cached loaded manifest.
-- @param repo_url string: The repository identifier.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @return table or nil: loaded manifest or nil if cache is empty.
function manif_core.get_cached_manifest(repo_url, lua_version)
lua_version = lua_version or cfg.lua_version
return manifest_cache[repo_url] and manifest_cache[repo_url][lua_version]
end
--- Back-end function that actually loads the manifest
-- and stores it in the manifest cache.
-- @param file string: The local filename of the manifest file.
-- @param repo_url string: The repository identifier.
-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
-- @param quick boolean: If given, skips type checking.
-- @return table or (nil, string, string): the manifest or nil,
-- error message and error code ("open", "load", "run" or "type").
function manif_core.manifest_loader(file, repo_url, lua_version, quick)
local manifest, err, errcode = persist.load_into_table(file)
if not manifest then
return nil, "Failed loading manifest for "..repo_url..": "..err, errcode
end
local globals = err
if not quick then
local ok, err = type_check.type_check_manifest(manifest, globals)
if not ok then
return nil, "Error checking manifest: "..err, "type"
end
end
manif_core.cache_manifest(repo_url, lua_version, manifest)
return manifest
end
--- Load a local manifest describing a repository.
-- All functions that use manifest tables assume they were obtained
-- through either this function or load_manifest.
-- @param repo_url string: URL or pathname for the repository.
-- @return table or (nil, string, string): A table representing the manifest,
-- or nil followed by an error message and an error code, see manifest_loader.
function manif_core.load_local_manifest(repo_url)
assert(type(repo_url) == "string")
local cached_manifest = manif_core.get_cached_manifest(repo_url)
if cached_manifest then
return cached_manifest
end
local pathname = dir.path(repo_url, "manifest")
return manif_core.manifest_loader(pathname, repo_url, nil, true)
end
--- Get all versions of a package listed in a manifest file.
-- @param name string: a package name.
-- @param deps_mode string: "one", to use only the currently
-- configured tree; "order" to select trees based on order
-- (use the current tree and all trees below it on the list)
-- or "all", to use all trees.
-- @return table: An array of strings listing installed
-- versions of a package.
function manif_core.get_versions(name, deps_mode)
assert(type(name) == "string")
assert(type(deps_mode) == "string")
local version_set = {}
path.map_trees(deps_mode, function(tree)
local manifest = manif_core.load_local_manifest(path.rocks_dir(tree))
if manifest and manifest.repository[name] then
for version in pairs(manifest.repository[name]) do
version_set[version] = true
end
end
end)
return util.keys(version_set)
end
return manif_core

View File

@ -0,0 +1,200 @@
--- Module implementing the LuaRocks "new_version" command.
-- Utility function that writes a new rockspec, updating data from a previous one.
local new_version = {}
local util = require("luarocks.util")
local download = require("luarocks.download")
local fetch = require("luarocks.fetch")
local persist = require("luarocks.persist")
local fs = require("luarocks.fs")
local type_check = require("luarocks.type_check")
util.add_run_function(new_version)
new_version.help_summary = "Auto-write a rockspec for a new version of a rock."
new_version.help_arguments = "[--tag=<tag>] [<package>|<rockspec>] [<new_version>] [<new_url>]"
new_version.help = [[
This is a utility function that writes a new rockspec, updating data
from a previous one.
If a package name is given, it downloads the latest rockspec from the
default server. If a rockspec is given, it uses it instead. If no argument
is given, it looks for a rockspec same way 'luarocks make' does.
If the version number is not given and tag is passed using --tag,
it is used as the version, with 'v' removed from beginning.
Otherwise, it only increments the revision number of the given
(or downloaded) rockspec.
If a URL is given, it replaces the one from the old rockspec with the
given URL. If a URL is not given and a new version is given, it tries
to guess the new URL by replacing occurrences of the version number
in the URL or tag. It also tries to download the new URL to determine
the new MD5 checksum.
If a tag is given, it replaces the one from the old rockspec. If there is
an old tag but no new one passed, it is guessed in the same way URL is.
WARNING: it writes the new rockspec to the current directory,
overwriting the file if it already exists.
]]
local function try_replace(tbl, field, old, new)
if not tbl[field] then
return false
end
local old_field = tbl[field]
local new_field = tbl[field]:gsub(old, new)
if new_field ~= old_field then
util.printout("Guessing new '"..field.."' field as "..new_field)
tbl[field] = new_field
return true
end
return false
end
-- Try to download source file using URL from a rockspec.
-- If it specified MD5, update it.
-- @return (true, false) if MD5 was not specified or it stayed same,
-- (true, true) if MD5 changed, (nil, string) on error.
local function check_url_and_update_md5(out_rs)
local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-"..out_rs.package)
if not file then
util.printerr("Warning: invalid URL - "..temp_dir)
return true, false
end
local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir)
if not inferred_dir then
return nil, found_dir
end
if found_dir and found_dir ~= inferred_dir then
out_rs.source.dir = found_dir
end
if file then
if out_rs.source.md5 then
util.printout("File successfully downloaded. Updating MD5 checksum...")
local new_md5, err = fs.get_md5(file)
if not new_md5 then
return nil, err
end
local old_md5 = out_rs.source.md5
out_rs.source.md5 = new_md5
return true, new_md5 ~= old_md5
else
util.printout("File successfully downloaded.")
return true, false
end
end
end
local function update_source_section(out_rs, url, tag, old_ver, new_ver)
if tag then
out_rs.source.tag = tag
end
if url then
out_rs.source.url = url
return check_url_and_update_md5(out_rs)
end
if new_ver == old_ver then
return true
end
if out_rs.source.dir then
try_replace(out_rs.source, "dir", old_ver, new_ver)
end
if out_rs.source.file then
try_replace(out_rs.source, "file", old_ver, new_ver)
end
if try_replace(out_rs.source, "url", old_ver, new_ver) then
return check_url_and_update_md5(out_rs)
end
if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then
return true
end
-- Couldn't replace anything significant, use the old URL.
local ok, md5_changed = check_url_and_update_md5(out_rs)
if not ok then
return nil, md5_changed
end
if md5_changed then
util.printerr("Warning: URL is the same, but MD5 has changed. Old rockspec is broken.")
end
return true
end
function new_version.command(flags, input, version, url)
if not input then
local err
input, err = util.get_default_rockspec()
if not input then
return nil, err
end
end
assert(type(input) == "string")
local filename, err
if input:match("rockspec$") then
filename, err = fetch.fetch_url(input)
if not filename then
return nil, err
end
else
filename, err = download.download("rockspec", input:lower())
if not filename then
return nil, err
end
end
local valid_rs, err = fetch.load_rockspec(filename)
if not valid_rs then
return nil, err
end
local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$")
local new_ver, new_rev
if flags.tag and not version then
version = flags.tag:gsub("^v", "")
end
if version then
new_ver, new_rev = version:match("(.*)%-(%d+)$")
new_rev = tonumber(new_rev)
if not new_rev then
new_ver = version
new_rev = 1
end
else
new_ver = old_ver
new_rev = tonumber(old_rev) + 1
end
local new_rockver = new_ver:gsub("-", "")
local out_rs, err = persist.load_into_table(filename)
local out_name = out_rs.package:lower()
out_rs.version = new_rockver.."-"..new_rev
local ok, err = update_source_section(out_rs, url, flags.tag, old_ver, new_ver)
if not ok then return nil, err end
if out_rs.build and out_rs.build.type == "module" then
out_rs.build.type = "builtin"
end
local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec"
persist.save_from_table(out_filename, out_rs, type_check.rockspec_order)
util.printout("Wrote "..out_filename)
local valid_out_rs, err = fetch.load_local_rockspec(out_filename)
if not valid_out_rs then
return nil, "Failed loading generated rockspec: "..err
end
return true
end
return new_version

View File

@ -0,0 +1,197 @@
--- Module implementing the LuaRocks "pack" command.
-- Creates a rock, packing sources or binaries.
local pack = {}
package.loaded["luarocks.pack"] = pack
local unpack = unpack or table.unpack
local path = require("luarocks.path")
local repos = require("luarocks.repos")
local fetch = require("luarocks.fetch")
local fs = require("luarocks.fs")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
local dir = require("luarocks.dir")
local manif = require("luarocks.manif")
local search = require("luarocks.search")
util.add_run_function(pack)
pack.help_summary = "Create a rock, packing sources or binaries."
pack.help_arguments = "{<rockspec>|<name> [<version>]}"
pack.help = [[
Argument may be a rockspec file, for creating a source rock,
or the name of an installed package, for creating a binary rock.
In the latter case, the app version may be given as a second
argument.
]]
--- Create a source rock.
-- Packages a rockspec and its required source files in a rock
-- file with the .src.rock extension, which can later be built and
-- installed with the "build" command.
-- @param rockspec_file string: An URL or pathname for a rockspec file.
-- @return string or (nil, string): The filename of the resulting
-- .src.rock file; or nil and an error message.
function pack.pack_source_rock(rockspec_file)
assert(type(rockspec_file) == "string")
local rockspec, err = fetch.load_rockspec(rockspec_file)
if err then
return nil, "Error loading rockspec: "..err
end
rockspec_file = rockspec.local_filename
local name_version = rockspec.name .. "-" .. rockspec.version
local rock_file = fs.absolute_name(name_version .. ".src.rock")
local source_file, source_dir = fetch.fetch_sources(rockspec, false)
if not source_file then
return nil, source_dir
end
local ok, err = fs.change_dir(source_dir)
if not ok then return nil, err end
fs.delete(rock_file)
fs.copy(rockspec_file, source_dir, cfg.perm_read)
if not fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) then
return nil, "Failed packing "..rock_file
end
fs.pop_dir()
return rock_file
end
local function copy_back_files(name, version, file_tree, deploy_dir, pack_dir, perms)
local ok, err = fs.make_dir(pack_dir)
if not ok then return nil, err end
for file, sub in pairs(file_tree) do
local source = dir.path(deploy_dir, file)
local target = dir.path(pack_dir, file)
if type(sub) == "table" then
local ok, err = copy_back_files(name, version, sub, source, target)
if not ok then return nil, err end
else
local versioned = path.versioned_name(source, deploy_dir, name, version)
if fs.exists(versioned) then
fs.copy(versioned, target, perms)
else
fs.copy(source, target, perms)
end
end
end
return true
end
-- @param name string: Name of package to pack.
-- @param version string or nil: A version number may also be passed.
-- @param tree string or nil: An optional tree to pick the package from.
-- @return string or (nil, string): The filename of the resulting
-- .src.rock file; or nil and an error message.
local function do_pack_binary_rock(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string" or not version)
local repo, repo_url
name, version, repo, repo_url = search.pick_installed_rock(name, version, tree)
if not name then
return nil, version
end
local root = path.root_dir(repo_url)
local prefix = path.install_dir(name, version, root)
if not fs.exists(prefix) then
return nil, "'"..name.." "..version.."' does not seem to be an installed rock."
end
local rock_manifest = manif.load_rock_manifest(name, version, root)
if not rock_manifest then
return nil, "rock_manifest file not found for "..name.." "..version.." - not a LuaRocks 2 tree?"
end
local name_version = name .. "-" .. version
local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock")
local temp_dir = fs.make_temp_dir("pack")
fs.copy_contents(prefix, temp_dir)
local is_binary = false
if rock_manifest.lib then
local ok, err = copy_back_files(name, version, rock_manifest.lib, path.deploy_lib_dir(root), dir.path(temp_dir, "lib"), cfg.perm_exec)
if not ok then return nil, "Failed copying back files: " .. err end
is_binary = true
end
if rock_manifest.lua then
local ok, err = copy_back_files(name, version, rock_manifest.lua, path.deploy_lua_dir(root), dir.path(temp_dir, "lua"), cfg.perm_read)
if not ok then return nil, "Failed copying back files: " .. err end
end
local ok, err = fs.change_dir(temp_dir)
if not ok then return nil, err end
if not is_binary and not repos.has_binaries(name, version) then
rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.")
end
fs.delete(rock_file)
if not fs.zip(rock_file, unpack(fs.list_dir())) then
return nil, "Failed packing "..rock_file
end
fs.pop_dir()
fs.delete(temp_dir)
return rock_file
end
function pack.pack_binary_rock(name, version, cmd, ...)
-- The --pack-binary-rock option for "luarocks build" basically performs
-- "luarocks build" on a temporary tree and then "luarocks pack". The
-- alternative would require refactoring parts of luarocks.build and
-- luarocks.pack, which would save a few file operations: the idea would be
-- to shave off the final deploy steps from the build phase and the initial
-- collect steps from the pack phase.
local temp_dir, err = fs.make_temp_dir("luarocks-build-pack-"..dir.base_name(name))
if not temp_dir then
return nil, "Failed creating temporary directory: "..err
end
util.schedule_function(fs.delete, temp_dir)
path.use_tree(temp_dir)
local ok, err = cmd(...)
if not ok then
return nil, err
end
local rname, rversion = path.parse_name(name)
if not rname then
rname, rversion = name, version
end
return do_pack_binary_rock(rname, rversion, temp_dir)
end
--- Driver function for the "pack" command.
-- @param arg string: may be a rockspec file, for creating a source rock,
-- or the name of an installed package, for creating a binary rock.
-- @param version string or nil: if the name of a package is given, a
-- version may also be passed.
-- @return boolean or (nil, string): true if successful or nil followed
-- by an error message.
function pack.command(flags, arg, version)
assert(type(version) == "string" or not version)
if type(arg) ~= "string" then
return nil, "Argument missing. "..util.see_help("pack")
end
local file, err
if arg:match(".*%.rockspec") then
file, err = pack.pack_source_rock(arg)
else
file, err = do_pack_binary_rock(arg:lower(), version, flags["tree"])
end
if err then
return nil, err
else
util.printout("Packed: "..file)
return true
end
end
return pack

View File

@ -0,0 +1,388 @@
--- LuaRocks-specific path handling functions.
-- All paths are configured in this module, making it a single
-- point where the layout of the local installation is defined in LuaRocks.
local path = {}
local dir = require("luarocks.dir")
local cfg = require("luarocks.cfg")
local util = require("luarocks.util")
--- Infer rockspec filename from a rock filename.
-- @param rock_name string: Pathname of a rock file.
-- @return string: Filename of the rockspec, without path.
function path.rockspec_name_from_rock(rock_name)
assert(type(rock_name) == "string")
local base_name = dir.base_name(rock_name)
return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec"
end
function path.rocks_dir(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.rocks_subdir)
else
assert(type(tree) == "table")
return tree.rocks_dir or dir.path(tree.root, cfg.rocks_subdir)
end
end
function path.root_dir(rocks_dir)
assert(type(rocks_dir) == "string")
return rocks_dir:match("(.*)" .. util.matchquote(cfg.rocks_subdir) .. ".*$")
end
function path.rocks_tree_to_string(tree)
if type(tree) == "string" then
return tree
else
assert(type(tree) == "table")
return tree.root
end
end
function path.deploy_bin_dir(tree)
if type(tree) == "string" then
return dir.path(tree, "bin")
else
assert(type(tree) == "table")
return tree.bin_dir or dir.path(tree.root, "bin")
end
end
function path.deploy_lua_dir(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.lua_modules_path)
else
assert(type(tree) == "table")
return tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path)
end
end
function path.deploy_lib_dir(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.lib_modules_path)
else
assert(type(tree) == "table")
return tree.lib_dir or dir.path(tree.root, cfg.lib_modules_path)
end
end
function path.manifest_file(tree)
if type(tree) == "string" then
return dir.path(tree, cfg.rocks_subdir, "manifest")
else
assert(type(tree) == "table")
return (tree.rocks_dir and dir.path(tree.rocks_dir, "manifest")) or dir.path(tree.root, cfg.rocks_subdir, "manifest")
end
end
--- Get the directory for all versions of a package in a tree.
-- @param name string: The package name.
-- @return string: The resulting path -- does not guarantee that
-- @param tree string or nil: If given, specifies the local tree to use.
-- the package (and by extension, the path) exists.
function path.versions_dir(name, tree)
assert(type(name) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name)
end
--- Get the local installation directory (prefix) for a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.install_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version)
end
--- Get the local filename of the rockspec of an installed rock.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the file) exists.
function path.rockspec_file(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, name.."-"..version..".rockspec")
end
--- Get the local filename of the rock_manifest file of an installed rock.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the file) exists.
function path.rock_manifest_file(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "rock_manifest")
end
--- Get the local installation directory for C libraries of a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.lib_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "lib")
end
--- Get the local installation directory for Lua modules of a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.lua_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "lua")
end
--- Get the local installation directory for documentation of a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.doc_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "doc")
end
--- Get the local installation directory for configuration files of a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.conf_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "conf")
end
--- Get the local installation directory for command-line scripts
-- of a package.
-- @param name string: The package name.
-- @param version string: The package version.
-- @param tree string or nil: If given, specifies the local tree to use.
-- @return string: The resulting path -- does not guarantee that
-- the package (and by extension, the path) exists.
function path.bin_dir(name, version, tree)
assert(type(name) == "string")
assert(type(version) == "string")
tree = tree or cfg.root_dir
return dir.path(path.rocks_dir(tree), name, version, "bin")
end
--- Extract name, version and arch of a rock filename,
-- or name, version and "rockspec" from a rockspec name.
-- @param file_name string: pathname of a rock or rockspec
-- @return (string, string, string) or nil: name, version and arch
-- or nil if name could not be parsed
function path.parse_name(file_name)
assert(type(file_name) == "string")
if file_name:match("%.rock$") then
return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$")
else
return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.(rockspec)")
end
end
--- Make a rockspec or rock URL.
-- @param pathname string: Base URL or pathname.
-- @param name string: Package name.
-- @param version string: Package version.
-- @param arch string: Architecture identifier, or "rockspec" or "installed".
-- @return string: A URL or pathname following LuaRocks naming conventions.
function path.make_url(pathname, name, version, arch)
assert(type(pathname) == "string")
assert(type(name) == "string")
assert(type(version) == "string")
assert(type(arch) == "string")
local filename = name.."-"..version
if arch == "installed" then
filename = dir.path(name, version, filename..".rockspec")
elseif arch == "rockspec" then
filename = filename..".rockspec"
else
filename = filename.."."..arch..".rock"
end
return dir.path(pathname, filename)
end
--- Convert a pathname to a module identifier.
-- In Unix, for example, a path "foo/bar/baz.lua" is converted to
-- "foo.bar.baz"; "bla/init.lua" returns "bla"; "foo.so" returns "foo".
-- @param file string: Pathname of module
-- @return string: The module identifier, or nil if given path is
-- not a conformant module path (the function does not check if the
-- path actually exists).
function path.path_to_module(file)
assert(type(file) == "string")
local name = file:match("(.*)%."..cfg.lua_extension.."$")
if name then
name = name:gsub(dir.separator, ".")
local init = name:match("(.*)%.init$")
if init then
name = init
end
else
name = file:match("(.*)%."..cfg.lib_extension.."$")
if name then
name = name:gsub(dir.separator, ".")
end
end
if not name then name = file end
name = name:gsub("^%.+", ""):gsub("%.+$", "")
return name
end
--- Obtain the directory name where a module should be stored.
-- For example, on Unix, "foo.bar.baz" will return "foo/bar".
-- @param mod string: A module name in Lua dot-separated format.
-- @return string: A directory name using the platform's separator.
function path.module_to_path(mod)
assert(type(mod) == "string")
return (mod:gsub("[^.]*$", ""):gsub("%.", dir.separator))
end
--- Set up path-related variables for a given rock.
-- Create a "variables" table in the rockspec table, containing
-- adjusted variables according to the configuration file.
-- @param rockspec table: The rockspec table.
function path.configure_paths(rockspec)
assert(type(rockspec) == "table")
local vars = {}
for k,v in pairs(cfg.variables) do
vars[k] = v
end
local name, version = rockspec.name, rockspec.version
vars.PREFIX = path.install_dir(name, version)
vars.LUADIR = path.lua_dir(name, version)
vars.LIBDIR = path.lib_dir(name, version)
vars.CONFDIR = path.conf_dir(name, version)
vars.BINDIR = path.bin_dir(name, version)
vars.DOCDIR = path.doc_dir(name, version)
rockspec.variables = vars
end
--- Produce a versioned version of a filename.
-- @param file string: filename (must start with prefix)
-- @param prefix string: Path prefix for file
-- @param name string: Rock name
-- @param version string: Rock version
-- @return string: a pathname with the same directory parts and a versioned basename.
function path.versioned_name(file, prefix, name, version)
assert(type(file) == "string")
assert(type(name) == "string")
assert(type(version) == "string")
local rest = file:sub(#prefix+1):gsub("^/*", "")
local name_version = (name.."_"..version):gsub("%-", "_"):gsub("%.", "_")
return dir.path(prefix, name_version.."-"..rest)
end
function path.use_tree(tree)
cfg.root_dir = tree
cfg.rocks_dir = path.rocks_dir(tree)
cfg.deploy_bin_dir = path.deploy_bin_dir(tree)
cfg.deploy_lua_dir = path.deploy_lua_dir(tree)
cfg.deploy_lib_dir = path.deploy_lib_dir(tree)
end
--- Apply a given function to the active rocks trees based on chosen dependency mode.
-- @param deps_mode string: Dependency mode: "one" for the current default tree,
-- "all" for all trees, "order" for all trees with priority >= the current default,
-- "none" for no trees (this function becomes a nop).
-- @param fn function: function to be applied, with the tree dir (string) as the first
-- argument and the remaining varargs of map_trees as the following arguments.
-- @return a table with all results of invocations of fn collected.
function path.map_trees(deps_mode, fn, ...)
local result = {}
if deps_mode == "one" then
table.insert(result, (fn(cfg.root_dir, ...)) or 0)
elseif deps_mode == "all" or deps_mode == "order" then
local use = false
if deps_mode == "all" then
use = true
end
for _, tree in ipairs(cfg.rocks_trees) do
if dir.normalize(path.rocks_tree_to_string(tree)) == dir.normalize(path.rocks_tree_to_string(cfg.root_dir)) then
use = true
end
if use then
table.insert(result, (fn(tree, ...)) or 0)
end
end
end
return result
end
local is_src_extension = { [".lua"] = true, [".tl"] = true, [".tld"] = true, [".moon"] = true }
--- Return the pathname of the file that would be loaded for a module, indexed.
-- @param file_name string: module file name as in manifest (eg. "socket/core.so")
-- @param name string: name of the package (eg. "luasocket")
-- @param version string: version number (eg. "2.0.2-1")
-- @param tree string: repository path (eg. "/usr/local")
-- @param i number: the index, 1 if version is the current default, > 1 otherwise.
-- This is done this way for use by select_module in luarocks.loader.
-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
function path.which_i(file_name, name, version, tree, i)
local deploy_dir
local extension = file_name:match("%.[a-z]+$")
if is_src_extension[extension] then
deploy_dir = path.deploy_lua_dir(tree)
file_name = dir.path(deploy_dir, file_name)
else
deploy_dir = path.deploy_lib_dir(tree)
file_name = dir.path(deploy_dir, file_name)
end
if i > 1 then
file_name = path.versioned_name(file_name, deploy_dir, name, version)
end
return file_name
end
--- Return the pathname of the file that would be loaded for a module,
-- returning the versioned pathname if given version is not the default version
-- in the given manifest.
-- @param module_name string: module name (eg. "socket.core")
-- @param file_name string: module file name as in manifest (eg. "socket/core.so")
-- @param name string: name of the package (eg. "luasocket")
-- @param version string: version number (eg. "2.0.2-1")
-- @param tree string: repository path (eg. "/usr/local")
-- @param manifest table: the manifest table for the tree.
-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
function path.which(module_name, file_name, name, version, tree, manifest)
local versions = manifest.modules[module_name]
assert(versions)
for i, name_version in ipairs(versions) do
if name_version == name.."/"..version then
return path.which_i(file_name, name, version, tree, i):gsub("//", "/")
end
end
assert(false)
end
return path

View File

@ -0,0 +1,69 @@
--- @module luarocks.path_cmd
-- Driver for the `luarocks path` command.
local path_cmd = {}
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
util.add_run_function(path_cmd)
path_cmd.help_summary = "Return the currently configured package path."
path_cmd.help_arguments = ""
path_cmd.help = [[
Returns the package path currently configured for this installation
of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH.
--bin Adds the system path to the output
--append Appends the paths to the existing paths. Default is to prefix
the LR paths to the existing paths.
--lr-path Exports the Lua path (not formatted as shell command)
--lr-cpath Exports the Lua cpath (not formatted as shell command)
--lr-bin Exports the system path (not formatted as shell command)
On Unix systems, you may run:
eval `luarocks path`
And on Windows:
luarocks path > "%temp%\_lrp.bat" && call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat"
]]
--- Driver function for "path" command.
-- @return boolean This function always succeeds.
function path_cmd.command(flags)
local lr_path, lr_cpath, lr_bin = cfg.package_paths(flags["tree"])
local path_sep = cfg.export_path_separator
if flags["lr-path"] then
util.printout(util.remove_path_dupes(lr_path, ';'))
return true
elseif flags["lr-cpath"] then
util.printout(util.remove_path_dupes(lr_cpath, ';'))
return true
elseif flags["lr-bin"] then
util.printout(util.remove_path_dupes(lr_bin, path_sep))
return true
end
if flags["append"] then
lr_path = package.path .. ";" .. lr_path
lr_cpath = package.cpath .. ";" .. lr_cpath
lr_bin = os.getenv("PATH") .. path_sep .. lr_bin
else
lr_path = lr_path.. ";" .. package.path
lr_cpath = lr_cpath .. ";" .. package.cpath
lr_bin = lr_bin .. path_sep .. os.getenv("PATH")
end
util.printout(cfg.export_lua_path:format(util.remove_path_dupes(lr_path, ';')))
util.printout(cfg.export_lua_cpath:format(util.remove_path_dupes(lr_cpath, ';')))
if flags["bin"] then
util.printout(cfg.export_path:format(util.remove_path_dupes(lr_bin, path_sep)))
end
return true
end
return path_cmd

View File

@ -0,0 +1,210 @@
--- Utility module for loading files into tables and
-- saving tables into files.
-- Implemented separately to avoid interdependencies,
-- as it is used in the bootstrapping stage of the cfg module.
local persist = {}
package.loaded["luarocks.persist"] = persist
local util = require("luarocks.util")
--- Load and run a Lua file in an environment.
-- @param filename string: the name of the file.
-- @param env table: the environment table.
-- @return (true, any) or (nil, string, string): true and the return value
-- of the file, or nil, an error message and an error code ("open", "load"
-- or "run") in case of errors.
local function run_file(filename, env)
local fd, err = io.open(filename)
if not fd then
return nil, err, "open"
end
local str, err = fd:read("*a")
fd:close()
if not str then
return nil, err, "open"
end
str = str:gsub("^#![^\n]*\n", "")
local chunk, ran
if _VERSION == "Lua 5.1" then -- Lua 5.1
chunk, err = loadstring(str, filename)
if chunk then
setfenv(chunk, env)
ran, err = pcall(chunk)
end
else -- Lua 5.2
chunk, err = load(str, filename, "t", env)
if chunk then
ran, err = pcall(chunk)
end
end
if not chunk then
return nil, "Error loading file: "..err, "load"
end
if not ran then
return nil, "Error running file: "..err, "run"
end
return true, err
end
--- Load a Lua file containing assignments, storing them in a table.
-- The global environment is not propagated to the loaded file.
-- @param filename string: the name of the file.
-- @param tbl table or nil: if given, this table is used to store
-- loaded values.
-- @return (table, table) or (nil, string, string): a table with the file's assignments
-- as fields and set of undefined globals accessed in file,
-- or nil, an error message and an error code ("open"; couldn't open the file,
-- "load"; compile-time error, or "run"; run-time error)
-- in case of errors.
function persist.load_into_table(filename, tbl)
assert(type(filename) == "string")
assert(type(tbl) == "table" or not tbl)
local result = tbl or {}
local globals = {}
local globals_mt = {
__index = function(t, k)
globals[k] = true
end
}
local save_mt = getmetatable(result)
setmetatable(result, globals_mt)
local ok, err, errcode = run_file(filename, result)
setmetatable(result, save_mt)
if not ok then
return nil, err, errcode
end
return result, globals
end
local write_table
--- Write a value as Lua code.
-- This function handles only numbers and strings, invoking write_table
-- to write tables.
-- @param out table or userdata: a writer object supporting :write() method.
-- @param v: the value to be written.
-- @param level number: the indentation level
-- @param sub_order table: optional prioritization table
-- @see write_table
local function write_value(out, v, level, sub_order)
if type(v) == "table" then
write_table(out, v, level + 1, sub_order)
elseif type(v) == "string" then
if v:match("[\r\n]") then
local open, close = "[[", "]]"
local equals = 0
local v_with_bracket = v.."]"
while v_with_bracket:find(close, 1, true) do
equals = equals + 1
local eqs = ("="):rep(equals)
open, close = "["..eqs.."[", "]"..eqs.."]"
end
out:write(open.."\n"..v..close)
else
out:write(("%q"):format(v))
end
else
out:write(tostring(v))
end
end
--- Write a table as Lua code in curly brackets notation to a writer object.
-- Only numbers, strings and tables (containing numbers, strings
-- or other recursively processed tables) are supported.
-- @param out table or userdata: a writer object supporting :write() method.
-- @param tbl table: the table to be written.
-- @param level number: the indentation level
-- @param field_order table: optional prioritization table
write_table = function(out, tbl, level, field_order)
out:write("{")
local sep = "\n"
local indentation = " "
local indent = true
local i = 1
for k, v, sub_order in util.sortedpairs(tbl, field_order) do
out:write(sep)
if indent then
for n = 1,level do out:write(indentation) end
end
if k == i then
i = i + 1
else
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
out:write(k)
else
out:write("[")
write_value(out, k, level)
out:write("]")
end
out:write(" = ")
end
write_value(out, v, level, sub_order)
if type(v) == "number" then
sep = ", "
indent = false
else
sep = ",\n"
indent = true
end
end
if sep ~= "\n" then
out:write("\n")
for n = 1,level-1 do out:write(indentation) end
end
out:write("}")
end
--- Write a table as series of assignments to a writer object.
-- @param out table or userdata: a writer object supporting :write() method.
-- @param tbl table: the table to be written.
-- @param field_order table: optional prioritization table
local function write_table_as_assignments(out, tbl, field_order)
for k, v, sub_order in util.sortedpairs(tbl, field_order) do
out:write(k.." = ")
write_value(out, v, 0, sub_order)
out:write("\n")
end
end
--- Save the contents of a table to a string.
-- Each element of the table is saved as a global assignment.
-- Only numbers, strings and tables (containing numbers, strings
-- or other recursively processed tables) are supported.
-- @param tbl table: the table containing the data to be written
-- @param field_order table: an optional array indicating the order of top-level fields.
-- @return string
function persist.save_from_table_to_string(tbl, field_order)
local out = {buffer = {}}
function out:write(data) table.insert(self.buffer, data) end
write_table_as_assignments(out, tbl, field_order)
return table.concat(out.buffer)
end
--- Save the contents of a table in a file.
-- Each element of the table is saved as a global assignment.
-- Only numbers, strings and tables (containing numbers, strings
-- or other recursively processed tables) are supported.
-- @param filename string: the output filename
-- @param tbl table: the table containing the data to be written
-- @param field_order table: an optional array indicating the order of top-level fields.
-- @return boolean or (nil, string): true if successful, or nil and a
-- message in case of errors.
function persist.save_from_table(filename, tbl, field_order)
local out = io.open(filename, "w")
if not out then
return nil, "Cannot create file at "..filename
end
write_table_as_assignments(out, tbl, field_order)
out:close()
return true
end
return persist

View File

@ -0,0 +1,79 @@
--- Module implementing the LuaRocks "purge" command.
-- Remove all rocks from a given tree.
local purge = {}
package.loaded["luarocks.purge"] = purge
local util = require("luarocks.util")
local fs = require("luarocks.fs")
local path = require("luarocks.path")
local search = require("luarocks.search")
local deps = require("luarocks.deps")
local repos = require("luarocks.repos")
local manif = require("luarocks.manif")
local cfg = require("luarocks.cfg")
local remove = require("luarocks.remove")
util.add_run_function(purge)
purge.help_summary = "Remove all installed rocks from a tree."
purge.help_arguments = "--tree=<tree> [--old-versions]"
purge.help = [[
This command removes rocks en masse from a given tree.
By default, it removes all rocks from a tree.
The --tree argument is mandatory: luarocks purge does not
assume a default tree.
--old-versions Keep the highest-numbered version of each
rock and remove the other ones. By default
it only removes old versions if they are
not needed as dependencies. This can be
overridden with the flag --force.
]]
function purge.command(flags)
local tree = flags["tree"]
if type(tree) ~= "string" then
return nil, "The --tree argument is mandatory. "..util.see_help("purge")
end
local results = {}
local query = search.make_query("")
query.exact_name = false
if not fs.is_dir(tree) then
return nil, "Directory not found: "..tree
end
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
search.manifest_search(results, path.rocks_dir(tree), query)
local sort = function(a,b) return deps.compare_versions(b,a) end
if flags["old-versions"] then
sort = deps.compare_versions
end
for package, versions in util.sortedpairs(results) do
for version, repositories in util.sortedpairs(versions, sort) do
if flags["old-versions"] then
util.printout("Keeping "..package.." "..version.."...")
local ok, err = remove.remove_other_versions(package, version, flags["force"], flags["force-fast"])
if not ok then
util.printerr(err)
end
break
else
util.printout("Removing "..package.." "..version.."...")
local ok, err = repos.delete_version(package, version, "none", true)
if not ok then
util.printerr(err)
end
end
end
end
return manif.make_manifest(cfg.rocks_dir, "one")
end
return purge

View File

@ -0,0 +1,33 @@
--- Module implementing the luarocks-admin "refresh_cache" command.
local refresh_cache = {}
package.loaded["luarocks.refresh_cache"] = refresh_cache
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local cache = require("luarocks.cache")
util.add_run_function(refresh_cache)
refresh_cache.help_summary = "Refresh local cache of a remote rocks server."
refresh_cache.help_arguments = "[--from=<server>]"
refresh_cache.help = [[
The flag --from indicates which server to use.
If not given, the default server set in the upload_server variable
from the configuration file is used instead.
]]
function refresh_cache.command(flags)
local server, upload_server = cache.get_upload_server(flags["server"])
if not server then return nil, upload_server end
local download_url = cache.get_server_urls(server, upload_server)
local ok, err = cache.refresh_local_cache(server, download_url, cfg.upload_user, cfg.upload_password)
if not ok then
return nil, err
else
return true
end
end
return refresh_cache

View File

@ -0,0 +1,174 @@
--- Module implementing the LuaRocks "remove" command.
-- Uninstalls rocks.
local remove = {}
package.loaded["luarocks.remove"] = remove
local search = require("luarocks.search")
local deps = require("luarocks.deps")
local fetch = require("luarocks.fetch")
local repos = require("luarocks.repos")
local path = require("luarocks.path")
local util = require("luarocks.util")
local cfg = require("luarocks.cfg")
local fs = require("luarocks.fs")
local manif = require("luarocks.manif")
util.add_run_function(remove)
remove.help_summary = "Uninstall a rock."
remove.help_arguments = "[--force|--force-fast] <name> [<version>]"
remove.help = [[
Argument is the name of a rock to be uninstalled.
If a version is not given, try to remove all versions at once.
Will only perform the removal if it does not break dependencies.
To override this check and force the removal, use --force.
To perform a forced removal without reporting dependency issues,
use --force-fast.
]]..util.deps_mode_help()
--- Obtain a list of packages that depend on the given set of packages
-- (where all packages of the set are versions of one program).
-- @param name string: the name of a program
-- @param versions array of string: the versions to be deleted.
-- @return array of string: an empty table if no packages depend on any
-- of the given list, or an array of strings in "name/version" format.
local function check_dependents(name, versions, deps_mode)
local dependents = {}
local blacklist = {}
blacklist[name] = {}
for version, _ in pairs(versions) do
blacklist[name][version] = true
end
local local_rocks = {}
local query_all = search.make_query("")
query_all.exact_name = false
search.manifest_search(local_rocks, cfg.rocks_dir, query_all)
local_rocks[name] = nil
for rock_name, rock_versions in pairs(local_rocks) do
for rock_version, _ in pairs(rock_versions) do
local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version))
if rockspec then
local _, missing = deps.match_deps(rockspec, blacklist, deps_mode)
if missing[name] then
table.insert(dependents, { name = rock_name, version = rock_version })
end
end
end
end
return dependents
end
--- Delete given versions of a program.
-- @param name string: the name of a program
-- @param versions array of string: the versions to be deleted.
-- @param deps_mode: string: Which trees to check dependencies for:
-- "one" for the current default tree, "all" for all trees,
-- "order" for all trees with priority >= the current default, "none" for no trees.
-- @return boolean or (nil, string): true on success or nil and an error message.
local function delete_versions(name, versions, deps_mode)
for version, _ in pairs(versions) do
util.printout("Removing "..name.." "..version.."...")
local ok, err = repos.delete_version(name, version, deps_mode)
if not ok then return nil, err end
end
return true
end
function remove.remove_search_results(results, name, deps_mode, force, fast)
local versions = results[name]
local version = next(versions)
local second = next(versions, version)
local dependents = {}
if not fast then
util.printout("Checking stability of dependencies in the absence of")
util.printout(name.." "..table.concat(util.keys(versions), ", ").."...")
util.printout()
dependents = check_dependents(name, versions, deps_mode)
end
if #dependents > 0 then
if force or fast then
util.printerr("The following packages may be broken by this forced removal:")
for _, dependent in ipairs(dependents) do
util.printerr(dependent.name.." "..dependent.version)
end
util.printerr()
else
if not second then
util.printerr("Will not remove "..name.." "..version..".")
util.printerr("Removing it would break dependencies for: ")
else
util.printerr("Will not remove installed versions of "..name..".")
util.printerr("Removing them would break dependencies for: ")
end
for _, dependent in ipairs(dependents) do
util.printerr(dependent.name.." "..dependent.version)
end
util.printerr()
util.printerr("Use --force to force removal (warning: this may break modules).")
return nil, "Failed removing."
end
end
local ok, err = delete_versions(name, versions, deps_mode)
if not ok then return nil, err end
util.printout("Removal successful.")
return true
end
function remove.remove_other_versions(name, version, force, fast)
local results = {}
search.manifest_search(results, cfg.rocks_dir, { name = name, exact_name = true, constraints = {{ op = "~=", version = version}} })
if results[name] then
return remove.remove_search_results(results, name, cfg.deps_mode, force, fast)
end
return true
end
--- Driver function for the "remove" command.
-- @param name string: name of a rock. If a version is given, refer to
-- a specific version; otherwise, try to remove all versions.
-- @param version string: When passing a package name, a version number
-- may also be given.
-- @return boolean or (nil, string, exitcode): True if removal was
-- successful, nil and an error message otherwise. exitcode is optionally returned.
function remove.command(flags, name, version)
if type(name) ~= "string" then
return nil, "Argument missing. "..util.see_help("remove")
end
local deps_mode = flags["deps-mode"] or cfg.deps_mode
local ok, err = fs.check_command_permissions(flags)
if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$")
local filename = name
if rock_type then
name, version = path.parse_name(filename)
if not name then return nil, "Invalid "..rock_type.." filename: "..filename end
end
local results = {}
name = name:lower()
search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version))
if not results[name] then
return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir)
end
local ok, err = remove.remove_search_results(results, name, deps_mode, flags["force"], flags["force-fast"])
if not ok then
return nil, err
end
manif.check_dependencies(nil, deps.get_deps_mode(flags))
return true
end
return remove

Some files were not shown because too many files have changed in this diff Show More