-
Notifications
You must be signed in to change notification settings - Fork 26
CONTRIBUTING
NetLogo Web and its constituent projects, Galapagos (the web site and model view) and Tortoise (the compiler and simulation engine), are open source software and we welcome contributions.
To get started, sign the Contributor License Agreement.
We accept contributions through the fork-and-pull methodology. Fork the relevant repository and submit pull requests for contributions you'd like to see merged.
Before contributing code, read the Architecture overview and get your local environment Building and Running.
Development follows standard git best practices: use atomic commits with clear messages and do work on side branches instead of master. Good branch name patterns are wip-feature-name (for "work in progress") and topic-feature-name for larger-scoped work.
Galapagos includes some automated tests, but at the moment most feature testing for model runs is done manually in the browser. It can be helpful to create special "feature test" NetLogo models that can be quickly loaded and checked to see if the changes are successful.
Tortoise has an extensive test suite. See Tortoise Tests for details on running docking tests, language tests, model dump checks, and the quick-test script to run before finalizing PRs.
If you have made changes in the Tortoise repository and you want to try them out in the browser using Galapagos, do the following:
- Launch
./sbt.shin the Tortoise repository, if you haven't already. - Run
publishLocalVersioned netLogoWeb. - Run
publishLocalVersioned compilerJVM.
This will publish the two Tortoise .jars to your local .jar repository (~/.ivy2), making them accessible to your local copy of Galapagos.
- Find the new version hash that was just published. You can check Git or read it from the logs printed during
publishLocalVersioned.- Example log entry:
[info] Packaging ~/Github/Tortoise/netlogo-web/target/netlogowebjs-1.0-e9dc67a.jar ... - So
$VERSION_HASHshould bee9dc67a.
- Example log entry:
- Copy the new version hash into the Galapagos repository's
build.sbt, where we settortoiseVersion.- Example:
val tortoiseVersion = "1.0-e9dc67a"
- Example:
If you did not commit your work in the Tortoise repository, the version will be something like 1.0-...$VERSION_HASH-dirty, so include -dirty in your build.sbt version if that is the case.
For Scala code, follow the rules from the NetLogo code formatting wiki page:
- Small Commits
- Line Length Limit of 120
- Whitespace (2 space indents, no tabs)
- String Concatenation
- Naming Things
- Binding Variables for Clarity
Tortoise also has a Scala style checker. Run stylecheck in the sbt console to check all Scala sub-projects.
Both Tortoise and Galapagos use CoffeeScript for engine and UI code. Because CoffeeScript uses whitespace to indicate blocks, some formatting is enforced automatically. Both projects also have a CoffeeScript linter that catches common issues. When in doubt, use existing code in the Tortoise engine as a reference.
- Indentation: 2 spaces. No tabs.
- Trailing whitespace: None.
- End of file: Always a single blank line.
-
Line length: No hard limit, but prefer wrapping long lines. Use
# coffeelint: disable=max_line_length/# coffeelint: enable=max_line_lengtharound sections where wrapping would hurt readability more than it helps.
-
File names: All lowercase. In Galapagos, use kebab-case (
widget-controller.coffee); in Tortoise, all lowercase with no punctuation (abstractagentset.coffee). -
Variables and properties:
camelCase. -
Constants (module-level, never reassigned):
SCREAMING_SNAKE_CASE. -
Classes:
PascalCase. -
Private members (on class instances): prefix with
_, e.g.@_layerDeps. -
Event names:
kebab-case, e.g.'world-might-change','close-button-keydown'. -
Boolean variables/properties: prefer
is/has/canprefixes, e.g.isEditing,hasFocus.
Always use parentheses to wrap method calls. Good: console.log(myObj), bad: console.log myObj. The linter enforces this, but it's worth calling out explicitly: the lack of parentheses can make order of operations very confusing, especially since everything in CoffeeScript is an expression.
All functions should have a type annotation in a comment on the line immediately above the definition. Types use PascalCase.
# (String, Number) -> Boolean
isValidAge = (name, age) ->
age > 0Common types:
| Notation | Meaning |
|---|---|
Unit |
No meaningful return value (void) |
String, Number, Boolean
|
Primitives |
Array[Type] |
A JS array of Type
|
Object[Type] |
A plain object with values of Type
|
Any |
Unknown or mixed type |
For complex or composite types, define them with a ### block comment above the code that uses them:
###
type Config = {
name: String
, maxIters: Number
}
###Instance variables declared on a class or in a Ractive data block should be annotated with an inline # Type comment:
data: -> {
agent: undefined # Agent
hasFocus: false # Boolean
}Use -> for regular functions. Use => (fat arrow) only when the function needs to capture the outer this/@, e.g. in callbacks passed to external APIs or setTimeout.
# Good: plain function, no `this` needed
transform = (x) -> x * 2
# Good: fat arrow because @ is needed inside the callback
@_editor.on('focus', => @set('hasFocus', true))Explicit return is required at the end of Unit functions (functions that produce a side effect and return nothing meaningful). Value-returning functions should omit the explicit return for the final expression.
# Unit function: explicit return
# (String) -> Unit
setTitle = (title) ->
@set('title', title)
return
# Value-returning function: no explicit return needed
# (Number, Number) -> Number
add = (a, b) ->
a + bFor type signatures with function-typed parameters or return values, use => for the arrow within the type annotation (to distinguish it from the surrounding -> structure):
# ((String) => Unit, Array[Widget]) -> Unit
setUpWidgets = (reportError, widgets) ->
...Prefer double-quoted strings. Use single quotes when the string itself contains double quotes, to avoid escaping.
Use string interpolation ("#{expr}") rather than concatenation.
Use comma-first style for multi-line object literals. Align values when the keys are of similar length and alignment aids readability:
widget = {
display: undefined # String
, fontSize: undefined # Number
, source: undefined # String
}Single-line objects use spaces inside braces: { key: value }.
The same alignment principle applies to repeated variable assignments on consecutive lines:
@_eventLoopTimeout = -1
@_lastRedraw = 0
@_lastUpdate = 0
@drawEveryFrame = falseUse class syntax for plain objects and services (e.g. SessionLite, ViewController). Use Ractive.extend({}) for Ractive components — do not use class extends Ractive.
In Ractive components:
- Define
data: -> { ... }with one property per line, each with an inline type comment. - Set
twoway: falseon edit forms and other components that manage their own submission lifecycle. - Put all event handlers in the
on: { ... }block. - Put reactive computations in the
computed: { ... }block. - Write templates as triple-quoted heredoc strings (
"""). - Component names in
components:arecamelCasekeys mapping toPascalCasevalues.
RactiveMyWidget = Ractive.extend({
data: -> {
label: undefined # String
, isActive: false # Boolean
}
twoway: false
components: {
editForm: MyEditForm
}
on: {
'close-button-clicked': ->
@fire('widget-closed')
return
}
template: """
<div class="my-widget">{{label}}</div>
"""
})Prefer if not over unless in most cases. Reserve unless for the rare situation where it genuinely reads more clearly than the if not equivalent. Prefer the existential operator ? over explicit != null or != undefined checks. Use ?. for optional method calls and ?= for conditional assignment.
# Preferred
doSomething() unless config?
result = cache ?= computeExpensiveThing()
viewWindow?.destructor()Use do -> for IIFEs when you need to create a local closure:
onQMark = do ->
focusedElement = undefined
(event) ->
...Write comments in plain English. For non-obvious decisions, include the author's initials and date:
# Kludge to work around strange IE behavior. --Jason B. (4/21/16)Use # TODO for known shortcomings that aren't being addressed immediately.
- Indentation: 2 spaces.
- Declarations: One per line.
- Braces: Opening brace on the same line as the selector; closing brace on its own line.
- Blank lines: One blank line between rules.
Within a rule block, align all values to a common column using trailing spaces on property names. The column is determined by the longest property name in the block:
/* Good */
.my-rule {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
/* Not preferred */
.my-rule {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}Apply this to all blocks with two or more declarations. Single-declaration blocks need no alignment. Vendor-prefixed properties that are longer than the unprefixed equivalent (e.g. -moz-user-select alongside user-select) are exempt — leave their colons unaligned rather than inflating the column for the rest of the block.
- Semicolons: Every declaration must end with a semicolon, including the last one in a block.
-
Zero: Use
0without a unit (margin: 0, notmargin: 0px). -
Redundant shorthand values: Collapse repeated values in multi-value shorthands.
padding: 8px 8px→padding: 8px;padding: 10px 30px 10px 30px→padding: 10px 30px. -
Function spacing: Put a space after each comma in CSS functions —
rgba(0, 0, 0, 0.5), notrgba(0,0,0,0.5). -
Shorthand order: Follow standard order for shorthands. For
border:width style color—border: 2px solid black, notborder: 2px black solid.
Prefer shorthand properties over a collection of longhand overrides:
/* Good */
.example {
padding: 6px 6px 6px 0.2em;
}
/* Not preferred */
.example {
padding: 0.2em;
padding-top: 6px;
padding-right: 6px;
padding-bottom: 6px;
}- No empty rule blocks: Remove rules with no declarations — they add noise and no behavior.
- No duplicate properties: A property listed twice in the same block means one is dead code. Remove it.
- No redundant values: Remove properties that reset to the browser default unless the intent needs to be made explicit for future readers.