Page MenuHomePhorge

No OneTemporary

diff --git a/src/docs/book/contributor.book b/src/docs/book/contributor.book
index d5b2ae90ae..10db63c011 100644
--- a/src/docs/book/contributor.book
+++ b/src/docs/book/contributor.book
@@ -1,36 +1,36 @@
{
"name": "phabcontrib",
"title": "Phabricator Contributor Documentation",
"short": "Phabricator Contributor Docs",
- "preface": "Information for Phabricator contributors.",
+ "preface": "Information for Phabricator contributors and developers.",
"root": "../../../",
"uri.source":
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
"rules": {
"(\\.diviner$)": "DivinerArticleAtomizer"
},
"exclude": [
"(^externals/)",
"(^resources/)",
"(^scripts/)",
"(^src/docs/flavor/)",
"(^src/docs/tech/)",
"(^src/docs/user/)",
"(^support/)",
"(^webroot/rsrc/externals/)"
],
"groups": {
"contrib": {
"name": "Contributor Overview"
},
"detail": {
"name": "Contributing in Detail"
},
"developer": {
"name": "Developer Guides"
},
"standards": {
"name": "Coding Standards"
}
}
}
diff --git a/src/docs/book/phabricator.book b/src/docs/book/phabricator.book
index ed74ddd012..2b362853b4 100644
--- a/src/docs/book/phabricator.book
+++ b/src/docs/book/phabricator.book
@@ -1,347 +1,347 @@
{
"name": "phabdev",
"title": "Phabricator Technical Documentation",
"short": "Phabricator Tech Docs",
- "preface": "Technical documentation intended for Phabricator developers.",
+ "preface": "Technical reference material for Phabricator developers.",
"root": "../../../",
"uri.source":
"https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
"rules": {
"(\\.diviner$)": "DivinerArticleAtomizer",
"(\\.php$)": "DivinerPHPAtomizer"
},
"exclude": [
"(^externals/)",
"(^resources/)",
"(^scripts/)",
"(^src/docs/contributor/)",
"(^src/docs/flavor/)",
"(^src/docs/user/)",
"(^support/)",
"(^webroot/rsrc/externals/)"
],
"groups": {
"aphront": {
"name": "Aphront",
"include": "(^src/aphront/)"
},
"almanac": {
"name": "Almanac",
"include": "(^src/applications/almanac/)"
},
"aphlict": {
"name": "Aphlict",
"include": "(^src/applications/aphlict/)"
},
"arcanist": {
"name": "Arcanist Integration",
"include": "(^src/applications/arcanist/)"
},
"audit": {
"name": "Audit",
"include": "(^src/applications/audit/)"
},
"auth": {
"name": "Auth",
"include": "(^src/applications/auth/)"
},
"baseapp": {
"name": "Application Basics",
"include": "(^src/applications/base/)"
},
"cache": {
"name": "Cache",
"include": "(^src/applications/cache/)"
},
"calendar": {
"name": "Calendar",
"include": "(^src/applications/calendar/)"
},
"celerity": {
"name": "Celerity",
"include": "(^src/applications/celerity/)"
},
"chatlog": {
"name": "Chatlog",
"include": "(^src/applications/chatlog/)"
},
"conduit": {
"name": "Conduit",
"include": "(^src/applications/conduit/)"
},
"config": {
"name": "Config",
"include": "(^src/applications/config/)"
},
"conpherence": {
"name": "Conpherence",
"include": "(^src/applications/conpherence/)"
},
"console": {
"name": "Console",
"include": "(^src/applications/console/)"
},
"countdown": {
"name": "Countdown",
"include": "(^src/applications/countdown/)"
},
"customfield": {
"name": "Custom Fields",
"include": "(^src/infrastructure/customfield/)"
},
"daemon": {
"name": "Daemons",
"include": [
"(^src/applications/daemon/)",
"(^src/infrastructure/daemon/)"
]
},
"dashboard": {
"name": "Dashboard",
"include": "(^src/applications/dashboard/)"
},
"differential": {
"name": "Differential",
"include": "(^src/applications/differential/)"
},
"diffusion": {
"name": "Diffusion",
"include": "(^src/applications/diffusion/)"
},
"diviner": {
"name": "Diviner",
"include": "(^src/applications/diviner/)"
},
"doorkeeper": {
"name": "Doorkeeper",
"include": "(^src/applications/doorkeeper/)"
},
"draft": {
"name": "Draft",
"include": "(^src/applications/draft/)"
},
"drydock": {
"name": "Drydock",
"include": "(^src/applications/drydock/)"
},
"edges": {
"name": "Edges",
"include": "(^src/infrastructure/edges/)"
},
"events": {
"name": "Events",
"include": "(^src/infrastructure/events/)"
},
"fact": {
"name": "Fact",
"include": "(^src/applications/fact/)"
},
"feed": {
"name": "Feed",
"include": "(^src/applications/feed/)"
},
"files": {
"name": "Files",
"include": "(^src/applications/files/)"
},
"flag": {
"name": "Flags",
"include": "(^src/applications/flag/)"
},
"fund": {
"name": "Fund",
"include": "(^src/applications/fund/)"
},
"harbormaster": {
"name": "Harbormaster",
"include": "(^src/applications/harbormaster/)"
},
"help": {
"name": "Help",
"include": "(^src/applications/help/)"
},
"herald": {
"name": "Herald",
"include": "(^src/applications/herald/)"
},
"home": {
"name": "Home",
"include": "(^src/applications/home/)"
},
"legalpad": {
"name": "Legalpad",
"include": "(^src/applications/legalpad/)"
},
"lipsum": {
"name": "Lipsum",
"include": "(^src/applications/lipsum/)"
},
"macro": {
"name": "Macro",
"include": "(^src/applications/macro/)"
},
"maniphest": {
"name": "Maniphest",
"include": "(^src/applications/maniphest/)"
},
"meta": {
"name": "Applications",
"include": "(^src/applications/meta/)"
},
"metamta": {
"name": "MetaMTA",
"include": "(^src/applications/metamta/)"
},
"multimeter": {
"name": "Multimeter",
"include": "(^src/applications/multimeter/)"
},
"notification": {
"name": "Notifications",
"include": "(^src/applications/notification/)"
},
"nuance": {
"name": "Nuance",
"include": "(^src/applications/nuance/)"
},
"oauthserver": {
"name": "OAuth Server",
"include": "(^src/applications/oauthserver/)"
},
"owners": {
"name": "Owners",
"include": "(^src/applications/owners/)"
},
"passphrase": {
"name": "Passphrase",
"include": "(^src/applications/passphrase/)"
},
"paste": {
"name": "Paste",
"include": "(^src/applications/paste/)"
},
"people": {
"name": "People",
"include": "(^src/applications/people/)"
},
"phame": {
"name": "Phame",
"include": "(^src/applications/phame/)"
},
"phid": {
"name": "PHIDs",
"include": "(^src/applications/phid/)"
},
"phlux": {
"name": "Phlux",
"include": "(^src/applications/phlux/)"
},
"pholio": {
"name": "Pholio",
"include": "(^src/applications/pholio/)"
},
"phortune": {
"name": "Phortune",
"include": "(^src/applications/phortune/)"
},
"phpast": {
"name": "PHPAST",
"include": "(^src/applications/phpast/)"
},
"phragment": {
"name": "Phragment",
"include": "(^src/applications/phragment/)"
},
"phrequent": {
"name": "Phrequent",
"include": "(^src/applications/phrequent/)"
},
"phriction": {
"name": "Phriction",
"include": "(^src/applications/phriction/)"
},
"phui": {
"name": "PHUI",
"include": "(^src/view/phui/)"
},
"policy": {
"name": "Policy",
"include": "(^src/applications/policy/)"
},
"ponder": {
"name": "Ponder",
"include": "(^src/applications/ponder/)"
},
"project": {
"name": "Projects",
"include": "(^src/applications/project/)"
},
"releeph": {
"name": "Releeph",
"include": "(^src/applications/releeph/)"
},
"remarkup": {
"name": "Remarkup",
"include": [
"(^src/applications/remarkup/)",
"(^src/infrastructure/markup/)"
]
},
"repository": {
"name": "Repositories",
"include": "(^src/applications/repository/)"
},
"search": {
"name": "Search",
"include": "(^src/applications/search/)"
},
"settings": {
"name": "Settings",
"include": "(^src/applications/settings/)"
},
"slowvote": {
"name": "Slowvote",
"include": "(^src/applications/slowvote/)"
},
"spaces": {
"name": "Spaces",
"include": "(^src/applications/spaces/)"
},
"storage": {
"name": "Storage",
"include": "(^src/infrastructure/storage/)"
},
"subscriptions": {
"name": "Subscriptions",
"include": "(^src/applications/subscriptions/)"
},
"support": {
"name": "Support",
"include": "(^src/applications/support/)"
},
"system": {
"name": "System",
"include": "(^src/applications/system/)"
},
"tokens": {
"name": "Tokens",
"include": "(^src/applications/tokens/)"
},
"transactions": {
"name": "Transactions",
"include": "(^src/applications/transactions/)"
},
"typeahead": {
"name": "Typeahead",
"include": "(^src/applications/typeahead/)"
},
"uiexample": {
"name": "UI Examples",
"include": "(^src/applications/uiexample/)"
},
"xhprof": {
"name": "XHProf",
"include": "(^src/applications/xhprof/)"
}
}
}
diff --git a/src/docs/contributor/adding_new_classes.diviner b/src/docs/contributor/adding_new_classes.diviner
new file mode 100644
index 0000000000..beb4567210
--- /dev/null
+++ b/src/docs/contributor/adding_new_classes.diviner
@@ -0,0 +1,256 @@
+@title Adding New Classes
+@group developer
+
+Guide to adding new classes to extend Phabricator.
+
+Overview
+========
+
+Phabricator is highly modular, and many parts of it can be extended by adding
+new classes. This document explains how to write new classes to change or
+expand the behavior of Phabricator.
+
+IMPORTANT: The upstream does not offer support with extension development.
+
+Fundamentals
+============
+
+Phabricator primarily discovers functionality by looking at concrete subclasses
+of some base class. For example, Phabricator determines which applications are
+available by looking at all of the subclasses of
+@{class@phabricator:PhabricatorApplication}. It
+discovers available workflows in `arc` by looking at all of the subclasses of
+@{class@arcanist:ArcanistWorkflow}. It discovers available locales
+by looking at all of the subclasses of @{class@libphutil:PhutilLocale}.
+
+This pattern holds in many cases, so you can often add functionality by adding
+new classes with no other work. Phabricator will automatically discover and
+integrate the new capabilities or features at runtime.
+
+There are two main ways to add classes:
+
+ - **Extensions Directory**: This is a simple way to add new code. It is
+ less powerful, but takes a lot less work. This is good for quick changes,
+ testing and development, or getting started on a larger project.
+ - **Creating Libraries**: This is a more advanced and powerful way to
+ organize extension code. This is better for larger or longer-lived
+ projects, or any code which you plan to distribute.
+
+The next sections walk through these approaches in greater detail.
+
+
+Extensions Directory
+====================
+
+The easiest way to extend Phabricator by adding new classes is to drop them
+into the extensions directory, at `phabricator/src/extensions/`.
+
+This is intended as a quick way to add small pieces of functionality, test new
+features, or get started on a larger project. Extending Phabricator like this
+imposes a small performance penalty compared to using a library.
+
+This directory exists in all libphutil libraries, so you can find similar
+directories in `arcanist/src/extensions/` and `libphutil/src/extensions/`.
+
+For example, to add a new application, create a file like this one and add it
+to `phabricator/src/extensions/`.
+
+```name=phabricator/src/extensions/ExampleApplication.php, lang=php
+<?php
+
+final class ExampleApplication extends PhabricatorApplication {
+
+ public function getName() {
+ return pht('Example');
+ }
+
+}
+```
+
+If you load {nav Applications} in the web UI, you should now see your new
+application in the list. It won't do anything yet since you haven't defined
+any interesting behavior, but this is the basic building block of Phabricator
+extensions.
+
+
+Creating Libraries
+==================
+
+A more powerful (but more complicated) way to extend Phabricator is to create
+a libphutil library. Libraries can organize a larger amount of code, are easier
+to work with and distribute, and have slightly better performance than loose
+source files in the extensions directory.
+
+In general, you'll perform these one-time setup steps to create a library:
+
+ - Create a new directory.
+ - Use `arc liberate` to initialize and name the library.
+ - Configure Phabricator or Arcanist to load the library.
+
+Then, to add new code, you do this:
+
+ - Write or update classes.
+ - Update the library metadata by running `arc liberate` again.
+
+Initializing a Library
+======================
+
+To create a new libphutil library, create a directory for it and run
+`arc liberate` on the directory. This documentation will use a conventional
+directory layout, which is recommended, but you are free to deviate from this.
+
+```
+$ mkdir libcustom/
+$ cd libcustom/
+libcustom/ $ arc liberate src/
+```
+
+Now you'll get a prompt like this:
+
+```lang=txt
+No library currently exists at that path...
+The directory '/some/path/libcustom/src' does not exist.
+
+ Do you want to create it? [y/N] y
+Creating new libphutil library in '/some/path/libcustom/src'.
+Choose a name for the new library.
+
+ What do you want to name this library?
+```
+
+Choose a library name (in this case, "libcustom" would be appropriate) and it
+you should get some details about the library initialization:
+
+```lang=txt
+Writing '__phutil_library_init__.php' to
+ '/some/path/libcustom/src/__phutil_library_init__.php'...
+Using library root at 'src'...
+Mapping library...
+Verifying library...
+Finalizing library map...
+ OKAY Library updated.
+```
+
+This will write three files:
+
+ - `src/.phutil_module_cache` This is a cache which makes "arc liberate"
+ faster when you run it to update the library. You can safely remove it at
+ any time. If you check your library into version control, you can add this
+ file to ignore rules (like `.gitignore`).
+ - `src/__phutil_library_init__.php` This records the name of the library and
+ tells libphutil that a library exists here.
+ - `src/__phutil_library_map__.php` This is a map of all the symbols
+ (functions and classes) in the library, which allows them to be autoloaded
+ at runtime and dependencies to be statically managed by `arc liberate`.
+
+Linking with Phabricator
+========================
+
+If you aren't using this library with Phabricator (e.g., you are only using it
+with Arcanist or are building something else on libphutil) you can skip this
+step.
+
+But, if you intend to use this library with Phabricator, you need to define its
+dependency on Phabricator by creating a `.arcconfig` file which points at
+Phabricator. For example, you might write this file to
+`libcustom/.arcconfig`:
+
+```lang=json
+{
+ "load": [
+ "phabricator/src/"
+ ]
+}
+```
+
+For details on creating a `.arcconfig`, see
+@{article:Arcanist User Guide: Configuring a New Project}. In general, this
+tells `arc liberate` that it should look for symbols in Phabricator when
+performing static analysis.
+
+NOTE: If Phabricator isn't located next to your custom library, specify a
+path which actually points to the `phabricator/` directory.
+
+You do not need to declare dependencies on `arcanist` or `libphutil`,
+since `arc liberate` automatically loads them.
+
+Finally, edit your Phabricator config to tell it to load your library at
+runtime, by adding it to `load-libraries`:
+
+```lang=json
+...
+'load-libraries' => array(
+ 'libcustom' => 'libcustom/src/',
+),
+...
+```
+
+Now, Phabricator will be able to load classes from your custom library.
+
+
+Writing Classes
+===============
+
+To actually write classes, create a new module and put code in it:
+
+ libcustom/ $ mkdir src/example/
+ libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
+
+Now, run `arc liberate` to regenerate the static resource map:
+
+ libcustom/ $ arc liberate src/
+
+This will automatically regenerate the static map of the library.
+
+
+What You Can Extend And Invoke
+==============================
+
+libphutil, Arcanist and Phabricator are strict about extensibility of classes
+and visibility of methods and properties. Most classes are marked `final`, and
+methods have the minimum required visibility (protected or private). The goal
+of this strictness is to make it clear what you can safely extend, access, and
+invoke, so your code will keep working as the upstream changes.
+
+IMPORTANT: We'll still break APIs frequently. The upstream does not support
+extension development, and none of these APIs are stable.
+
+When developing libraries to work with libphutil, Arcanist and Phabricator, you
+should respect method and property visibility.
+
+If you want to add features but can't figure out how to do it without changing
+Phabricator code, here are some approaches you may be able to take:
+
+ - {icon check, color=green} **Use Composition**: If possible, use composition
+ rather than extension to build your feature.
+ - {icon check, color=green} **Find Another Approach**: Check the
+ documentation for a better way to accomplish what you're trying to do.
+ - {icon check, color=green} **File a Feature Request**: Let us know what your
+ use case is so we can make the class tree more flexible or configurable, or
+ point you at the right way to do whatever you're trying to do, or explain
+ why we don't let you do it. Note that we **do not support** extension
+ development so you may have mixed luck with this one.
+
+These approaches are **discouraged**, but also possible:
+
+ - {icon times, color=red} **Fork**: Create an ad-hoc local fork and remove
+ `final` in your copy of the code. This will make it more difficult for you
+ to upgrade in the future, although it may be the only real way forward
+ depending on what you're trying to do.
+ - {icon times, color=red} **Use Reflection**: You can use
+ [[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove
+ modifiers at runtime. This is fragile and discouraged, but technically
+ possible.
+ - {icon times, color=red} **Remove Modifiers**: Send us a patch removing
+ `final` (or turning `protected` or `private` into `public`). We will almost
+ never accept these patches unless there's a very good reason that the
+ current behavior is wrong.
+
+
+Next Steps
+==========
+
+Continue by:
+
+ - visiting the [[ https://secure.phabricator.com/w/community_resources/ |
+ Community Resources ]] page to find or share extensions and libraries.
diff --git a/src/docs/contributor/internationalization.diviner b/src/docs/contributor/internationalization.diviner
index 401bf4950a..f937ec1ae2 100644
--- a/src/docs/contributor/internationalization.diviner
+++ b/src/docs/contributor/internationalization.diviner
@@ -1,65 +1,378 @@
@title Internationalization
@group developer
Describes Phabricator translation and localization.
Overview
========
Phabricator partially supports internationalization, but many of the tools
are missing or in a prototype state.
-This document very briefly summarizes some of what exists today.
+This document describes what tools exist today, how to add new translations,
+and how to use the translation tools to make a codebase translatable.
+
+
+Adding a New Locale
+===================
+
+To add a new locale, subclass @{class:PhutilLocale}. This allows you to
+introduce a new locale, like "German" or "Klingon".
+
+Once you've created a locale, applications can add translations for that
+locale.
+
+For instructions on adding new classes, see @{article:Adding New Classes}.
+
+
+Adding Translations to Locale
+=============================
+
+To translate strings, subclass @{class:PhutilTranslation}. Translations need
+to belong to a locale: the locale defines an available language, and each
+translation subclass provides strings for it.
+
+Translations are separated from locales so that third-party applications can
+provide translations into different locales without needing to define those
+locales themselves.
+
+For instructions on adding new classes, see @{article:Adding New Classes}.
+
Writing Translatable Code
-========
+=========================
Strings are marked for translation with @{function@libphutil:pht}.
-Adding a New Locale
-=========
+The `pht()` function takes a string (and possibly some parameters) and returns
+the translated version of that string in the current viewer's locale, if a
+translation is available.
-To add a new locale, subclass @{class:PhutilLocale}.
+If text strings will ultimately be read by humans, they should essentially
+always be wrapped in `pht()`. For example:
-Translating Strings
-========
+```lang=php
+$dialog->appendParagraph(pht('This is an example.'));
+```
+
+This allows the code to return the correct Spanish or German or Russian
+version of the text, if the viewer is using Phabricator in one of those
+languages and a translation is available.
+
+Using `pht()` properly so that strings are translatable can be tricky. Briefly,
+the major rules are:
+
+ - Only pass static strings as the first parameter to `pht()`.
+ - Use parameters to create strings containing user names, object names, etc.
+ - Translate full sentences, not sentence fragments.
+ - Let the translation framework handle plural rules.
+ - Use @{class@libphutil:PhutilNumber} for numbers.
+ - Let the translation framework handle subject gender rules.
+ - Translate all human-readable text, even exceptions and error messages.
+
+See the next few sections for details on these rules.
+
+
+Use Static Strings
+==================
+
+The first parameter to `pht()` must always be a static string. Broadly, this
+means it should not contain variables or function or method calls (it's OK to
+split it across multiple lines and concatenate the parts together).
+
+These are good:
+
+```lang=php
+pht('The night is dark.');
+pht(
+ 'Two roads diverged in a yellow wood, '.
+ 'and sorry I could not travel both '.
+ 'and be one traveler, long I stood.');
+
+```
+
+These won't work (they might appear to work, but are wrong):
+
+```lang=php, counterexample
+pht(some_function());
+pht('The duck says, '.$quack);
+pht($string);
+```
+
+The first argument must be a static string so it can be extracted by static
+analysis tools and dumped in a big file for translators. If it contains
+functions or variables, it can't be extracted, so translators won't be able to
+translate it.
+
+Lint will warn you about problems with use of static strings in calls to
+`pht()`.
+
+
+Parameters
+==========
+
+You can provide parameters to a translation string by using `sprintf()`-style
+patterns in the input string. For example:
+
+```lang=php
+pht('%s earned an award.', $actor);
+pht('%s closed %s.', $actor, $task);
+```
+
+This is primarily appropriate for usernames, object names, counts, and
+untranslatable strings like URIs or instructions to run commands from the CLI.
+
+Parameters normally should not be used to combine two pieces of translated
+text: see the next section for guidance.
+
+Sentence Fragments
+==================
+
+You should almost always pass the largest block of text to `pht()` that you
+can. Particularly, it's important to pass complete sentences, not try to build
+a translation by stringing together sentence fragments.
+
+There are several reasons for this:
+
+ - It gives translators more context, so they can be more confident they are
+ producing a satisfying, natural-sounding translation which will make sense
+ and sound good to native speakers.
+ - In some languages, one fragment may need to translate differently depending
+ on what the other fragment says.
+ - In some languages, the most natural-sounding translation may change the
+ order of words in the sentence.
+
+For example, suppose we want to translate these sentence to give the user some
+instructions about how to use an interface:
+
+> Turn the switch to the right.
+
+> Turn the switch to the left.
+
+> Turn the dial to the right.
+
+> Turn the dial to the left.
+
+Maybe we have a function like this:
+
+```
+function get_string($is_switch, $is_right) {
+ // ...
+}
+```
+
+One way to write the function body would be like this:
+
+```lang=php, counterexample
+$what = $is_switch ? pht('switch') : pht('dial');
+$dir = $is_right ? pht('right') : pht('left');
+
+return pht('Turn the ').$what.pht(' to the ').$dir.pht('.');
+```
+
+This will work fine in English, but won't work well in other languages.
+
+One problem with doing this is handling gendered nouns. Languages like Spanish
+have gendered nouns, where some nouns are "masculine" and others are
+"feminine". The gender of a noun affects which article (in English, the word
+"the" is an article) should be used with it.
+
+In English, we say "**the** knob" and "**the** switch", but a Spanish speaker
+would say "**la** perilla" and "**el** interruptor", because the noun for
+"knob" in Spanish is feminine (so it is used with the article "la") while the
+noun for "switch" is masculine (so it is used with the article "el").
+
+A Spanish speaker can not translate the string "Turn the" correctly without
+knowing which gender the noun has. Spanish has //two// translations for this
+string ("Gira el", "Gira la"), and the form depends on which noun is being
+used.
+
+Another problem is that this reduces flexibility. Translating fragments like
+this locks translators into a specific word order, when rearranging the words
+might make the sentence sound much more natural to a native speaker.
+
+For example, if the string read "The knob, to the right, turn it.", it
+would technically be English and most English readers would understand the
+meaning, but no native English speaker would speak or write like this.
+
+However, some languages have different subject-verb order rules or
+colloquisalisms, and a word order which transliterates like this may sound more
+natural to a native speaker. By translating fragments instead of complete
+sentences, you lock translators into English word order.
+
+Finally, the last fragment is just a period. If a translator is presented with
+this string in an interface without much context, they have no hope of guessing
+how it is used in the software (it could be an end-of-sentence marker, or a
+decimal point, or a date separator, or a currency separator, all of which have
+very different translations in many locales). It will also conflict with all
+other translations of the same string in the codebase, so even if they are
+given context they can't translate it without technical problems.
+
+To avoid these issues, provide complete sentences for translation. This almost
+always takes the form of writing out alternatives in full. This is a good way
+to implement the example function:
+
+```lang=php
+if ($is_switch) {
+ if ($is_right) {
+ return pht('Turn the switch to the right.');
+ } else {
+ return pht('Turn the switch to the left.');
+ }
+} else {
+ if ($is_right) {
+ return pht('Turn the dial to the right.');
+ } else {
+ return pht('Turn the dial to the left.');
+ }
+}
+```
+
+Although this is more verbose, translators can now get genders correct,
+rearrange word order, and have far more context when translating. This enables
+better, natural-sounding translations which are more satisfying to native
+speakers.
-To translate strings, subclass @{class:PhutilTranslation}.
Singular and Plural
-========
+===================
+
+Different languages have various rules for plural nouns.
+
+In English there are usually two plural noun forms: for one thing, and any
+other number of things. For example, we say that one chair is a "chair" and any
+other number of chairs are "chairs": "0 chairs", "1 chair", "2 chairs", etc.
+
+In other languages, there are different (and, in some cases, more) plural
+forms. For example, in Czech, there are separate forms for "one", "several",
+and "many".
+
+Because plural noun rules depend on the language, you should not write code
+which hard-codes English rules. For example, this won't translate well:
-Different languages have various rules for using singular and plural. All you
-need to do is to call @{function@libphutil:pht} with a text that is suitable for
-both forms. Example:
+```lang=php, counterexample
+if ($count == 1) {
+ return pht('This will take an hour.');
+} else {
+ return pht('This will take hours.');
+}
+```
- pht('%d beer(s)', $count);
+This code is hard-coding the English rule for plural nouns. In languages like
+Czech, the correct word for "hours" may be different if the count is 2 or 15,
+but a translator won't be able to provide the correct translation if the string
+is written like this.
-Translators will translate this text for all different forms the language uses:
+Instead, pass a generic string to the translation engine which //includes// the
+number of objects, and let it handle plural nouns. This is the correct way to
+write the translation:
- // English translation
- array('%d beer', '%d beers');
+```lang=php
+return pht('This will take %s hour(s).', new PhutilNumber($count));
+```
- // Czech translation
- array('%d pivo', '%d piva', '%d piv');
+If you now load the web UI, you'll see "hour(s)" literally in the UI. To fix
+this so the translation sounds better in English, provide translations for this
+string in the @{class@phabricator:PhabricatorUSEnglishTranslation} file:
-The ugly identifier passed to @{function@libphutil:pht} will remain in the text
-only if the translation doesn't exist.
+```lang=php
+'This will take %s hour(s).' => array(
+ 'This will take an hour.',
+ 'This will take hours.',
+),
+```
+
+The string will then sound natural in English, but non-English translators will
+also be able to produce a natural translation.
+
+Note that the translations don't actually include the number in this case. The
+number is being passed from the code, but that just lets the translation engine
+get the rules right: the number does not need to appear in the final
+translations shown to the user.
+
+Using PhutilNumber
+==================
+
+When translating numbers, you should almost always use `%s` and wrap the count
+or number in `new PhutilNumber($count)`. For example:
+
+```lang=php
+pht('You have %s experience point(s).', new PhutilNumber($xp));
+```
+
+This will let the translation engine handle plural noun rules correctly, and
+also format large numbers correctly in a locale-aware way with proper unit and
+decimal separators (for example, `1000000` may be printed as "1,000,000",
+with commas for readability).
+
+The exception to this rule is IDs which should not be written with unit
+separators. For example, this is correct for an object ID:
+
+```lang=php
+pht('This diff has ID %d.', $diff->getID());
+```
Male and Female
-========
+===============
+
+Different languages also use different words for talking about subjects who are
+male, female or have an unknown gender. In English this is mostly just
+pronouns (like "he" and "she") but there are more complex rules in other
+languages, and languages like Czech also require verb agreement.
+
+When a parameter refers to a gendered person, pass an object which implements
+@{interface@libphutil:PhutilPerson} to `pht()` so translators can provide
+gendered translation variants.
+
+```lang=php
+pht('%s wrote', $actor);
+```
+
+Translators will create these translations:
+
+```lang=php
+// English translation
+'%s wrote';
+
+// Czech translation
+array('%s napsal', '%s napsala');
+```
+
+(You usually don't need to worry very much about this rule, it is difficult to
+get wrong in standard code.)
+
+
+Exceptions and Errors
+=====================
+
+You should translate all human-readable text, even exceptions and error
+messages. This is primarily a rule of convenience which is straightforward
+and easy to follow, not a technical rule.
+
+Some exceptions and error messages don't //technically// need to be translated,
+as they will never be shown to a user, but many exceptions and error messages
+are (or will become) user-facing on some way. When writing a message, there is
+often no clear and objective way to determine which type of message you are
+writing. Rather than try to distinguish which are which, we simply translate
+all human-readable text. This rule is unambiguous and easy to follow.
+
+In cases where similar error or exception text is often repeated, it is
+probably appropriate to define an exception for that category of error rather
+than write the text out repeatedly, anyway. Two examples are
+@{class@libphutil:PhutilInvalidStateException} and
+@{class@libphutil:PhutilMethodNotImplementedException}, which mostly exist to
+produce a consistent message about a common error state in a convenient way.
-Different languages use different words for talking about males, females and
-unknown genders. Callsites have to call @{function@libphutil:pht} passing
-@{class:PhabricatorUser} (or other implementation of
-@{interface@libphutil:PhutilPerson}) if talking about the user. Example:
+There are a handful of error strings in the codebase which may be used before
+the translation framework is loaded, or may be used during handling other
+errors, possibly rasised from within the translation framework. This handful
+of special cases are left untranslated to prevent fatals and cycles in the
+error handler.
- pht('%s wrote', $actor);
-Translators will create this translations:
+Next Steps
+==========
- // English translation
- '%s wrote';
+Continue by:
- // Czech translation
- array('%s napsal', '%s napsala');
+ - adding a new locale or translation file with @{article:Adding New Classes}.
diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner
index 405633fc39..41adfbd092 100644
--- a/src/docs/user/configuration/custom_fields.diviner
+++ b/src/docs/user/configuration/custom_fields.diviner
@@ -1,211 +1,211 @@
@title Configuring Custom Fields
@group config
How to add custom fields to applications which support them.
= Overview =
Several Phabricator applications allow the configuration of custom fields. These
fields allow you to add more information to objects, and in some cases reorder
or remove builtin fields.
For example, you could use custom fields to add an "Estimated Hours" field to
tasks, a "Lead" field to projects, or a "T-Shirt Size" field to users.
These applications currently support custom fields:
| Application | Support |
|-------------|---------|
| Maniphest | Full Support |
| Projects | Full Support |
| People | Full Support |
| Differential | Partial Support |
| Diffusion | Limited Support |
Custom fields can appear in many interfaces and support search, editing, and
other features.
= Basic Custom Fields =
To get started with custom fields, you can use configuration to select and
reorder fields and to add new simple fields.
If you don't need complicated display controls or sophisticated validation,
these simple fields should cover most use cases. They allow you to attach
things like strings, numbers, and dropdown menus to objects.
The relevant configuration settings are:
| Application | Add Fields | Select Fields |
|-------------|------------|---------------|
| Maniphest | `maniphest.custom-field-definitions` | `maniphest.fields` |
| Projects | `projects.custom-field-definitions` | `projects.fields` |
| People | `user.custom-field-definitions` | `user.fields` |
| Differential | Planned | `differential.fields` |
| Diffusion | Planned | Planned |
When adding fields, you'll specify a JSON blob like this (for example, as the
value of `maniphest.custom-field-definitions`):
{
"mycompany:estimated-hours": {
"name": "Estimated Hours",
"type": "int",
"caption": "Estimated number of hours this will take.",
"required": true
},
"mycompany:actual-hours": {
"name": "Actual Hours",
"type": "int",
"caption": "Actual number of hours this took."
},
"mycompany:company-jobs": {
"name": "Job Role",
"type": "select",
"options": {
"mycompany:engineer": "Engineer",
"mycompany:nonengineer": "Other"
}
},
"mycompany:favorite-dinosaur": {
"name": "Favorite Dinosaur",
"type": "text"
}
}
The fields will then appear in the other config option for the application
(for example, in `maniphest.fields`) and you can enable, disable, or reorder
them.
For details on how to define a field, see the next section.
= Custom Field Configuration =
When defining custom fields using a configuration option like
`maniphest.custom-field-definitions`, these options are available:
- **name**: Display label for the field on the edit and detail interfaces.
- **description**: Optional text shown when managing the field.
- **type**: Field type. The supported field types are:
- **int**: An integer, rendered as a text field.
- **text**: A string, rendered as a text field.
- **bool**: A boolean value, rendered as a checkbox.
- **select**: Allows the user to select from several options as defined
by **options**, rendered as a dropdown.
- **remarkup**: A text area which allows the user to enter markup.
- **users**: A typeahead which allows multiple users to be input.
- **date**: A date/time picker.
- **header**: Renders a visual divider which you can use to group fields.
- **link**: A text field which allows the user to enter a link.
- **edit**: Show this field on the application's edit interface (this
defaults to `true`).
- **view**: Show this field on the application's view interface (this
defaults to `true`). (Note: Empty fields are not shown.)
- **search**: Show this field on the application's search interface, allowing
users to filter objects by the field value.
- **fulltext**: Index the text in this field as part of the object's global
full-text index. This allows users to find the object by searching for
the field's contents using global search.
- **caption**: A caption to display underneath the field (optional).
- **required**: True if the user should be required to provide a value.
- **options**: If type is set to **select**, provide options for the dropdown
as a dictionary.
- **default**: Default field value.
- **strings**: Allows you to override specific strings based on the field
type. See below.
- **instructions**: Optional block of remarkup text which will appear
above the control when rendered on the edit view.
- **placeholder**: A placeholder text that appears on text boxes. Only
supported in text, int and remarkup fields (optional).
The `strings` value supports different strings per control type. They are:
- **bool**
- **edit.checkbox** Text for the edit interface, no default.
- **view.yes** Text for the view interface, defaults to "Yes".
- **search.default** Text for the search interface, defaults to "(Any)".
- **search.require** Text for the search interface, defaults to "Require".
Some applications have specific options which only work in that application.
In **Maniphest**:
- **copy**: When a user creates a task, the UI gives them an option to
"Create Another Similar Task". Some fields from the original task are copied
into the new task, while others are not; by default, fields are not copied.
If you want this field to be copied, specify `true` for the `copy` property.
Internally, Phabricator implements some additional custom field types and
options. These are not intended for general use and are subject to abrupt
change, but are documented here for completeness:
- **Credentials**: Controls with type `credential` allow selection of a
Passphrase credential which provides `credential.provides`, and creation
of credentials of `credential.type`.
= Advanced Custom Fields =
If you want custom fields to have advanced behaviors (sophisticated rendering,
advanced validation, complicated controls, interaction with other systems, etc),
you can write a custom field as an extension and add it to Phabricator.
NOTE: This API is somewhat new and fairly large. You should expect that there
will be occasional changes to the API requiring minor updates in your code.
To do this, extend the appropriate `CustomField` class for the application you
want to add a field to:
| Application | Extend |
|-------------|---------|
| Maniphest | @{class:ManiphestCustomField} |
| Projects | @{class:PhabricatorProjectCustomField} |
| People | @{class:PhabricatorUserCustomField} |
| Differential | @{class:DifferentialCustomField} |
| Diffusion | @{class:PhabricatorCommitCustomField} |
The easiest way to get started is to drop your subclass into
`phabricator/src/extensions/`, which should make it immediately available in the
UI (if you use APC, you may need to restart your webserver). For example, this
is a simple template which adds a custom field to Maniphest:
name=ExampleManiphestCustomField.php
<?php
final class ExampleCustomField extends ManiphestCustomField {
public function getFieldKey() {
return 'example:test';
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return pht('Example Custom Field');
}
public function renderPropertyViewValue(array $handles) {
return phutil_tag(
'h1',
array(
'style' => 'color: #ff00ff',
),
pht('It worked!'));
}
}
Broadly, you can then add features by overriding more methods and implementing
them. Many of the native fields are implemented on the custom field
architecture, and it may be useful to look at them. For details on available
integrations, see the base class for your application and
@{class:PhabricatorCustomField}.
= Next Steps =
Continue by:
- learning more about extending Phabricator with custom code in
- @{article:libphutil Libraries User Guide};
+ @{article@contributor:Adding New Classes};
- or returning to the @{article: Configuration Guide}.
diff --git a/src/docs/user/configuration/managing_daemons.diviner b/src/docs/user/configuration/managing_daemons.diviner
index 36187ca30f..d82421496f 100644
--- a/src/docs/user/configuration/managing_daemons.diviner
+++ b/src/docs/user/configuration/managing_daemons.diviner
@@ -1,140 +1,140 @@
@title Managing Daemons with phd
@group config
Explains Phabricator daemons and the daemon control program `phd`.
= Overview =
Phabricator uses daemons (background processing scripts) to handle a number of
tasks:
- tracking repositories, discovering new commits, and importing and parsing
commits;
- sending email; and
- collecting garbage, like old logs and caches.
Daemons are started and stopped with **phd** (the **Ph**abricator **D**aemon
launcher). Daemons can be monitored via a web console.
You do not need to run daemons for most parts of Phabricator to work, but some
features (principally, repository tracking with Diffusion) require them and
several features will benefit in performance or stability if you configure
daemons.
= phd =
**phd** is a command-line script (located at `phabricator/bin/phd`). To get
a list of commands, run `phd help`:
phabricator/ $ ./bin/phd help
NAME
phd - phabricator daemon launcher
...
Generally, you will use:
- **phd start** to launch all daemons;
- **phd restart** to restart all daemons;
- **phd status** to get a list of running daemons; and
- **phd stop** to stop all daemons.
If you want finer-grained control, you can use:
- **phd launch** to launch individual daemons; and
- **phd debug** to debug problems with daemons.
NOTE: When you upgrade Phabricator or change configuration, you should restart
the daemons by running `phd restart`.
= Daemon Console =
You can view status and debugging information for daemons in the Daemon Console
via the web interface. Go to `/daemon/` in your install or click
**Daemon Console** from "More Stuff".
The Daemon Console shows a list of all the daemons that have ever launched, and
allows you to view log information for them. If you have issues with daemons,
you may be able to find error information that will help you resolve the problem
in the console.
NOTE: The easiest way to figure out what's wrong with a daemon is usually to use
**phd debug** to launch it instead of **phd start**. This will run it without
daemonizing it, so you can see output in your console.
= Available Daemons =
You can get a list of launchable daemons with **phd list**:
- **libphutil test daemons** are not generally useful unless you are
developing daemon infrastructure or debugging a daemon problem;
- **PhabricatorTaskmasterDaemon** performs work from a task queue;
- **PhabricatorRepositoryPullLocalDaemon** daemons track repositories, for
more information see @{article:Diffusion User Guide}; and
- **PhabricatorTriggerDaemon** schedules event triggers and cleans up old
logs and caches.
= Debugging and Tuning =
In most cases, **phd start** handles launching all the daemons you need.
However, you may want to use more granular daemon controls to debug daemons,
launch custom daemons, or launch special daemons like the IRC bot.
To debug a daemon, use `phd debug`:
phabricator/bin/ $ ./phd debug <daemon>
You can pass arguments like this (normal arguments are passed to the daemon
control mechanism, not to the daemon itself):
phabricator/bin/ $ ./phd debug <daemon> -- --flavor apple
In debug mode, daemons do not daemonize, and they print additional debugging
output to the console. This should make it easier to debug problems. You can
terminate the daemon with `^C`.
To launch a nonstandard daemon, use `phd launch`:
phabricator/bin/ $ ./phd launch <daemon>
This daemon will daemonize and run normally.
== General Tips ==
- You can set the maximum number of taskmasters that will run at once
by adjusting `phd.taskmasters`. If you have a task backlog, try increasing
it.
- When you `phd launch` or `phd debug` a daemon, you can type any unique
substring of its name, so `phd launch pull` will work correctly.
- `phd stop` and `phd restart` stop **all** of the daemons on the machine, not
just those started with `phd start`. If you're writing a restart script,
have it launch any custom daemons explicitly after `phd restart`.
- You can write your own daemons and manage them with `phd` by extending
- @{class:PhabricatorDaemon}. See @{article:libphutil Libraries User Guide}.
+ @{class:PhabricatorDaemon}. See {article@contributor:Adding New Classes}.
- See @{article:Diffusion User Guide} for details about tuning the repository
daemon.
== Multiple Machines ==
If you have multiple machines, you should use `phd launch` to tweak which
daemons launch, and split daemons across machines like this:
- `PhabricatorRepositoryPullLocalDaemon`: Run one copy on any machine.
On each web frontend which is not running a normal copy, run a copy
with the `--no-discovery` flag.
- `PhabricatorTriggerDaemon`: Run one copy on any machine.
- `PhabricatorTaskmasterDaemon`: Run as many copies as you need to keep
tasks from backing up. You can run them all on one machine or split them
across machines.
A gratuitously wasteful install might have a dedicated daemon machine which
runs `phd start` with a large pool of taskmasters set in the config, and then
runs `phd launch PhabricatorRepositoryPullLocalDaemon -- --no-discovery` on each
web server. This is grossly excessive in normal cases.
= Next Steps =
Continue by:
- learning about the repository daemon with @{article:Diffusion User Guide};
or
- - writing your own daemons with @{article:libphutil Libraries User Guide}.
+ - writing your own daemons with {article@contributor:Adding New Classes}.
diff --git a/src/docs/user/userguide/arcanist_lint_unit.diviner b/src/docs/user/userguide/arcanist_lint_unit.diviner
index e425911fae..00ad4e237b 100644
--- a/src/docs/user/userguide/arcanist_lint_unit.diviner
+++ b/src/docs/user/userguide/arcanist_lint_unit.diviner
@@ -1,92 +1,92 @@
@title Arcanist User Guide: Customizing Lint, Unit Tests and Workflows
@group userguide
Explains how to build new classes to control how Arcanist behaves.
This is a configuration guide that helps you set up advanced features. If you're
just getting started, you don't need to look at this yet. Instead, start with
the @{article:Arcanist User Guide}.
= Overview =
Arcanist has some basic configuration options available in the `.arcconfig`
file (see @{article:Arcanist User Guide: Configuring a New Project}), but it
can't handle everything. If you want to customize Arcanist at a deeper level,
you need to build new classes. For instance:
- if you want to configure linters, or add new linters, you need to create a
new class which extends @{class@arcanist:ArcanistLintEngine}.
- if you want to integrate with a unit testing framework, you need to create a
new class which extends @{class@arcanist:ArcanistUnitTestEngine}.
- if you you want to change how workflows behave, or add new workflows, you
need to create a new class which extends
@{class@arcanist:ArcanistConfiguration}.
Arcanist works through a sort of dependency-injection approach. For example,
Arcanist does not run lint rules by default, but you can set `lint.engine`
in your `.arcconfig` to the name of a class which extends
@{class@arcanist:ArcanistLintEngine}. When running from inside your project,
Arcanist will load this class and call methods on it in order to run lint. To
make this work, you need to do three things:
- actually write the class;
- add the library where the class exists to your `.arcconfig`;
- add the class name to your `.arcconfig` as the **lint.engine**,
**unit.engine**, or **arcanist_configuration**.
= Create a libphutil Library =
If you haven't created a library for the class to live in yet, you need to do
that first. Follow the instructions in
-@{article:libphutil Libraries User Guide}, then make the library loadable by
+@{article@contributor:Adding New Classes}, then make the library loadable by
adding it to your `.arcconfig` like this:
{
// ...
"load" : [
// ...
"/path/to/my/library", // Absolute path
"support/arcanist", // Relative path in this project
// ...
]
// ...
}
You can either specify an absolute path, or a path relative to the project root.
When you run `arc list --trace`, you should see a message to the effect that
it has loaded your library.
For debugging or testing, you can also run Arcanist with the
`--load-phutil-library` flag:
arc --load-phutil-library=/path/to/library <command>
You can specify this flag more than once to load several libraries. Note that
if you use this flag, Arcanist will ignore any libraries listed in
`.arcconfig`.
= Use the Class =
This step is easy: just edit `.arcconfig` to specify your class name as
the appropriate configuration value.
{
// ...
"lint.engine" : "CustomArcanistLintEngine",
// ...
}
Now, when you run Arcanist in your project, it will invoke your class when
appropriate.
For lint and unit tests, you can also use the `--engine` flag override the
default engine:
arc lint --engine MyCustomArcanistLintEngine
This is mostly useful for debugging and testing.
= Next Steps =
- Learn how to reuse existing linters by reading
@{article:Arcanist User Guide: Customizing Existing Linters}.
diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner
index cc65344f0f..b414a1e997 100644
--- a/src/docs/user/userguide/arcanist_new_project.diviner
+++ b/src/docs/user/userguide/arcanist_new_project.diviner
@@ -1,223 +1,223 @@
@title Arcanist User Guide: Configuring a New Project
@group userguide
Explains how to configure Arcanist projects with `.arcconfig` files.
= Overview =
In most cases, you should be able to use `arc` without specifically configuring
your project for it. If you want to adjust `arc` behaviors, you can create a
`.arcconfig` file in your project to provide project-specific settings.
= .arcconfig Basics =
An `.arcconfig` file is a JSON file which you check into your project's root.
Arcanist uses `.arcconfig` files to customize a number of things about its
behavior. The first thing you're likely to want to configure is the URI
for your Phabricator install. A simple, valid file looks something like this:
name=.arcconfig
{
"phabricator.uri" : "https://phabricator.example.com/"
}
For details on available options, see below.
NOTE: You should commit your `.arcconfig` file! It contains project
configuration, not user configuration.
= Advanced .arcconfig =
Common options are:
- **phabricator.uri**: the URI for the Phabricator install that `arc` should
connect to when run in this project. This option was previously called
`conduit_uri`.
- **repository.callsign**: The callsign of this repository in Diffusion.
Normally, `arc` can detect this automatically, but if it can't figure it out
you can specify it explicitly. Use `arc which` to understand the detection
process.
- **history.immutable**: Configures `arc` to use workflows which never rewrite
history in the working copy. By default, `arc` will perform some rewriting
of unpublished history (amending commit messages, squash merging) on some
workflows in Git. The distinctions are covered in detail below.
Other options include:
- **load**: list of additional Phutil libraries to load at startup.
See below for details about path resolution, or see
- @{article:libphutil Libraries User Guide} for a general introduction to
+ @{article@contributor:Adding New Classes} for a general introduction to
libphutil libraries.
- **https.cabundle**: specifies the path to an alternate certificate bundle
for use when making HTTPS connections.
- **lint.engine**: the name of a subclass of
@{class@arcanist:ArcanistLintEngine}, which should be used to apply lint
rules to this project. See @{article:Arcanist User Guide: Lint}.
- **unit.engine**: the name of a subclass of
@{class@arcanist:ArcanistUnitTestEngine}, which should be used to apply
unit test rules to this project. See
@{article:Arcanist User Guide: Customizing Lint, Unit Tests and Workflows}.
These options are supported, but their use is discouraged:
- **http.basicauth.user**: specify an HTTP basic auth username for use when
connecting to Phabricator.
- **http.basicauth.pass**: specify an HTTP basic auth password for use when
connecting to Phabricator.
- **https.blindly-trust-domains**: a list of domains to trust blindly over
HTTPS, even if their certificates are invalid. This is a brute force
solution to certificate validity problems, and is discouraged. Instead,
use valid certificates.
For a complete list of options, run `arc get-config`. Although all
options can be set in `.arcconfig`, some options (like `editor`) usually do not
make sense to set here because they're likely to vary from user to user.
= History Mutability =
Arcanist workflows run in two broad modes: either history is //mutable// or
//immutable//. Under a //mutable// history, `arc` commands may rewrite the
working copy history; under an //immutable// history, they may not.
You control history mutability by setting `history.immutable` to `true` or
`false` in your configuration. By default, it is `false` in Git (i.e.,
//mutable//) and `true` in Mercurial (i.e., //immutable//). The sections below
explain how these settings affect workflows.
== History Mutability: Git ==
In a workflow with //mutable// history, you rewrite local history. You develop
in feature branches, but squash or amend before pushing by using `git commit
--amend`, `git rebase -i`, or `git merge --squash`. Generally, one idea in
the remote is represented by one commit.
In a workflow with //immutable// history, you do not rewrite local history. You
develop in feature branches and push them without squashing commits. You do not
use `git commit --amend` or `git rebase -i`. Generally, one idea in the
remote is represented by many commits.
Practically, these are the differences you'll see based on your setting:
- **Mutable**
- `arc diff` will prompt you to amend lint changes into HEAD.
- `arc diff` will amend the commit message in HEAD after creating a
revision.
- `arc land` will default to the `--squash` strategy.
- `arc amend` will amend the commit message in HEAD with information from
the corresponding or specified Differential revision.
- **Immutable**
- `arc diff` will abort if it makes lint changes.
- `arc diff` will not amend the commit message in HEAD after creating a
revision.
- `arc land` will default to the `--merge` strategy.
- `arc amend` will exit with an error message.
== History Mutability: Mercurial ==
Before version 2.2, stock Mercurial has no history mutation commands, so
this setting has no effect. With Mercurial 2.2. or newer, making history
//mutable// means:
- **Mutable** (versions 2.2 and newer)
- `arc diff` will amend the commit message in `.` after creating a
revision.
- `arc amend` will amend the commit message in `.` with information from
the corresponding or specified Differential revision.
- **Immutable** (or versions prior to 2.2)
- `arc diff` will not amend the commit message in `.` after creating a
revision.
- `arc amend` will exit with an error message.
= How Libraries Are Located =
If you specify an external library to load, like 'examplelib', and use a
relative path like this:
{
...
"load": [
"examplelib/src"
],
...
}
...arc looks for it by trying these paths:
- `path/to/root/examplelib/src/` First, arc looks in the project's root
directory (where the `.arcconfig` lives) to see if the library is part of
the project. This makes it easy to just put project-specific code in a
project.
- `path/to/root/../examplelib/src/` Next, arc looks //next to// the project's
root directory to see if the library is in a sibling directory. If you
work with several repositories, this makes it easy to put all the `arc`
code in one repository and just check it out in the same directory as
everything else.
- `php/include/path/examplelib/src` Finally, arc falls back to PHP, which
will look in paths described in the `include_path` php.ini setting. This
allows you to install libraries in some global location if you prefer.
You can alternately supply an absolute path, like `/var/arc/examplelib/src`, but
then everyone will need to install the library at that exact location.
NOTE: Specify the path to the directory which includes
`__phutil_library_init__.php`. For example, if your init file is in
`examplelib/src/__phutil_library_init__.php`, specify `examplelib/src`,
not just `examplelib/`.
The general intent here is:
- Put project-specific code in some directory in the project, like
`support/arc/src/`.
- Put shared code (e.g., which enforces general coding standards or hooks
up to unit tests or whatever) in a separate repository and check it out
next to other repositories.
- Or put everything in some standard location and add it to `include_path`.
= Running Without .arcconfig =
Although you don't need to set up `.arcconfig`, and you can run `arc` command
that require a working copy in any Git, Subversion or Mercurial working copy,
some features won't work unless you set up an `.arcconfig` file.
Without `.arcconfig`:
- You will need to set a default Phabricator URI with
`arc set-config default <uri>`, or specify an explicit URI
with `--conduit-uri` each time you run a command.
- You will not be able to run linters through arc unless you pass `--engine`
explicitly.
- You will not be able to customize certain linter parameters even with
`--engine`.
- You will not be able to run unit tests through arc unless you pass
`--engine` explicitly.
- You will not be able to trigger lint and unit integration through
`arc diff`.
- You will not be able to put Git working copies into immutable history mode
(see below).
- You will not be able to specify a repository encoding. UTF-8 will be assumed
if you do not pass `--encoding`.
- You will not be able to add plugins to arc to modify existing workflows or
add new ones.
- You will not be able to load additional libraries unless you specify them
explicitly with `--load-phutil-library`.
- Symbol index integration, which allows users to click function or class
names in Differential and jump to their definitions, will not work.
- `arc patch` will be unable to detect that you are applying changes to the
wrong project.
- In Subversion, `arc` will be unable to determine the canonical root
of a project, and will assume it is the working directory (in Subversion
prior to 1.7) or the root of the checkout (in Subversion after 1.7). This
means the paths of files in diffs won't be anchored to the same place,
and will have different amounts of path context, which may be confusing for
reviewers and will sometimes prevent patches from applying properly if they
are applied against a different directory than they were generated from.
- In Subversion, `arc` will be unable to guess that you intend to update
an existing revision; you must use `--update` explicitly or `--preview`
and attach diffs via the web interface.
= Next Steps =
Continue by:
- returning to @{article:Arcanist User Guide}.
diff --git a/src/docs/user/userguide/events.diviner b/src/docs/user/userguide/events.diviner
index 888fe2cf06..5bb8eb313a 100644
--- a/src/docs/user/userguide/events.diviner
+++ b/src/docs/user/userguide/events.diviner
@@ -1,330 +1,330 @@
@title Events User Guide: Installing Event Listeners
@group userguide
Using Phabricator event listeners to customize behavior.
= Overview =
Phabricator and Arcanist allow you to install custom runtime event listeners
which can react to certain things happening (like a Maniphest Task being edited
or a user creating a new Differential Revision) and run custom code to perform
logging, synchronize with other systems, or modify workflows.
These listeners are PHP classes which you install beside Phabricator or
Arcanist, and which Phabricator loads at runtime and runs in-process. They
require somewhat more effort upfront than simple configuration switches, but are
the most direct and powerful way to respond to events.
= Installing Event Listeners (Phabricator) =
To install event listeners in Phabricator, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions,
- see @{article:libphutil Libraries User Guide}.
+ see @{article@contributor:Adding New Classes}.
- Configure Phabricator to load the library by adding it to `load-libraries`
in the Phabricator config.
- Configure Phabricator to install the event listener by adding the class
name to `events.listeners` in the Phabricator config.
You can verify your listener is registered in the "Events" tab of DarkConsole.
It should appear at the top under "Registered Event Listeners". You can also
see any events the page emitted there. For details on DarkConsole, see
@{article:Using DarkConsole}.
= Installing Event Listeners (Arcanist) =
To install event listeners in Arcanist, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions,
- see @{article:libphutil Libraries User Guide}.
+ see @{article@contributor:Adding New Classes}.
- Configure Phabricator to load the library by adding it to `load`
in the Arcanist config (e.g., `.arcconfig`, or user/global config).
- Configure Arcanist to install the event listener by adding the class
name to `events.listeners` in the Arcanist config.
You can verify your listener is registered by running any `arc` command with
`--trace`. You should see output indicating your class was registered as an
event listener.
= Example Listener =
Phabricator includes an example event listener,
@{class:PhabricatorExampleEventListener}, which may be useful as a starting
point in developing your own listeners. This listener listens for a test
event that is emitted by the script `scripts/util/emit_test_event.php`.
If you run this script normally, it should output something like this:
$ ./scripts/util/emit_test_event.php
Emitting event...
Done.
This is because there are no listeners for the event, so nothing reacts to it
when it is emitted. You can add the example listener by either adding it to
your `events.listeners` configuration or with the `--listen` command-line flag:
$ ./scripts/util/emit_test_event.php --listen PhabricatorExampleEventListener
Installing 'PhabricatorExampleEventListener'...
Emitting event...
PhabricatorExampleEventListener got test event at 1341344566
Done.
This time, the listener was installed and had its callback invoked when the
test event was emitted.
= Available Events =
You can find a list of all Phabricator events in @{class:PhabricatorEventType}.
== All Events ==
The special constant `PhutilEventType::TYPE_ALL` will let you listen for all
events. Normally, you want to listen only to specific events, but if you're
writing a generic handler you can listen to all events with this constant
rather than by enumerating each event.
== Arcanist Events ==
Arcanist event constants are listed in @{class@arcanist:ArcanistEventType}.
All Arcanist events have this data available:
- `workflow` The active @{class@arcanist:ArcanistWorkflow}.
== Arcanist: Commit: Will Commit SVN ==
The constant for this event is `ArcanistEventType::TYPE_COMMIT_WILLCOMMITSVN`.
This event is dispatched before an `svn commit` occurs and allows you to
modify the commit message. Data available on this event:
- `message` The text of the message.
== Arcanist: Diff: Will Build Message ==
The constant for this event is `ArcanistEventType::TYPE_DIFF_WILLBUILDMESSAGE`.
This event is dispatched before an editable message is presented to the user,
and allows you to, e.g., fill in default values for fields. Data available
on this event:
- `fields` A map of field values to be compiled into a message.
== Arcanist: Diff: Was Created ==
The constant for this event is `ArcanistEventType::TYPE_DIFF_WASCREATED`.
This event is dispatched after a diff is created. It is currently only useful
for collecting timing information. No data is available on this event.
== Arcanist: Revision: Will Create Revision ==
The constant for this event is
`ArcanistEventType::TYPE_REVISION_WILLCREATEREVISION`.
This event is dispatched before a revision is created. It allows you to modify
fields to, e.g., edit revision titles. Data available on this event:
- `specification` Parameters that will be used to invoke the
`differential.createrevision` Conduit call.
== Controller: Check Request ==
The constant for this event is
`PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST`.
This event is dispatched when controller is about to begin execution. It is
meant for checking if the user is allowed to use the application at the moment.
It can check if the user has performed too many operations recently, if his IP
address is allowed or if the servers are overloaded to process the request.
Data available on this event:
- `request` Object of class @{class:AphrontRequest}.
- `controller` Class name of the current controller.
You can delegate the execution to another controller by modifying `controller`.
== Maniphest: Will Edit Task ==
The constant for this event is
`PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK`.
This event is dispatched before a task is edited, and allows you to respond to
or alter the edit. Data available on this event:
- `task` The @{class:ManiphestTask} being edited.
- `transactions` The list of edits (objects of class
@{class:ManiphestTransaction}) being applied.
- `new` A boolean indicating if this task is being created.
- `mail` If this edit originates from email, the
@{class:PhabricatorMetaMTAReceivedMail} object.
This is similar to the next event (did edit task) but occurs before the edit
begins.
== Maniphest: Did Edit Task ==
The constant for this event is
`PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK`.
This event is dispatched after a task is edited, and allows you to react to the
edit. Data available on this event:
- `task` The @{class:ManiphestTask} that was edited.
- `transactions` The list of edits (objects of class
@{class:ManiphestTransaction}) that were applied.
- `new` A boolean indicating if this task was newly created.
- `mail` If this edit originates from email, the
@{class:PhabricatorMetaMTAReceivedMail} object.
This is similar to the previous event (will edit task) but occurs after the
edit completes.
== Differential: Will Mark Generated ==
The constant for this event is
`PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED`.
This event is dispatched before Differential decides if a file is generated (and
doesn't need to be reviewed) or not. Data available on this event:
- `corpus` Body of the file.
- `is_generated` Boolean indicating if this file should be treated as
generated.
== Diffusion: Did Discover Commit ==
The constant for this event is
`PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT`.
This event is dispatched when the daemons discover a commit for the first time.
This event happens very early in the pipeline, and not all commit information
will be available yet. Data available on this event:
- `commit` The @{class:PhabricatorRepositoryCommit} that was discovered.
- `repository` The @{class:PhabricatorRepository} the commit was discovered
in.
== Diffusion: Lookup User ==
The constant for this event is
`PhabricatorEventType::TYPE_DIFFUSION_LOOKUPUSER`.
This event is dispatched when the daemons are trying to link a commit to a
Phabricator user account. You can listen for it to improve the accuracy of
associating users with their commits.
By default, Phabricator will try to find matches based on usernames, real names,
or email addresses, but this can result in incorrect matches (e.g., if you have
several employees with the same name) or failures to match (e.g., if someone
changed their email address). Listening for this event allows you to intercept
the lookup and supplement the results from another datasource.
Data available on this event:
- `commit` The @{class:PhabricatorRepositoryCommit} that data is being looked
up for.
- `query` The author or committer string being looked up. This will usually
be something like "Abraham Lincoln <alincoln@logcabin.example.com>", but
comes from the commit metadata so it may not be well-formatted.
- `result` The current result from the lookup (Phabricator's best guess at
the user PHID of the user named in the "query"). To substitute the result
with a different result, replace this with the correct PHID in your event
listener.
Using @{class@libphutil:PhutilEmailAddress} may be helpful in parsing the query.
== Search: Did Update Index ==
The constant for this event is
`PhabricatorEventType::TYPE_SEARCH_DIDUPDATEINDEX`.
This event is dispatched from the Search application's indexing engine, after
it indexes a document. It allows you to publish search-like indexes into other
systems.
Note that this event happens after the update is fully complete: you can not
prevent or modify the update. Further, the event may fire significantly later
in real time than the update, as indexing may occur in the background. You
should use other events if you need guarantees about when the event executes.
Finally, this event may fire more than once for a single update. For example,
if the search indexes are rebuilt, this event will fire on objects which have
not actually changed.
So, good use cases for event listeners are:
- Updating secondary search indexes.
Bad use cases are:
- Editing the object or document.
- Anything with side effects, like sending email.
Data available on this event:
- `phid` The PHID of the updated object.
- `object` The object which was updated (like a @{class:ManiphesTask}).
- `document` The @{class:PhabricatorSearchAbstractDocument} which was indexed.
This contains an abstract representation of the object, and may be useful
in populating secondary indexes because it provides a uniform API.
== Test: Did Run Test ==
The constant for this event is
`PhabricatorEventType::TYPE_TEST_DIDRUNTEST`.
This is a test event for testing event listeners. See above for details.
== UI: Did Render Actions ==
The constant for this event is
`PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS`.
This event is dispatched after a @{class:PhabricatorActionListView} is built by
the UI. It allows you to add new actions that your application may provide, like
"Fax this Object". Data available on this event:
- `object` The object which actions are being rendered for.
- `actions` The current list of available actions.
NOTE: This event is unstable and subject to change.
= Debugging Listeners =
If you're having problems with your listener, try these steps:
- If you're getting an error about Phabricator being unable to find the
listener class, make sure you've added it to a libphutil library and
configured Phabricator to load the library with `load-libraries`.
- Make sure the listener is registered. It should appear in the "Events" tab
of DarkConsole. If it's not there, you may have forgotten to add it to
`events.listeners`.
- Make sure it calls `listen()` on the right events in its `register()`
method. If you don't listen for the events you're interested in, you
won't get a callback.
- Make sure the events you're listening for are actually happening. If they
occur on a normal page they should appear in the "Events" tab of
DarkConsole. If they occur on a POST, you could add a `phlog()`
to the source code near the event and check your error log to make sure the
code ran.
- You can check if your callback is getting invoked by adding `phlog()` with
a message and checking the error log.
- You can try listening to `PhutilEventType::TYPE_ALL` instead of a specific
event type to get all events, to narrow down whether problems are caused
by the types of events you're listening to.
- You can edit the `emit_test_event.php` script to emit other types of
events instead, to test that your listener reacts to them properly. You
might have to use fake data, but this gives you an easy way to test the
at least the basics.
- For scripts, you can run under `--trace` to see which events are emitted
and how many handlers are listening to each event.
= Next Steps =
Continue by:
- taking a look at @{class:PhabricatorExampleEventListener}; or
- building a library with @{article:libphutil Libraries User Guide}.
diff --git a/src/docs/user/userguide/libraries.diviner b/src/docs/user/userguide/libraries.diviner
deleted file mode 100644
index b0614d7a8f..0000000000
--- a/src/docs/user/userguide/libraries.diviner
+++ /dev/null
@@ -1,155 +0,0 @@
-@title libphutil Libraries User Guide
-@group userguide
-
-Guide to creating and managing libphutil libraries.
-
-= Overview =
-
-libphutil includes a library system which organizes PHP classes and functions
-into modules. Some extensions and customizations of Arcanist and Phabricator
-require you to make code available to Phabricator by providing it in a libphutil
-library.
-
-For example, if you want to store files in some kind of custom storage engine,
-you need to write a class which can interact with that engine and then tell
-Phabricator to load it.
-
-In general, you perform these one-time setup steps:
-
- - Create a new directory.
- - Use `arc liberate` to initialize and name the library.
- - Add a dependency on Phabricator if necessary.
- - Add the library to your Phabricator config or `.arcconfig` so it will be
- loaded at runtime.
-
-Then, to add new code, you do this:
-
- - Write or update classes.
- - Update the library metadata by running `arc liberate` again.
-
-= Creating a New Library =
-
-To **create a new libphutil library**:
-
- $ mkdir libcustom/
- $ cd libcustom/
- libcustom/ $ arc liberate src/
-
-Now you'll get a prompt like this:
-
- lang=txt
- No library currently exists at that path...
- The directory '/some/path/libcustom/src' does not exist.
-
- Do you want to create it? [y/N] y
- Creating new libphutil library in '/some/path/libcustom/src'.
- Choose a name for the new library.
-
- What do you want to name this library?
-
-Choose a library name (in this case, "libcustom" would be appropriate) and it
-you should get some details about the library initialization:
-
- lang=txt
- Writing '__phutil_library_init__.php' to
- '/some/path/libcustom/src/__phutil_library_init__.php'...
- Using library root at 'src'...
- Mapping library...
- Verifying library...
- Finalizing library map...
- OKAY Library updated.
-
-This will write three files:
-
- - `src/.phutil_module_cache` This is a cache which makes "arc liberate"
- faster when you run it to update the library. You can safely remove it at
- any time. If you check your library into version control, you can add this
- file to ignore rules (like .gitignore).
- - `src/__phutil_library_init__.php` This records the name of the library and
- tells libphutil that a library exists here.
- - `src/__phutil_library_map__.php` This is a map of all the symbols
- (functions and classes) in the library, which allows them to be autoloaded
- at runtime and dependencies to be statically managed by "arc liberate".
-
-= Linking with Phabricator =
-
-If you aren't using this library with Phabricator (e.g., you are only using it
-with Arcanist or are building something else on libphutil) you can skip this
-step.
-
-But, if you intend to use this library with Phabricator, you need to define its
-dependency on Phabricator by creating a `.arcconfig` file which points at
-Phabricator. For example, you might write this file to
-`libcustom/.arcconfig`:
-
- {
- "load": [
- "phabricator/src/"
- ]
- }
-
-For details on creating a `.arcconfig`, see
-@{article:Arcanist User Guide: Configuring a New Project}. In general, this
-tells `arc liberate` that it should look for symbols in Phabricator when
-performing static analysis.
-
-NOTE: If Phabricator isn't located next to your custom library, specify a
-path which actually points to the `phabricator/` directory.
-
-You do not need to declare dependencies on `arcanist` or `libphutil`,
-since `arc liberate` automatically loads them.
-
-Finally, edit your Phabricator config to tell it to load your library at
-runtime, by adding it to `load-libraries`:
-
- ...
- 'load-libraries' => array(
- 'libcustom' => 'libcustom/src/',
- ),
- ...
-
-Now, Phabricator will be able to load classes from your custom library.
-
-= Writing Classes =
-
-To actually write classes, create a new module and put code in it:
-
- libcustom/ $ mkdir src/example/
- libcustom/ $ nano src/example/ExampleClass.php # Edit some code.
-
-Now, run `arc liberate` to regenerate the static resource map:
-
- libcustom/ $ arc liberate src/
-
-This will automatically regenerate the static map of the library.
-
-= What You Can Extend And Invoke =
-
-libphutil, Arcanist and Phabricator are strict about extensibility of classes
-and visibility of methods and properties. Most classes are marked `final`, and
-methods have the minimum required visibility (protected or private). The goal of
-this strictness is to make it clear what you can safely extend, access, and
-invoke, so your code will keep working as the upstream changes.
-
-When developing libraries to work with libphutil, Arcanist and Phabricator, you
-should respect method and property visibility and extend only classes marked
-`@stable`. They are rendered with a large callout in the documentation (for
-example: @{class@libphutil:AbstractDirectedGraph}). These classes are external
-interfaces intended for extension.
-
-If you want to extend a class but it is not marked `@stable`, here are some
-approaches you can take:
-
- - Good: If possible, use composition rather than extension to build your
- feature.
- - Good: Check the documentation for a better way to accomplish what you're
- trying to do.
- - Good: Let us know what your use case is so we can make the class tree more
- flexible or configurable, or point you at the right way to do whatever
- you're trying to do, or explain why we don't let you do it.
- - Discouraged: Send us a patch removing "final" (or turning "protected" or
- "private" into "public"). We generally will not accept these patches, unless
- there's a good reason that the current behavior is wrong.
- - Discouraged: Create an ad-hoc local fork and remove "final" in your copy of
- the code. This will make it more difficult for you to upgrade in the future.
- - Discouraged: Use Reflection to violate visibility keywords.

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:08 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126800
Default Alt Text
(82 KB)

Event Timeline